Commit Graph
1284 Commits
Author SHA1 Message Date
digital-garbageandClaude Opus 5 cd6639e7a7 feat: guide strengths span the full 0..1 range
mid_scene_guide_strength, the continuity pin/prior/mid-scene strengths, and
the per-guide strength field in the inspector were all clamped to 0.25-0.5 —
the measured audio-safe band, hardcoded as a limit. That band is advice, not
physics: it stays in the tooltips, the clamps go.

Backend clamps in timeline.py, the node input range, the JoyAI memory guide's
0.25 floor, and the three UI spinners all move to 0..1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:32:20 +03:00
digital-garbageandClaude Opus 5 68a697d521 fix: drop the H3 v2a_grad_scale chip
v2a_grad_scale does nothing without joyai_audio_memory on any model, and
gating the chip on that toggle still left a warning about a knob the user
never asked about. Removes the chip, its fingerprint inputs, and the docs
line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:23:14 +03:00
digital-garbageandClaude Opus 5 05a97ada2d fix: turning JoyAI audio memory off clears the H3 v2a chip
The gate added in f40ae82 was right, but fpActionbar never listed
joyai_audio_memory — so switching it off changed nothing the fingerprint
could see and the actionbar zone skipped its repaint, leaving the chip
stranded next to Generate until some other field forced a redraw.

Adds joyai_audio_memory, plus models.disable_core (every chip is gated on
the Chain Sampler running at all), and reads the models config through the
same state.models || project.models fallback PipelineCaps uses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 11:36:29 +03:00
digital-garbageandClaude Opus 5 7af38e2e55 Merge dev into main - Release 3.5.0
MiniMax H3 as a second model family, alongside LTX-2 / LTXAV. See CHANGELOG.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v3.5.0
2026-08-04 22:57:25 +03:00
digital-garbageandClaude Opus 5 a407b01e9f release: 3.5.0 — MiniMax H3 as a second model family
Version bump and changelog for everything on dev since 3.4.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:57:10 +03:00
digital-garbageandClaude Opus 5 f40ae828a5 fix: the H3 v2a hint only fires when JoyAI audio memory is on; guard the inputs too
Two things, both from the same debugging session.

v2a_grad_scale is JoyAI's coupling knob and is never installed without
joyai_audio_memory (see the call site), so a left-over value with the feature
off was being reported as an H3 limitation — blaming the model for a knob that
would be equally inert on LTX. Both the sampler's pre-run notice and the
Editor's inert-setting hint now gate on joyai_audio_memory.

The NaN guard only saw the sampler's OUTPUT, which cannot tell "the model
computed garbage" from "the model was handed garbage and propagated it" — and
those have disjoint suspect lists. _sample_chunk now checks the incoming latent
and the schedule first, and says which it was. The schedule check is there
because it is hand-typed and our solvers divide by sigma (er = sigma_next /
sigma): an interior zero or a repeated value is an instant Inf that poisons
every value downstream. A trailing zero stays legal — it is only ever a step's
target, never a divisor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:48:15 +03:00
digital-garbageandClaude Opus 5 9ea0be0713 feat: a non-finite latent stops the run where it happened
A chunk that samples to completion and returns NaN/Inf currently sails through
everything: the scene blend spreads it into the already-finished scenes, the VAE
decodes it, and the first thing that objects is ffmpeg's AAC encoder — "Input
contains (near) NaN/+-Inf", naming nothing that produced it, after the whole
montage has been paid for. The video half never objects at all: NaN through
astype(np.uint8) is undefined but silent, so it degrades with a warning.

_sample_chunk now checks the returned latent and raises with the run-phase label
("scene 2/3 · pass 1 of 2"), which stream, how many values and what dtype, plus
the causes worth testing in order — mismatched LoRA, a mis-inferred checkpoint
layout, a dtype the model cannot hold. It raises rather than warns because a
non-finite latent is unrecoverable and blending it forward destroys the scenes
that were still good. One isfinite() per chunk against a multi-second sample is
not a cost worth weighing; a latent the helper cannot read is ignored, never
fatal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:41:45 +03:00
digital-garbageandClaude Opus 5 8e8a4a5471 fix: the log panel stops eating your selection mid-copy
Every 1500 ms the poll did `bodyEl.textContent = lines.join("\n")`, and
assigning textContent destroys and rebuilds the text node — which drops
whatever the user had selected. Copying three lines out of a running log was
a race against the next tick.

Now the write is withheld while a non-collapsed selection sits inside the log
body (a small "paused — selection held" note says so), and lands as soon as the
selection is released or moves out. Unchanged text no longer touches the DOM at
all, so an idle log is completely still. Adds an explicit ⏸ Pause toggle for
deliberate freezing, and Copy now copies just the selection when there is one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:33:15 +03:00
digital-garbageandClaude Opus 5 4372988bc2 fix: a core override left behind by a family switch is no longer emitted
core_overrides are saved per core ID, not per node class, so switching model
family leaves the replaced node's input names sitting on its successor:
audiodec.audio_vae (LTXVAudioVAEDecode) survives onto VAEDecodeAudio, whose VAE
input is called `vae`. The Models panel lists the NEW node's inputs, so the
leftover is invisible there — it only surfaces when ComfyUI is handed a kwarg
the node never declared and the run dies inside it:

  VAEDecodeAudio.execute() got an unexpected keyword argument 'audio_vae'

Guided wiring filtered these out as a side effect of its open-port check; full
control applied them.

The builder now refuses to write an input the node's class does not declare and
says so in the report (a leftover is not a reason to refuse the whole run), and
the family switch prunes overrides the new core has no input for, so it stops
being carried in the saved project. A valid override is untouched by both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:18:12 +03:00
digital-garbageandClaude Opus 5 0bf1a80820 feat: the KSampler pass can build its own schedule from steps + a scheduler
A SAMPLER object is only the step function, so picking KSampler used to leave
the pass with no schedule at all unless a sigma list was typed by hand.

steps + schedule now build one from the loaded model's model_sampling, exactly
as ComfyUI's BasicScheduler does. The schedule dropdown is the switch between
the two sources rather than a silent precedence rule: "use_user_sigmas" — the
default, so nothing changes for an existing config — runs the Sigmas field as
typed, and any real scheduler computes instead, leaving the typed schedule
parked in the field to switch back to without retyping it. Studio logs the fact
that the field is being ignored, and steps is only shown while it applies.

Both frontends, same change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:07:55 +03:00
digital-garbageandClaude Opus 5 e001853f7a fix: H3 token tags are reconciled with the conditioning Studio hands on
minimax_h3.py shipped tags_match / token_tags_length / extend_token_tags with a
docstring saying a mismatch is a hard IndexError mid-forward — and nothing ever
called them. Studio edits conditioning in many places, and H3 tokenizes reference
images into the same span (ref_image_size "match" scales them to the output
resolution), so the token count moves with the prompt AND with the resolution.
That is why the crash looked intermittent and why editing either one made it come
and go, reported only as a bare "list index out of range" at step 0.

Studio now reconciles before returning: extend the tags when tokens were appended,
drop them when tokens were removed (absent tags the DiT handles, wrong tags it does
not), and print which happened — losing the tags changes how the text span is
modulated, so it must not be silent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 22:03:24 +03:00
digital-garbageandClaude Opus 5 981798d6d9 fix: the sampler-name list comes from ComfyUI, not a hardcoded eight
Studio's KSampler mode passes the chosen name straight to comfy.samplers.
sampler_object(), so any sampler this ComfyUI has always worked — but both
frontends offered a hardcoded list of eight written before the rest existed.
That silently hid 36 of them, including res_multistep, which is what ComfyUI's
own MiniMax H3 templates use.

Both now read sampler_name from the live KSampler node spec and fall back to the
old list if that fetch fails. A saved name missing from the live list stays
selectable instead of being swapped for the first option.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:52:14 +03:00
digital-garbageandClaude Opus 5 3506471906 fix: H3 decodes audio from the sampler latent, matching the reference graph
ComfyUI's official H3 templates (video_minimax_h3_r2v / _i2v) feed the raw sampler
latent to BOTH VAEDecode and VAEDecodeAudio and let each VAE take its own stream.
We unbound it first via LTXVSeparateAVLatent, so vae_decode_audio — which hands
samples["samples"] straight to vae.decode() — received a different object than the
reference graph gives it. Video was unaffected because the Chain Sampler decodes
video itself, which matches the symptom: clean video, corrupted audio.

Separate stays in the H3 graph for the refinement save's video latent. LTX is
untouched. UNVALIDATED live — no H3 hardware here; revert this link if audio is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:46:12 +03:00
digital-garbageandClaude Opus 5 9eb56e791b feat: reference media is marked in the bin and wired to node inputs
Replaces the Engine Settings 'Reference media' tab. Any image / video / audio in
the Media Bin (Editor) or Gallery (Easy Gen) takes an 'R' mark beside the identity
pin; any number can carry one, and mark ORDER is the numbering the badge shows.
Each marked item then appears as an input source in Models & Pipeline — 'R1 ·
face.png (image)' — for every socket its kind can fill, so references reach a node
the same way the timeline image does.

At generation the marked files are copied into ComfyUI's input folder and the
builder injects one loader per reference, chosen by what the DESTINATION socket
asks for (IMAGE / AUDIO / VIDEO), shared when two sockets take the same one. A
reference whose file is gone, or one no installed node can load into that socket,
is reported against the socket that wanted it instead of silently unwired.

The editor no longer feeds Studio's built-in ref2va list: reference media is
wired now, not configured. Project.h3_references stays readable so old project
files still load, but nothing writes or sends it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:44:23 +03:00
digital-garbageandClaude Opus 5 209c5d01f8 fix: the updater survives an upstream history rewrite
git pull --ff-only cannot cross a rewritten history — every local commit has a new
hash, so git reports divergence and the in-app update dead-ends in a git hint.
On that failure the updater now asks git cherry whether any local commit's CONTENT
is missing upstream: if none is, nothing can be lost, so it realigns to origin and
reports that it did; if there is real local work, it refuses and says how many
commits would go. A cherry that fails to run counts as 'not proven safe'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:23:06 +03:00
digital-garbageandClaude Opus 5 eb799743fe fix: autogrow entries are addressed by their dotted socket id
ComfyUI expands an autogrow list into sockets named <parent>.<template name> —
'ref_images.ref_image_0' — and splits that path to rebuild the node's list. We
sent the bare template name, which validation reports as a missing required
input and, where it slips past, arrives at execute() as an unexpected keyword
argument. Verified both ways against a live ComfyUI: the bare name is rejected,
the dotted one queues and executes.

The UI keeps showing the short name; only the wire key changed. Configs saved
under the old bare name are renamed in place on load, and the builder maps any
that remain, so an existing pipeline doesn't keep failing every run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:16:32 +03:00
digital-garbageandClaude Opus 5 22607b0c14 refactor: core widget baseline is a values-only file, keyed by core id
The editor seeded its fixed core nodes from a saved graph dump. Replaced with a
small file holding just the widget values the builder consumes — no free text,
nothing user-typed, since every text field a run needs arrives from the project
at generate time. Graph output verified identical against live object_info.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:04:20 +03:00
digital-garbageandClaude Opus 5 e1ae4acea9 fix: wire H3 reference lists, bind node prompts to the project prompt, survive a missing projector file
Three things blocked the MiniMax-H3 reference node in Models & Pipeline:

Autogrow list inputs (COMFY_AUTOGROW_V3) were offered as a single unwireable
socket typed after the list wrapper, so nothing — not the timeline image, not
another node's IMAGE output — matched them. They are a TEMPLATE, not a socket:
ComfyUI expands them into ref_image0, ref_image1, … and rebuilds the dict from
whichever indices the API graph carries. connection_inputs() now expands them
the same way, typed as the element type, and the editor shows the filled entries
plus one empty slot so a 50-entry list doesn't bury the node. Auto-wire skips
them — a lone IMAGE producer would otherwise be copied into every index.

A node's own prompt field can now be driven by the project prompt: the linked-
input 'Driven by' list gained Prompt / Negative prompt / Seed, filtered to the
widget kinds each can legitimately fill.

A combo value whose file isn't on this machine (a saved ArcFace projector with
models/loras empty) made ComfyUI reject the WHOLE prompt, so nothing queued even
with Best-FaceID switched off. Editor-supplied Studio/Chain-Sampler values are
now checked against the live list and fall back to the node's default with a
visible note, the same rule slot nodes already had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 19:56:32 +03:00
digital-garbageandClaude Opus 5 ab85ef4ca9 test: kill the 22 chronic failures — they were stub collisions, not redundant tests
Every one of them was a real test of a live feature. 18 failed only in a full-suite run and
passed alone, 4 asserted behaviour that was deliberately changed and never got updated.

Ordering (18): each test module built its own partial `comfy` stub and registered it with
`sys.modules.setdefault(...)`, so the first module to import won and every later module's
richer stub was silently discarded — the scene chain sampler then died on `comfy` having no
`.sample`, and the mask-safe attention install bailed at its first guard because
`import comfy.ldm.modules.attention` resolves through the PARENT's attribute, which one
module set and another did not. tests/_comfy_stubs.py now builds that package tree once,
wiring every parent attribute, and test modules attach their fakes as attributes on the
shared modules (per-test, via fixtures, where the fake is only theirs — the idiom
test_detailing.py already used). Same file also stubs `server.PromptServer`, whose absence
made every module that imports templates.py (i.e. anything importing conditioning) fail on
`No module named 'server'`.

Stale assertions (4):
* the "every learning rating" loop excluded non-learning ratings by hardcoded LABEL, so the
  later-added `Wrong appearance|loved` and the two control sentinels were asserted to train
  phrase memory. Now filtered by profile key, with the reason per key.
* three split_by_transitions tests still used "scene ten" / "cut to" as cut markers. The
  unified splitter cuts on a real transition trigger or the generic `scene <N>` label, on
  purpose — an incidental phrase must never split a prompt. Rewritten on the current
  contract, and they now also pin that the delimiter is dropped rather than encoded.

Verified: 584 pass, and every file passes alone, in reverse order, and with movie_editor's
suite standalone. Mutation-checked the rewritten split tests (disabling the scene-label
pattern fails 4 of 5) so they are not passing vacuously.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:48:58 +03:00
digital-garbageandClaude Opus 5 4aec53e01b feat: MiniMax-H3 against the merged release — real i2v anchors, model-owned frame geometry
H3 support was written against ComfyUI's PR; it has since merged (57500fc5, shipped in
v0.30.0) and the weights are out. Everything the adapter assumed still holds — audio time
axis, 17k+5 grid, batch-1 forward, the imported optimized_attention symbol, every payload
key — so this is what the release itself changed, not a correction of the reading. LTX-2 /
LTXAV is untouched throughout: a project picks a family and both are first-class.

* i2v anchors now work on H3. LTX writes the image into the starting latent
  (LTXVImgToVideoInplace); H3 has no such path — its anchor is a condition row packed
  beside the text — so a scene's anchor image becomes a frame-0 keyframe pin instead, and
  the LTX node is no longer run against an H3 latent. Studio hands the source image's
  pixels to the sampler (it owns the CLIP, the sampler owns the VAE). Pins now MERGE, so
  a first-frame anchor and a last-frame guide coexist: H3's own first-and-last mode.
* Frame geometry follows the model: frames per scene snap to the family's grid (LTX 8k+1,
  H3 17k+5) in both frontends, in the graph builder, and in the sampler, and H3 renders at
  its fixed 24 fps. Picking a family moves the project onto its geometry and says so.
* The two checkpoints are named. fl2va (anchors) and ref2va (references) load identically
  and never reject each other's conditioning, so the run reports which mode it is in and
  the setup panel spells out the split.
* Pipeline setup is no longer a placeholder: released, with the published filenames.
* Reference images can be encoded at the reference pipeline's 2048px short edge.

Fixes found on the way:
* A video reference lost its soundtrack between Studio and the sampler — Studio had
  already announced it to the text encoder as <Audio 1>, so every later audio ordinal in
  the prompt pointed one reference earlier than written.
* Easy Gen never knew it was on H3: its model config lives on the project, which the
  shared capability check did not read, so every H3 warning silently reported LTX.
* An off-grid scene length failed with arithmetic instead of an explanation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:17:43 +03:00
digital-garbageandClaude Opus 5 349cce2be8 feat: model family is chosen in Pipeline setup, with declared placeholders for H3
Setting the family in Models was the wrong first step — the family decides which nodes
and which model files everything after it looks for, so it is now the first question in
the Pipeline setup panel rather than something to find afterwards.

MiniMax H3 is not released: its nodes are in an unmerged ComfyUI PR and no weights exist.
Rather than hide the family until then, pipeline_deps.FAMILY_SETUP declares what it will
need — the slot nodes (with why each one is there) and the model files with their target
models/ folders — and the panel shows that as "not in your ComfyUI yet" / "to download
when published", with a link to the PR. Picking it configures the project now; it starts
working when ComfyUI ships the nodes and the weights land, with no further FunPack update.
The table is deliberately data, not code: shipping the real thing means flipping
"released" and filling in the hints, not editing the setup flow.

Dependency detection is family-aware throughout, so an H3 project is no longer told to
install packs for LTXVConditioning / Concat / LTXVAudioVAEDecode — nodes its graph never
emits. An unreleased family still counts as needs_setup, or the modal would never open
and a project that cannot generate would look ready. The install button and its lead text
only appear when there is something Manager can actually install; offering to fetch an
unreleased model would be a promise the button cannot keep.

Also reachable on demand: Models → Model family → Setup…, since the auto-prompt is
one-shot per browser and was previously unreachable once dismissed.

Verified live: picking MiniMax H3 in the panel switches the project, lists the H3 node and
all four model files with their folders, links the PR, and switches back cleanly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 01:44:56 +03:00
digital-garbageandClaude Opus 5 990b29b8c9 feat: MiniMax-H3 in the Editor and Easy Gen — family-aware graph, ref2va UI, video references
The graph builder is now parameterized by model family instead of assuming LTXAV. H3
keeps the core's shape but three of its nodes do not apply: LTXVConditioning (stamps LTX's
frame_rate onto conditioning, which H3 has no field for), LTXVConcatAVLatent (H3's own
EmptyMiniMaxH3LatentAV emits both streams, so the latent feeds the sampler directly) and
LTXVAudioVAEDecode (reads an output_sample_rate H3's audio VAE does not define — it would
raise). Requirements, wirable ports, role targets, guided-wiring hints and the port picker
all follow the family, so a loader can no longer be wired into a node the graph never emits.
The family is a Models setting, asked and never guessed from a checkpoint filename: a wrong
guess fails deep inside ComfyUI at generate time instead of in the panel that can fix it.

Video references now work, rather than being refused. FunPack owns the pipeline and already
requires ffmpeg for preview/export, so minimax_h3 decodes reference clips itself — once, at
24 fps — and derives both views from that single decode: the 2 fps timestamped frames Qwen
sees, and the full-rate latent block the DiT packs. A clip's own soundtrack rides with it,
presented as its <Audio j> label immediately before its <Video k>, which is what ties the
two together. Capped at 15s because reference tokens ride every sampling step.

Editor UI: a Reference media view (H3 only — there is no LTX equivalent to grey out) with
per-type 1-based tags shown exactly as the prompt must spell them, reorder controls that
renumber both sides at once, and a soundtrack picker on video entries. Warning chips beside
Generate for the LTX-only sampler settings H3 switches off, so that is known before a
generation is spent rather than after; Easy Gen gets the same warnings on its strip and the
same family selector (it already loads models.js and pipeline_caps.js).

Verified live in the Cutting Room: the family toggle flips requirements, ports and hints
both ways and persists per project; tags renumber on reorder; the H3 chips replace the
Best-FaceID chip on H3 and hand back to it on LTXAV.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 01:31:04 +03:00
digital-garbageandClaude Opus 5 c77fd95f12 feat: MiniMax-H3 ref2va — native reference conditioning, replacing the LTX identity path
H3 packs reference blocks into the sequence itself (own position grid, never denoised,
never rendered), which is the thing Best-FaceID approximated on LTX with overlap tokens
plus a trained ArcFace projector. On H3 there is nothing to project and nothing to slice
back off, so this is the model's own feature, not a port of that one.

A reference has to reach the model twice and in the same order both times: as
presentation (Qwen sees "<Picture 1>: <vision block>" so the prompt can name it) and as
a packed latent block. Those encodes happen in different nodes, so the ordered spec
travels between them:

- Studio owns the CLIP: resolves the reference list, bakes the presentation via the
  tokenizer's minimax_ref_items, and records what it presented as funpack_h3_refs on
  the conditioning. Reference media and a first-frame anchor are separate conditioning
  modes, so source_image steps aside (loudly) when references are present.
- Chain Sampler owns the VAE: encodes exactly that list, in that order, sized against
  the scene's canvas, and attaches minimax_refs. Applied on every scene branch so an
  identity holds across the whole chain, and cached per run so it is encoded once
  rather than once per scene.

Order is the contract — drop or reorder an entry and "<Picture 2>" points at a
different reference than the text encoder saw. Anything that cannot be encoded is
dropped AND named, and an audio reference lost for want of an audio_vae says out loud
that later <Audio j> labels have shifted.

New optional audio_vae input on the Chain Sampler (audio references only; image
references need just the main vae). Video references are refused with a reason rather
than half-implemented — they need 2 fps frame sampling and a video loader FunPack does
not own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 01:03:48 +03:00
digital-garbageandClaude Opus 5 c731d8d451 feat: MiniMax-H3 foundation — adapter module, stream axes, frame grid, keyframe guides
Day-0 groundwork for Comfy-Org/ComfyUI#15224 (MiniMax H3). New minimax_h3.py is the
one place that knows how H3 differs from LTXAV; everything else consults it.

Four divergences that failed silently before:

- Audio latent axis. LTXAV audio is [B,C,T,freq] (time on dim 2, same as video);
  H3 audio is [B,32,stereo,T]. Both 4-D, so only the model family can say which
  axis is time. The Chain Sampler now carries a per-stream time axis through
  continuation, blending, tail crops and JoyAI audio memory.
- Frame grid. LTXAV 8k+1 -> k+1; H3 17k+5 -> 5k+2. H3's downscale_index_formula
  is the index map, not the count map, so the old uniform formula under-counted
  by ~20%. Frame counts now come from vae.downscale_ratio[0], which reproduces
  LTXAV's answer exactly.
- Attention patches. LTX calls optimized_attention through its module; H3 binds
  it at import, so FunPack's temperature/capture patch was inert on H3. It now
  rebinds every module holding a reference, and position-indexed work (BachVid
  K/V, attn importance) sits out H3's pre-reshaped [1,heads,S,D] layout.
- Batch size 1. H3's DiT refuses a batched forward and comfy batches cond+uncond
  for cfg != 1.0. install_batch_split runs them one at a time, keeping cfg live.

Guides become keyframe pins: H3 packs condition rows into the sequence instead of
appending a masked latent frame, so no tail is appended and none is cropped. The
layout only accepts a first/last pin, and a mid-clip request is refused up front
instead of raising inside PackedLayout.

Studio: reference images route through Qwen3-VL's images= kwarg (the Gemma3 vision
probe returns False for H3 and was dropping them); the attn2 K/V direction patch
reports that H3 has no cross-attention to hook instead of installing a no-op.

Features that depend on LTX transformer structure (bounded attention, Best-FaceID
identity transfer, v2a_grad_scale, segmented detailing, second-pass latent ops) are
now switched off explicitly on H3 with the reason printed, rather than each one
discovering its own missing attribute mid-scene.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 00:51:54 +03:00
digital-garbageandClaude Opus 5 64ad739d2b feat: live elapsed timer on the Generate buttons
"Generating (44s)", counted from the press until the finished video comes
back. Both frontends.

Editor: the clock spans the WHOLE operation, not one run. pollStart already
existed but restarts on every run of a multi-run montage, so a press that
kicks off three runs would have shown the third run's age. genWallStart is
wrapped around the public entry points (generate / generateMontage /
generateSelected) in a finally, which no early-return error path can miss.

The tick writes the button's textContent directly and never re-renders the
zone: rebuilding every button once a second would throw away focus and hover
for a number one label owns. For the same reason the elapsed value is NOT in
store state and NOT in fpActionbar — the zone still repaints on gen.state,
which is what starts and stops the ticker.

A run started from Selected counts on that button; a montage or a single
scene fired from the inspector counts on the main one. Re-attaching after a
reload reads "1m 04s+" with a tooltip saying it counts from the reconnect,
since the press happened before the page load and the real total is unknown.

Seconds are zero-padded so the label's width changes once a minute rather
than once a second; tabular figures keep it from twitching in between.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 03:17:56 +03:00
digital-garbageandClaude Opus 5 0b8dbf91f0 fix: stop the sharpen round-trip from smearing through a box filter
_downscale_to used mode="area" — a 2x2 box for the exact-2x ratio both
callers hit. A box loses on both axes at once: its passband droops well
before Nyquist (detail comes back soft) and its stopband leaks (the detail
the upsampler just invented folds back as aliasing). Soft AND speckled is
precisely the reported "sharpen leaves smearing and artifacts".

Antialiased bicubic instead. On a full-band chirp, RMS error against an
ideal brick-wall decimation drops from 0.316 to 0.244 (~23% closer).
Constant input still round-trips exactly, so the DC behaviour both callers
rely on is unchanged.

This is shared with segmented detailing's 'repair' paste, which downscales
its refined crop through the same helper — that path gets the same change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 02:52:06 +03:00
digital-garbageandClaude Opus 5 858e7d9cb6 feat: warn on the main window when Best-FaceID cannot do anything
Best-FaceID reads identity from an identity_pin-tagged guide, and only the AUTO
continuity builder ever applies that tag (timeline._identity_pin_guide). Every
way of losing it is silent: the run succeeds and simply carries no identity.

Three such states, all verified against _run_sampler_inputs (tagged guides in
the emitted funpack_scene_guides payload: 2 when healthy, 0 in each bad case):
  - no identity pin set at all
  - custom guide stack on -- manual guides REPLACE auto guides (`manual or auto`),
    and manual GuideEntry has no identity_pin field, so the pin is dropped
  - auto continuity off -- same outcome by the other route

Shown on the main window of both frontends, not inside the settings pane that
turns it on: you would only see it there if you already went looking.
  - shared predicate: PipelineCaps.identityTransferIssue -> {short, detail}
  - Editor:   amber chip beside Generate in the timeline header, click opens
              Engine settings; full text in the tooltip
  - Easy Gen: full-width amber strip between the preview and the Generate row

fpActionbar had to grow the identity fields -- it hashed only pid/sel/gen, so
setting the pin would have left a stale warning sitting next to Generate.

Verified live in both UIs across all five transitions (no pin -> pin -> stack on
-> stack off -> auto off -> feature off); appears and clears correctly in each.
Silent when the Chain Sampler is not in the pipeline, where the knob is inert.

Tests: 22 failed / 465 passed, unchanged from baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 02:01:09 +03:00
digital-garbageandClaude Opus 5 ffe920474c feat: warn when a scene's source mode wants an anchor but none is picked
Restores source_needs_anchor_media (removed as unreferenced in 0b68af5) and
wires it up, which is what it always lacked.

The existing check, _missing_scene_anchor_media, catches a media_ref that IS
set but has fallen out of the media bin. Nothing caught the ref never being
set: scene_anchor_media_refs guards on `if ref and stype in (...)`, so such a
scene is skipped silently and renders with no anchor at all, quietly ignoring
its own Source setting.

Warning, not a block -- the run is valid, it just may not be the shot the user
set up. Surfaces in two places:
  - backend: report["unsatisfied"] on the generate response
  - editor:  an insp-hint warn under the anchor picker, where Source is chosen

Frontend mirrors the predicate in pipeline_caps.js; the two must stay in step.
Also drops a comment left stranded by 0b68af5's constant removal.

Verified live in the Movie Editor (ComfyUI on :8188): warning appears for
image/mixed with no ref, disappears once an anchor is picked, and never shows
for carry. Silent without Chain Sampler, where an anchorless image scene
degrades to t2v by design.

Tests: 22 failed / 465 passed (baseline 22/460 -- +5 passing, 0 new failures).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:40:36 +03:00
digital-garbageandClaude Opus 5 eddeccaf68 fix: restore the GuideEntry dataclass, repairing the i2v guide stack
7106db0 added scene_anchor_media_refs by writing it over the "@dataclass /
class GuideEntry:" header lines. The class body survived as unreachable code
after that function's return, so GuideEntry was referenced in three places but
never defined.

Effect: every code path that switches the per-scene i2v guide stack on raised
NameError -- build_scene_guides_payload and continuity_media_refs both died as
soon as guide_settings.stack_enabled was true. It went unnoticed for seven
weeks because the stack defaults off and no test covered it.

Fix moves the stranded body back out of scene_anchor_media_refs and restores
the two lost header lines. The body is byte-identical to b9f6e74; only its
position and header changed. scene_anchor_media_refs is untouched.

Adds two regression tests, both verified to fail against the unfixed file.
Tests: 22 failed / 462 passed (baseline 22/460 -- +2 passing, 0 new failures).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:31:50 +03:00
digital-garbageandClaude Opus 5 8d4ed22853 maint: remove template helpers orphaned by the payload removal
json_safe and maybe_store_string were reachable only through
collect_template_payload (removed in 055b256). Caught by re-running the
orphan scan after that commit rather than assuming the first pass was final.

Checked: 1 occurrence each repo-wide (grep -a), 0 refs in ComfyUI core/FunUI.
Tests: 22 failed / 460 passed, identical to baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:17:36 +03:00
digital-garbageandClaude Opus 5 51093cde70 maint: remove unused leading-trigger regex cache
_leading_trigger_re plus its _TRIGGER_RE / _TRIGGER_RE_BUILT module globals.
The globals were touched only inside the function itself, so the whole
memoisation block was self-contained and unreachable.

Checked: 1 occurrence repo-wide (grep -a), not a route handler, not in
NODE_CLASS_MAPPINGS, not an INPUT_TYPES widget, 0 refs in ComfyUI core/FunUI.
Tests: 22 failed / 460 passed, identical to baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:17:02 +03:00
digital-garbageandClaude Opus 5 b68a83d6d9 maint: remove unreferenced movie_editor backend helpers
source_needs_anchor_media, is_chain_only_source_type (+ its now-orphaned
_CHAIN_ONLY_SOURCES constant), _job_snapshot, default_slot_extras,
_ui_output_links.

Kept deliberately: DEFAULT_WIRES_BY_ROLE and DEFAULT_INPUT_SOURCES_BY_ROLE
survive default_slot_extras — both are still exported via the slot-defaults
payload at pipeline_wiring.py:235-236.

Checked: 1 occurrence each repo-wide (grep -a), not route handlers, not in
NODE_CLASS_MAPPINGS, not INPUT_TYPES widgets, 0 refs in ComfyUI core/FunUI.
Tests: 22 failed / 460 passed, identical to baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:15:55 +03:00
digital-garbageandClaude Opus 5 7810ae7a5f maint: remove unused template payload/summary helpers
collect_template_payload and template_field_summary. Both from the original
template-manager commit; the surviving /funpack/templates routes build their
responses directly and never call either.

Checked: 1 occurrence each repo-wide (grep -a), not route handlers, not in
NODE_CLASS_MAPPINGS, not INPUT_TYPES widgets, 0 refs in ComfyUI core/FunUI.
Tests: 22 failed / 460 passed, identical to baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:15:03 +03:00
digital-garbageandClaude Opus 5 233333b97e maint: remove orphaned {a|b} wildcard engine
resolve_wildcards + its WILDCARD_RE pattern. Both arrived with the Creative
Template node (514e29d / 5f1c2ad); that node is no longer registered, and the
{a|b} syntax is handled nowhere else in Python or JS, so the resolver has had
no caller since the node was dropped.

User impact checked: 0 occurrences of {a|b} in the user's shortcuts file, so
no live prompt depended on this expanding.

Checked: 1 occurrence each repo-wide (grep -a), not a route handler, not in
NODE_CLASS_MAPPINGS, not an INPUT_TYPES widget, 0 refs in ComfyUI core/FunUI.
Tests: 22 failed / 460 passed, identical to baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:14:41 +03:00
digital-garbageandClaude Opus 5 2ace4dc4d7 maint: remove dead refiner-state template cluster
conditioning_from_refiner + its four exclusive helpers (load_refiner_state,
choose_prompt_history, best_history_conditioning, prompt_key_for_refiner).
Closed cluster: the only references to any of them were from each other.

Checked: 1 occurrence each repo-wide (grep -a, incl. NUL-byte frontend files),
not a route handler, not in NODE_CLASS_MAPPINGS, not an INPUT_TYPES widget,
no getattr/setattr or monkey-patch use, 0 refs in ComfyUI core and FunUI.
Tests: 22 failed / 460 passed, identical to baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 18:14:07 +03:00
digital-garbageandClaude Opus 5 3978e4c975 Merge dev into main - Release 3.4.1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v3.4.1
2026-07-31 18:17:16 +03:00
digital-garbageandClaude Opus 5 44a9373524 Release 3.4.1: CHANGELOG, README, version bump
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 18:17:06 +03:00
digital-garbageandClaude Opus 5 2011b18b95 fix: don't reject projects saved with the old context_window_schedule names
Renaming the combo's choices to core's spellings broke queueing for anyone who
had one of the old ones saved: ComfyUI validates combo VALUES at queue time,
before the node ever runs, so the alias map added with the rename never got a
chance. The three legacy names are back in the list (they resolve through the
alias map at runtime), and the Editor now displays a stored legacy value as the
name it maps to and writes that back, so the dropdown can't show one setting
while another runs.

And that rejection was unreadable. ComfyUI's top-level `error` for a validation
failure is a fixed blob ("Prompt outputs failed validation", details ""); the
part naming the node, the widget and the bad value is in `node_errors`, which
`error or node_errors` never reached because the blob is always truthy. Every
rejected prompt therefore reported the same nothing. Both are formatted now:

  Prompt outputs failed validation; FunPackLTXAVSceneChainSampler: Value not in
  list — context_window_schedule: 'static_standard' not in [...]

Also: the progress poll no longer imports anything. current_progress() reads
run_phase's label straight off `sys`. It fires every 700ms on ComfyUI's event
loop WHILE the worker thread samples, and that is not a path to do import work
on — the direct read is both cheaper and the correct one, since run_phase keeps
its state there anyway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 18:10:10 +03:00
digital-garbageandClaude Opus 5 6457512838 second_pass_op: show the upsampler it depends on, and say why it was skipped
Answering "how does sharpen operate — I never saw the upsampler download?":
it uses the same file segmented detailing does, resolved the same way (any
model already in models/latent_upscale_models wins; the ~1 GB official file is
downloaded only when that folder is empty). So no download is expected on a
machine that already has one — but nothing in the Editor said so.

The upsampler picker rendered only under Experimental, and only while segmented
detailing was on, so choosing 'sharpen' alone gave no model control and no hint
that a 1 GB file was involved. It now also renders in the Second pass group
whenever an operation is selected, and the knob's hint states the dependency
and that the pass still runs (operation skipped, reason in the scene report)
when the file can't be obtained.

That reason is also worth something now: a failure said only "no latent
upsampler could be loaded", which is the same message for a missing
huggingface_hub, a failed download and an unreadable file. The real cause is
carried into the scene report and printed to the console once — previously only
the successful download printed, so a silent skip read as the op having run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 18:00:12 +03:00
digital-garbageandClaude Opus 5 7681c599a0 progress: say which scene and which pass is running; fix context_window_schedule
Three things, all about a run telling you what it is doing.

1. The second pass is now visible. ComfyUI's progress channel carries only
   (value, max), so a chain sampling some scenes twice was one anonymous bar.
   The sampler publishes a live label ("scene 2/3 · pass 2 of 2") through the
   new run_phase module; /api/progress hands it to the editor, which appends it
   to the readout, and pass 2 also announces itself on the console the moment it
   starts rather than only in the after-the-fact run report.

   run_phase keeps its state on `sys`, not in module globals: the writer reaches
   it package-relative and the reader imports FunPack modules top-level by name,
   so they can hold different module objects and the label would never arrive.

2. The progress bar total now counts both passes. It only ever counted the main
   schedule, so with a second pass on it overflowed during scene 1 and jumped
   backwards at scene 2, whose offset is a multiple of the per-scene stride.

3. context_windows was unusable. The knob offered "uniform_standard" etc.;
   core's names are the other way round ("standard_uniform"), so every schedule
   except "batched" raised ValueError out of the sampler and FAILED THE RENDER.
   Core's names are the choices now, the old spellings are aliased so saved
   projects keep working, and an unknown name is refused with a reason naming
   what core accepts instead of crashing.

   Same call had a second, quieter break: a keyword core's handler doesn't take
   (latent_retain_index_list is absent on some builds, including this Mac's)
   raised TypeError, which was reported as "ComfyUI core too old" — silently
   switching off a feature core fully supports. Extras are dropped now, with a
   note naming the one knob that stops working.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 17:57:21 +03:00
digital-garbageandClaude Opus 5 39533dfde4 anchor_shift: remove the shift, keep only the head cut
The two-pass shift regrew the frames it dropped, and the invented ending
consistently came out with worse or missing movement — not worth paying
steps for. What is left is the part that works: generate the scene exactly
as normal, anchor pinned at full strength the whole way, then cut the
opening off the finished clip. Nothing is regrown, so the scene comes out
that much shorter and the audio is cropped to match.

Six knobs collapse into one INT, cut_opening_frames (0 = off), in the
Editor under Chain Sampler ▸ Timing & Seed rather than Experimental.
Gone with the shift: the mid-schedule split, the rewind re-noise
(_renoise_to_sigma), the tail refill modes, the fresh-audio reset, and the
schedule_context/continue_from_state plumbing that existed only so a split
scene would not re-time the sampler inside each half.

second_pass is untouched — it never used any of that machinery.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 17:36:13 +03:00
digital-garbageandClaude Opus 5 1dd27101aa second_pass: say plainly that the sampler does the re-entry, and what the first sigma costs
User asked why there is an "img2img re-noise" at all — "you just pass the original latent
from 1st pass as latent_image into 2nd pass and that's it, sampler does everything itself."
That is exactly what the code does. Pass 1's latent goes in as latent_image with
continue_from_state off, so _sample_chunk calls prepare_noise and comfy's own noise_scaling
handles the entry; _renoise_to_sigma is never called on this path (it is anchor_shift's
only, for its genuinely mid-trajectory case). Between the passes there are just two things,
one of them usually nothing: the optional op (skipped at "none") and re-attaching the i2v
noise_mask so pass 2 does not re-denoise the anchor.

So no code change — but the docs said "re-noising the finished clip", which named comfy's
internal step as if it were machinery I had added, and that is what prompted the question.
Reworded to say the sampler does it.

The same wording hid the answer to their actual symptom (softness). CONST scaling is
x = s*noise + (1-s)*picture, so the schedule's first sigma is a literal strength: at 0.812
pass 2 starts from 81% fresh noise over 19% of the pass-1 picture with 4 steps to resolve
it, which is a very strong img2img and is expected to read soft. Both tooltips now give the
0.8 / 0.4 / 0.2 scale so the dial is legible instead of implicit.

Suites at baseline: 22 pre-existing failures, 467 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 14:05:49 +03:00
digital-garbageandClaude Opus 5 63f225e231 second_pass: both schedules run in FULL — remove the cut I invented
User, after a live run: "First pass runs its own FULL schedule. Then 2nd pass begins.
If I want to stop 1st pass earlier - I remove sigmas from schedule... 'Continue from'
is ONLY relevant in our shifting technique which is a separate entity."

Correct, and the cut was mine, not theirs. I carried anchor_shift's cut/restart
semantics into a feature that has no use for them. Consequences they hit on the rental:

- Steps. Pass 1 was cut at the second schedule's first sigma, so a 9-step main plus a
  4-step second pass ran 3+4=7, not 13. They noticed the missing steps.
- Distortion. With the cut, the between-pass operation ran on a MID-TRAJECTORY latent,
  so "sharpen" was sharpening the noise still in it. It only makes sense on a finished
  clip.

Now: pass 1 runs the main schedule in full, exactly as written. The operation runs on
that finished clip. Pass 2 runs second_pass_sigmas in full, exactly as written, entering
the way any img2img refine does — comfy's ordinary CONST scaling on a clean latent, so
continue_from_state stays off. Total steps are the two schedules added up. To make pass 1
shorter, shorten the main schedule.

second_pass_sigma and second_pass_restart_sigma are deleted outright: with both schedules
run as written there is nothing to cut and nothing to continue from. The second schedule's
first sigma is now simply the strength dial, the way it is in every img2img refine.
_second_pass_plan is replaced by _second_pass_schedule, which only validates.

Also reported: "I have only sharpening enabled but log tells me upsampling is enabled as
well." The load line printed "[FunPackSceneChain] segmented detailing upsampler: ..."
regardless of which feature pulled it in, and sharpen legitimately loads that model —
so a sharpen-only run announced a feature that was off. Now "latent upsampler loaded".

Kept: the malformed-schedule refusals (ascending, or not ending at 0) — both are silent
in the output rather than loud at runtime, which is how the distorted run read.

tests/test_second_pass.py rewritten around the corrected shape, including that a 9-step
main plus a 4-step second pass is 13. Suites at baseline: 22 pre-existing failures, 467
passing. Verified live that typing a schedule sets second_pass with no cut knob leaking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:55:48 +03:00
digital-garbageandClaude Opus 5 1c6afe2e4d second_pass: in the Editor it is just the schedule — enable and cut derived from it
User: "'2nd pass starts at' and '2nd pass re-enters at' are confusing - just give me
the schedule to operate and that's it." They are right, and it was two controls saying
the same thing in different words: with an explicit pass-2 schedule, where pass 1 stops
and where pass 2 re-enters are both simply that schedule's first sigma.

The Editor now has ONE control for this: the Second pass schedule field. Typing a
schedule turns the second pass on and sets the cut to its first sigma; clearing it turns
the pass off (both sampler inputs are unset, not left behind). Cut == entry, so the
hand-over is exact and nothing is re-noised — the case that needed the re-entry sigma
does not arise. Only the optional between-pass operation is left beside it.

Frontend-only derivation (syncSecondPassFromSchedule), so the node keeps its full
range: second_pass / second_pass_sigma / second_pass_restart_sigma are still there for
graph users with no schedule wired, where they are the only way to express a rewind.
A single number is not treated as a schedule.

Also cleaned up after my own UI verification: earlier passes had left anchor_shift=true
in the README demo project's sampler_inputs. Unset and saved — that was my residue, not
a setting the user picked.

Verified live: no "Enable second pass" / "starts at" / "re-enters at" controls remain in
the Editor; typing 0.812, 0.6, 0.35, 0.15, 0.0 yields second_pass=true and
second_pass_sigma=0.812, and clearing the field removes both. Suites at baseline: 22
pre-existing failures, 469 passing across both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:45:13 +03:00
digital-garbageandClaude Opus 5 8d0e7ffaa3 second_pass: expose the pass-2 schedule in the Editor, move it out of Experimental
Three corrections from the user, all right.

1. The pass-2 schedule needed no graph-topology change at all — I was wrong to leave
   it out. Studio has ALWAYS emitted a second schedule: _build_samplers builds both
   samplers_cfg["high"] and ["low"], and low_pass_sigmas is output 7. The Editor simply
   never wired it (sampler_panel.js even says so: "outputs 6+7 are not connected").
   So this is one CORE_LINKS entry, "second_pass_sigmas": ("studio", 7), plus surfacing
   the low pass's existing `sigmas` text field. No new node, no new output.

   Only the SIGMAS is taken, not the low-pass SAMPLER: pass 2 reuses the sampler
   configured above it. So the panel deliberately shows just that one field rather than
   the whole low-pass sampler section — rendering its algorithm settings would offer
   knobs that quietly do nothing, which is the thing the editor rules forbid.

2. No re-entry sigma in the Editor. With an explicit pass-2 schedule the first sigma of
   that schedule IS where pass 2 re-enters, so second_pass_restart_sigma was a redundant
   second way to say the same thing. Dropped from the Editor; still on the node for
   graph users with no second schedule wired, where it is the only way to say it.

3. Not experimental. At its defaults the split is behaviour-neutral by construction —
   pass 2 resumes from exactly the state pass 1 handed over, same step count, same
   output — so calling it experimental overstated it. Moved from Chain Sampler >
   Experimental to FunPack Studio > Sampler algorithm, next to the schedule field it
   depends on, and the EXPERIMENTAL / UNVALIDATED LIVE framing is gone from both
   tooltips.

Verified live in the running app: the Sampler algorithm pane shows the Second pass
group and the schedule field, the re-entry knob is gone, and typing a schedule persists
to studio_settings.samplers.low.sigmas (confirmed the high pass is untouched). Suites at
baseline: 22 pre-existing failures / 306 passing, movie_editor 163 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:42:37 +03:00
digital-garbageandClaude Opus 5 16045e086c second_pass: optional latent operations between the passes (none by default)
Per the user's reminder that these are OPTIONS, not defaults: second_pass_op is a
combo defaulting to "none", and nothing runs unless it is picked. The expensive one
is therefore not a tax on anything — it costs what it costs only when chosen.

- "sharpen": one forward of Lightricks' trained 2x LatentUpsampler, resampled straight
  back to the original size. No video-model calls, so a fraction of a step; pass 2 then
  re-denoises the sharpened latent, which is what makes it stick. Adds detail consistent
  with what is there and CANNOT fix wrong structure — the same limit segmented
  detailing's sharpen mode documents.
- "upscale_2x": same upsampler, result KEPT at 2x, so pass 2 runs at 4x the pixels and
  the scene decodes at double resolution. 3-5x the second half's cost.

Reuses detailing.py's _run_upsampler / _downscale_to and its lazily-loaded upsampler
(same model, same detail_upsampler knob, "auto" downloads the official file) rather
than adding a second copy of any of it.

Ordering that matters: the op runs BEFORE the rewind re-noise, because sharpening a
latent that has just had noise added back sharpens the noise.

Two failure modes handled rather than left implicit:
- upscale_2x changes the latent resolution, so the chunk's i2v anchor and mask are the
  wrong size. Rescaling them would be inventing an anchor, so the pin is dropped and
  the scene report SAYS the pass runs unpinned and may drift from the reference image.
- A missing or throwing upsampler degrades to "second pass, no operation" with the
  reason, instead of killing a whole montage over an optional sharpen.

Found while writing the tests: an unrecognised op name was validated only AFTER the
upsampler forward had already run, so a typo paid full price and then reported the
wrong reason. Name is checked first now.

Editor + Easy Gen get the combo under Engine > Experimental; verified live that it
renders with none/sharpen/upscale_2x and defaults to none. Suite at baseline: 22
pre-existing failures, 306 passing.

Not built: downscale-then-rescale (DeepShrink-shaped). It is not a between-passes op —
pass 1 itself has to run at reduced resolution, which means downscaling the chunk, its
mask and the anchor before pass 1, so it needs its own insertion point rather than
this hook.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:35:29 +03:00
digital-garbageandClaude Opus 5 b7fa3756f1 Chain Sampler: general "Enable second pass", with an optional separate schedule
anchor_shift built the two-pass machinery — stop the schedule, hand the state over,
re-enter exactly (_sample_chunk continue_from_state, _renoise_to_sigma, _schedule_view).
The user's point: that is generally useful, not anchor-specific. This exposes it on its
own, and keeps the two features independent as they asked: reaching the second_pass
branch means anchor_shift did not act on this scene, so they can never nest.

  second_pass                 stop partway and finish in a second pass
  second_pass_sigma           where pass 1 stops (walked against the real schedule)
  second_pass_restart_sigma   where pass 2 re-enters; 0 = carry straight on
  second_pass_sigmas          OPTIONAL SIGMAS socket: pass 2 runs THAT schedule instead

At restart 0 with no second schedule the split is behaviour-neutral by construction —
pass 2 resumes from the exact handed-over state, nothing added, and 5+4 steps on a
9-step schedule is still 9. It earns its keep when the second half is made different:
re-entering higher (the model reworks what it committed to) or finishing on a different
schedule entirely, so the tail can have its own step count, spacing or shift.

Two things a split gets silently wrong if you let it, both handled:
- The i2v anchor. _sample_chunk drops noise_mask from what it returns, so pass 2 would
  run UNPINNED and re-denoise the reference frame. anchor_shift wants exactly that;
  a plain second pass must not. _restore_pinned_prefix puts the mask back AND restores
  the pinned frames from the original chunk, since a rewind re-noise adds noise
  everywhere including frames that are meant to be frozen.
- The sampler's own phases. Pass 2's schedule_context is the alternate schedule at
  offset 0 when one is connected, or the main schedule at the slice offset when not —
  so quality steps / final-correction window / AB2 ramp are measured against whichever
  schedule pass 2 is actually running.

Every refusal names its fix (cut outside the schedule, entry below the cut, a
single-sigma SIGMAS input that is not a schedule) and lands in the scene report as
second_pass(SKIPPED: ...) — never a silent no-op.

Editor + Easy Gen get the three widgets under Engine > Experimental. The SIGMAS socket
is node-graph only for now: the Editor's fixed graph takes sigmas from ("studio", 5) and
has no second scheduler node, so exposing it there is a graph-topology change I have not
made unilaterally.

Latent/pixel-space pass-2 operations (sharpen, downscale->rescale, upscale) are next,
as opt-in options with nothing on by default.

New tests/test_second_pass.py: 10 tests over the split, the separate schedule, the
refusals and the pin. Suite at baseline: 22 pre-existing failures, 299 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:31:07 +03:00
digital-garbageandClaude Opus 5 71e001afff anchor_shift: sigma 0 = no second pass, just cut the head off the finished clip
Reported by the user: setting the shift sigma to 1 or 0 with restart 0 got them
nowhere, and what they wanted was "generate me a full video and don't make the 2nd
pass, just cut off the beginning".

That was a gap, not a crash. Both extremes fell through to the splitter, which
correctly refused (a cut at the first sigma leaves pass 1 no step; a cut at the last
leaves pass 2 none) and reported a skip — so anchor_shift silently did nothing and
the skip reason did not name the thing they were actually after.

anchor_shift_sigma=0 now means it: a cut at sigma 0 IS the end of the schedule, so
pass 1 is the entire run and there is nothing left to denoise. The scene samples
exactly as if anchor_shift were off — anchor pinned at full strength the whole way,
which is the point — and the slide is applied to the FINISHED latent. Zero extra
sampling: the cheapest form of this feature by a wide margin, and with tail=crop it
is plainly "generate the video, then delete the opening". Widget min lowered 0.05 -> 0.

Two things are refused rather than half-worked, each naming its fix:
- tail=extend_last / empty, because with no second pass a refilled tail is never
  denoised — extend_last would freeze the last frame for anchor_shift_frames and
  empty would leave an empty latent on screen. crop and wrap are pure rearrangements
  of finished content, so both are allowed.
- a non-zero restart sigma, which has nothing to restart.
And anchor_shift_fresh_audio is forced off in this mode (reported, not silent):
resetting the audio with nothing left to regenerate it would just produce a silent
clip.

The old "schedule never reaches ..." skip reason now points at sigma=0 as the way to
run the whole schedule and only cut the head, so the next person hitting the extreme
is told what to set.

Suite at baseline: 22 pre-existing failures, 289 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:25:56 +03:00
digital-garbageandClaude Opus 5 9f7e8bb3c6 anchor_shift: add tail=crop — no regrown tail, the clip just ends earlier
User's idea, as an OPTION (default stays extend_last). The three existing tail modes
all refill the freed end so the clip keeps its length, which means pass 2 has to invent
an ending and blend a joint — the source of the "thin ending" and of most of the reason
to reach for a rewind restart. 'crop' regrows nothing: the scene ends anchor_shift_frames
earlier and every surviving frame was generated as part of one continuous shot.

Checked the model side before assuming this was free, since a bad latent size would
mismatch rather than degrade:
- LTXAV's video patchifier is SymmetricPatchifier(1) -> temporal patch size 1, so there
  is no divisibility rule on video latent frames.
- The streams reach the transformer as independent tensors (separate_audio_and_video_
  latents just unbinds the list); no video:audio ratio is enforced there.
- comfy re-packs per sample_custom call and re-derives latent_shapes, and the denoise
  masks off them (comfy/samplers.py outer_sample), so nothing is baked in from
  scene-build time and a shorter latent needs no conditioning rebuild.
- VAE decode is (f-1)*time_scale+1 for any f >= 1.
So no size is rejected. What IS real is proportion: audio timings come from the audio
stream's own index and video's from its own RoPE, so cropping the two by different
amounts of TIME desyncs the clip silently — no error, just drifting sound. Each extra
stream's KEPT length is therefore derived from the new video length directly rather than
by subtracting a separately-rounded drop, so rounding cannot leave them describing clips
of different durations. fresh_audio zeroes after the crop, at the new length.

Downstream needed nothing: _prepend_soft_continuation already clamps to the previous
scene's real length and _derived_overlap clamps to the stream, so a shorter scene flows
through the chain as-is.

The scene report now states the cropped length in latent frames and in real frames,
because a scene coming out shorter than the length that was set must never be something
the user has to infer from the output.

Tests: crop shortens instead of refilling and the surviving frames are the untouched
originals; audio/video stay in proportion (including four rates that do not divide
evenly); fresh_audio resets at the cropped length; the other three modes still keep the
clip length. Suite at baseline: 22 pre-existing failures, 288 passing (+5).

Verified live in the running app: the Engine ▸ Experimental dropdown offers
extend_last/wrap/empty/crop, default unchanged, hint present.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:15:01 +03:00
digital-garbageandClaude Opus 5 4c2a6b6b54 anchor_shift: the sampler must see the real schedule, not its own half of it
Raised by the user: "it should read the actual sampler schedule." They were right,
and this is a bigger deal than anchor_shift.

Both FunPack samplers derive their phase boundaries from len(sigmas) — the tensor
they are handed. anchor_shift runs a scene as two sample_custom calls over slices of
one schedule, so each half re-derived every phase INSIDE the slice:

  _sample_const_rf_full (Hybrid Euler 2S, and the LTXAV path of it)
    - quality phase: late_start = pct of the SLICE. On the user's 9-step schedule at
      high_quality_pct 0.35 the quality phase starts at sigma 0.603; split at 0.482 it
      starts at 0.812 in pass 1 and 0.482 in pass 2.
    - motion-pulse anchors keyed by step index within the slice
    - eta decay + velocity-bias ratio measured against the slice's first sigma
    - the euler->Heun correction ramp restarted per call
  sample_funpack_distilled_flow
    - final-correction window: the last N steps OF THE SLICE, so pass 1 ran a "final"
      Heun correction in the middle of the trajectory
    - AB2 ramp w = i/(total-1) restarted from 0 in pass 2
    - velocity-bias sigma ratio against the slice's first sigma

So turning anchor_shift on silently re-timed the sampler's own machinery in both
halves. Present since 015f274 and a far better explanation for a visible joint or a
thin ending than anything in the shift itself.

Fix: _schedule_view(sigmas, schedule_context) -> (real schedule, step offset). The
chain sampler passes {"sigmas": <the scene's schedule>, "offset": <slice start>} via
extra_options (the same chunk-scoped mechanism the alg_guide_* options already use,
always written even as None so it cannot leak into a later chunk). Both samplers take
the kwarg; every phase-relative quantity now reads the schedule and every step index
is step_abs = i + offset. No context = a normal single call = sigmas IS the schedule,
offset 0, byte-identical behaviour.

_anchor_shift_split_sigmas also returns pass 2's index into the schedule, and its
docstring now states the boundary rule the way the user framed it: walk the schedule,
the last pre-shift step is the one whose NEXT sigma has crossed the threshold. On
1.000 0.955 0.893 0.812 0.715 0.603 0.482 0.241 0.121 0.0 with shift 0.482 and restart
0.812 that gives pass 1 = 1.000..0.482 and pass 2 = 0.812..0.0, which is now asserted
directly as a test on that schedule.

Not changed, deliberately: sample_funpack_hybrid_euler_2s's own non-CONST loop (LTXAV
always takes the RF port, so no anchor_shift run reaches it) and segmented detailing's
stage-2 tail (a separate refine pass over a finished latent with its own schedule by
design, not a slice of one).

Suite at baseline: 22 pre-existing failures, 283 passing (+4).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 12:59:39 +03:00