Custom transition triggers in Studio can now have a visual_effect assigned
(fade_to_black, crossfade, blur_out_in). When triggered during scene splitting,
the effect fires at that scene boundary: the sampler decodes the latent window
around the boundary, applies the pixel-space effect, and re-encodes in-place.
No new nodes needed — VAE is already connected to the scene chain sampler.
New transition_duration input controls how many pixel frames are affected.
New scene_boundaries output provides the boundary list for debugging.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The i2v hidden state injection only produces meaningful results alongside
carry_i2v_guides in the Scene Chain Sampler. Without it the injection
has no reliable reference to anchor from (proven through testing).
New Studio Refiner setting: "Reference injection" toggle (default off).
Hint explains it should be enabled together with carry_i2v_guides.
Passing reference_latent=None to build_enhancements when disabled so
the entire injection path is skipped cleanly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When an i2v latent is connected (noise_mask with protected first frame),
FunPack now automatically extracts hidden state maps from the reference
image at blocks 14, 19, 21, 26, 31 via a single forward pass at sigma=0.03.
These maps are injected during generation to anchor character appearance
without requiring any prior rated runs.
Priority: reference maps from i2v > blessed maps from prior rated run.
T2v latents (no noise_mask or mask=1 throughout) are detected and skipped
cleanly - no injection happens and generation is unaffected.
Reference map injection uses fixed moderate strength (anchor 22%, identity
10%) since there is no reward signal to scale against. Blessed maps continue
to scale with reward as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New placement option 'silent': the transition phrase marks the scene
boundary but is stripped from the output entirely. Neither the previous
nor the next segment contains it. Available globally and as a per-entry
override on custom transitions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Trailing \b after a non-word character (like '.') never matches since
\b requires a word char on one side. Custom triggers ending with
punctuation now use (?=\s|$) as the end boundary instead, so
"Scene cut." correctly splits prompts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Global setting in Studio Refiner tab: "Transition placement" (start/end).
start = transition opens the new segment (existing default behavior).
end = transition closes the previous segment instead.
- Per-entry placement override in Transitions tab (global/start/end).
Custom transitions can pin their placement regardless of the global setting.
- transition items gain a placement field (global/start/end, defaults to global).
- load_custom_transition_triggers returns {trigger_lower: placement|None} so
the split function can do per-trigger lookups efficiently.
- refine_v2 and Studio.run() wired with split_transition_placement parameter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The [a-z][\w-]* alternative matched any word after "scene", turning
"scene proceeds", "scene features", "scene shows" etc. into false
transition split points. Removed the over-broad alternative - the
pattern now only matches numbered scenes (scene 1, scene -2) and
"scene minus <label>" forms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single words like "next", "suddenly", "later", "finally" etc. fire on
normal prompt text and cause spurious scene splits. Removed the entire
single-word temporal markers block. Shortest triggers are now two words
minimum. Also removes "and suddenly" and "and next" which were similarly
too vague.
Stray article-comma cleanup ("the, next" -> "the next") also applied when
building transition segments to handle commas left by the split.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the stacking fix merges a pure-transition segment into the next one,
it now uses a space instead of ", " if the segment ends with an article
(a/an/the). This prevents "the scene then cuts to the, next view" when
"next" is also a transition word and the segments get merged mid-phrase.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If a prompt ends with a transition phrase plus nothing (or just a lone
article like "the"), the last segment is now dropped rather than kept
as a near-empty scene. Covers cases like "...K/DA Kai'Sa, the scene
then cuts to the" where the user did not complete the transition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Shortcuts: allow empty string as a valid replacement to remove matched phrases
from prompts; post-pass cleans up orphaned commas/spaces left by removals
- Shortcuts: add cross-shortcut longer-phrase-wins test confirming ordered
alternation handles multiple shortcuts with overlapping triggers correctly
- Transitions: new user-editable transition word list stored in transitions.json;
custom triggers extend the built-in split pattern at runtime, keyed by name
- Transitions: Studio JS gets a dedicated Transitions tab (name, trigger, enabled)
with Add/Refresh/Import/Export toolbar
- Transitions: four API routes mirroring shortcuts (GET, export, save/delete, import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
With split_by_transitions outputting a single conditioning, the contrastive
conditioning math has nothing to act on. Removed from Refiner V2 inputs,
refine_v2 signature, Studio JSON settings, Studio popup UI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multi-entry conditioning without a working per-window routing mechanism
accumulates all scene conditionings onto every frame, which confuses the
model. The model already places scene cuts naturally from transition words
in the full prompt - splitting the conditioning breaks this.
split_by_transitions now:
- Detects scene segments and shows them in encoded_prompts (transparency)
- Outputs a SINGLE full-prompt conditioning (model uses natural cuts)
- Status shows "Transition split: N scenes detected"
- encoded_prompts shows "Detected scenes" for use with FunPack Scene Noise
FunPackSceneCutWindows still works independently: it reads num_scenes from
conditioning entries, so with single conditioning it stays inactive (safe).
FunPackSceneNoise still seeds noise per temporal region from the seed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Anchor fix: use the ENTIRE first segment (before the first transition word)
as the character anchor for all windows. Previous code split at the first
comma, which broke prompts like "In this anime video, Character is a..."
by using "In this anime video" as the anchor and losing the character desc
from all subsequent windows.
i2v fix: in FunPackSceneCutWindows, scene N>0 tokens now have access to
scene 0's tokens as key/value (in addition to their own scene). The i2v
guide frame lives in the early temporal positions (scene 0 region), so
blocking it from later scenes caused the reference image to be ignored.
Scenes 1 and 2+ remain isolated from each other.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New phrases: 'the scene begins with', 'after a brief scene cut',
'as the scene transitions', 'in the next scene', 'later another scene
shows', 'another scene shows', 'the video concludes'.
Stacking fix: when two transition phrases are adjacent ('after a brief
scene cut, in the next scene...'), the pure-transition intermediate
segment is prepended to the following segment rather than left as an
empty conditioning fragment. Fixed direction (was appending to previous,
now prepends to next).
Diagnostic log: context_transition.py now logs cond group sizes,
num_windows, detected dim, and x_in shape at INFO level on every
should_use_context call to confirm routing is working.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
conds = [uncond_list, cond_list] - conds[0] is the negative conditioning
which always has exactly 1 entry. Reading len(conds[0]) always gave
num_windows=1, so the schedule produced a single window covering all
frames, and split_conds_to_windows picked the middle scene's conditioning
for the entire video (center_ratio≈0.5 → region 1 of 3).
Fix: max(len(c) for c in conds) correctly reads the positive conditioning
(3 entries for 3 scenes) and creates 3 isolated windows.
Also added 'the final scene' and 'the last scene' to transition phrases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For each context window, apply:
window_i = general + contrast × (scene_i − general)
where general = the full-prompt conditioning (refined, with memory
applied). The character description cancels in the delta because it
appears in both scene_i and general; the push focuses on what is
unique to each scene (action, environment).
- contrast=1.0 → plain scene encoding (original behaviour)
- contrast=1.5 → default, 50% extra push toward scene content
- contrast=2.0 → classic contrastive (2×scene − general)
New parameter: transition_contrast (FLOAT, default 1.5, range 1.0-4.0)
on Refiner V2 and in Studio popup Refiner tab.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Transition phrase list: add 'the scene then cuts to', 'the scene cuts to',
'the scene transitions to', 'the scene fades to', 'the scene dissolves to',
and short forms ('the scene then cuts', 'the scene cuts').
encoded_prompts output: when split_by_transitions is active, append a
'Per-window prompts' section showing the exact text encoded for each
context window. Example:
Window 1: A woman in red, walking in a park
Window 2: A woman in red, then sitting on a bench
Window 3: A woman in red, suddenly she runs away
Also updated _v2_encode_per_window_conditionings to return (conditionings,
window_texts) tuple instead of conditionings only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Transition words ('then', 'suddenly', 'cut to', etc.) are now kept at
the START of the new scene's conditioning text rather than discarded.
They are valuable tokens that signal a scene change to the model.
Example: 'A woman in red, walking, then sitting on a bench, suddenly runs'
Window 0: 'A woman in red, walking'
Window 1: 'A woman in red, then sitting on a bench'
Window 2: 'A woman in red, suddenly runs'
Also added ordinal scene markers to the detection list:
'the first scene' ... 'the tenth scene'
'scene one' ... 'scene ten'
This lets users explicitly label scenes in the prompt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All configuration is now auto-computed at sampling time - no user-facing
settings except the optional total_frames INT. Window count is derived
from the number of conditioning entries Refiner/Studio outputs. Window
size is total_frames (or actual latent size) / num_windows. Boundaries
are placed at equal intervals. split_conds_to_windows always on.
Studio: move split_by_transitions from direct node input to the Refiner
tab in the popup editor. Removed direct input, added toggle + hint text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Studio now exposes split_by_transitions as an optional BOOLEAN
input on the node face, giving direct control without editing
the raw settings JSON. The direct input takes precedence over
the popup JSON setting when connected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Refiner V2 and Studio: new split_by_transitions option detects
transition words in the final prompt (then, suddenly, cut to, etc.),
extracts the character description (text before the first comma),
and encodes one conditioning per scene window with the character
description prepended to each segment. Returns a multi-entry
conditioning list that routes per-window via split_conds_to_windows.
Context Transition Windows: new total_frames optional input.
When > 0 and split_conds_to_windows is enabled, context_length
is auto-computed at sampling time as total_frames / num_cond_entries
so windows size themselves to match Refiner's segment count.
Also fixes pre-existing bugs: early error return in refine_v2 was
missing video_latent (7th return value), and test unpacking was stale.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three bugs causing i2v latent to be lost:
1. eff_key and prev_reward were defined inside 'if patched_model is not
None' but used outside it. NameError was silently caught but could
cause unexpected behavior. Moved both definitions before the block.
2. load_and_apply_creativity_mask returned bare {'samples': ...} dropping
all other keys from the original latent (noise_mask, type, batch_index,
etc.). Now copies the full original dict and only replaces 'samples'.
3. Modified samples were left on CPU (detached earlier) while the original
latent samples may be on CUDA. Now moves modified tensor to match the
original device and dtype before returning.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refine_v2: initialise video_latent = latent instead of None so the connected
input passes through even when no key is set or mask computation is skipped.
Mask result only overwrites when it returns something non-None.
load_and_apply_creativity_mask: track explicit_latent separately from the
disk-loaded source. All early-exit and error paths return explicit_latent
(the connected input unchanged) instead of None, preserving the i2v or
previous-generation latent through the node when mask has nothing to add.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sits between Perfect (1.0) and Missing details (0.35). For when the output
looks great and you'd keep it, but it took creative liberty with the prompt
(man grabbed woman's hand instead of the other way - still a hand grab, still
loved, but not word-for-word correct).
Behaviour vs Perfect:
- axis_feedback: same - no missing/wrong axes, repair cleared
- good_streak: increments (same as Perfect)
- liked_dir: updated (quality direction was good)
- liked_conditioning average: NOT updated (intent drift - only Perfect locks in)
- phrase memory: delta 0.35x vs Perfect's 0.55x; category weights +0.08 vs +0.13
- preferred context: base_delta 0.65 vs 1.0
- phrase forgiven_count: -0.10 vs -0.18
- LoRA weights: quality/style +0.44x, action/character +0.24x (vs Perfect's 0.55x flat)
- Attention anchors: blessed (composition was good regardless of prompt fidelity)
- Advisor: not treated as perfect execution - repair signal still possible
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
clear_refinement_data() removes temp attention maps, blessed attention
maps, and creativity latent for the refinement key. Called from
_v2_load_state() at the start of a reset before fresh state is written.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Action LoRAs can carry micro-movement details tied to the action itself
(subtle motion blur, fluid dynamics, small camera adjustments). Previously
they only responded to the action axis, so 'Missing details' ratings never
adjusted their weight. Now mirrors character type which already had both.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed from LORA_BLOCK_TYPE_PROFILES, LORA_TYPE_BLOCK_ZONES,
LORA_REFINER_TYPE_PROFILES, ACTION_LORA_TYPES, the validation
special-case, and the Studio JS LORA_TYPES dropdown.
normalize_lora_type() keeps the concept->action mapping for silent
backwards compatibility with any existing saved stacks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _is_ltx_model() checks inner model class name, unet_config image_model
key (ltxv/ltxav), and model_type string. build_enhancements() returns
the model unchanged with a console note when a non-LTX model is detected
(e.g. WAN2.2) - generation continues normally, only the LTX patches skip.
- Update model input tooltip in FunPackVideoRefinerV2 to state LTX-2/LTXAV
family requirement and pass-through behavior for other models.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ltx_enhancements.py with four automatic generation improvements
driven by the refiner rating system:
Technique 2 - Per-block attention temperature: sharper (colder) attention
on semantic anchor blocks 14/19 when quality/concept is missing; softer
on early blocks when details are missing. Capped at 0.72-1.22x range.
Technique 3 - Temporal RoPE style: frame_rate manipulation via
model_function_wrapper. Selector exposed in FunPackVideoRefinerV2 node
(optional input) and in Studio popup Refiner tab.
natural=no change, accelerate=+35% fps, decelerate=-28% fps,
loop/freeze=experimental coordinate tricks.
Technique 4 - Denoise creativity mask: spatial noise weighting derived
from latent variance; high-variance regions get more denoising freedom
scaled by reward. Built but applied by downstream latent consumers.
Technique 5 - Attention anchor transfer: hidden states at anchor blocks
14 and 19 are captured via patches_replace["dit"] on every run and saved
to refinements/attn_maps/temp_{key}.pt. When the user rates a run
Perfect, bless_attention_maps() promotes the temp file to a blessed file.
Subsequent runs inject the blessed maps at 5-28% strength scaled by
reward, transferring semantic structure from good outputs onto new runs.
All enhancements are composable with existing attn2 direction patches
and STG/PAG blocks via patches_replace composition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two new output pairs between seed and loss_graph:
- high_pass_sampler (SAMPLER) + high_pass_sigmas (SIGMAS)
- low_pass_sampler (SAMPLER) + low_pass_sigmas (SIGMAS)
Sampler tab in popup: per-pass selection of Hybrid Euler 2S, Distilled
Flow, or KSampler (standard ComfyUI fallback). Each type shows its own
settings fields. Motion pulse and velocity bias sections collapse when
mode is "off". Sigmas entered as comma-separated floats; empty means
rely on external sigmas input.
Python: _build_one_sampler() and _build_samplers() class methods handle
the instantiation and sigma parsing. Each sampler is built from the
same get_sampler() call as the standalone node.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add negative_prompt STRING input. Priority chain for negative output:
1. negative_conditioning (pre-encoded CONDITIONING) - pass through as-is
2. negative_prompt (text) encoded via CLIP - removes need for external CLIPTextEncode
3. Popup default negative_prompt field in Refiner tab
4. Empty list if nothing is provided
Negative prompt field added to Refiner tab in popup with override toggle.
Override system extended with negative_prompt key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inputs reordered per spec: model, clip, advisor_clip, positive_conditioning,
negative_conditioning, clip_vision_output, source_image, lora_stack,
positive_prompt, user_intent_prompt, feedback_prompt, refinement_key_input.
Outputs reordered: model, modified_positive, negative, seed, loss_graph,
status, training_info, encoded_prompts.
negative_conditioning: CONDITIONING input passed straight through to the
negative output unchanged. Empty list when not connected.
advisor_clip: external CLIP input now accepted. Takes precedence over the
LLM configured in the Advisor popup tab. Same pattern as other overridable
inputs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LoRA pipeline is now fully internal to Studio:
1. FunPackApplyLoraWeights reads session weight suggestions for the
current prompt and builds a stack with adjusted model weights
2. FunPackLoraLoader applies the stack to model and clip
3. refine_v2 receives the LoRA-patched model and applies the attn2
direction patch on top
Removes the lora_stack return value - MODEL output already contains
everything (LoRA patches + direction injection). External lora_stack
input still accepted to bypass internal management entirely.
Adds mode (ltx2/wan) and per_block settings to the LoRA tab in popup,
stored in loras_config dict in studio_settings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moved from required widget to optional forceInput so it appears as a
connector like all other inputs, not as a text area widget.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add lora_stack (FUNPACK_LORA_STACK) as the 8th return value so the
active stack flows out for downstream use
- Remove positive_prompt from hidden widgets so the text area is visible
on the node face and can be connected as a link input
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each input that has both a connected link and a popup value now has an
"Override" toggle below it (default off). Off = external input wins,
popup value is a fallback. On = popup value wins regardless.
Applies to: refinement_key (Session tab), feedback_prompt and
user_intent_prompt (Refiner tab). All other settings are popup-only
and unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Connected value overrides whatever is typed in the Refiner popup.
Useful for wiring dynamic feedback from another node.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FunPackStudio combines Refinement Key, Scene Builder, Refiner V2,
Advisor LLM, LoRA loading, and Conditioning Adjust under a single node
with a tabbed popup editor. Rating widget stays on the node face;
everything else is in the popup.
Python:
- FunPackStudio.run() chains all subsystems: scene builder prompt
construction, advisor LLM load (using shared cache), LoRA loading via
FunPackLoraLoader, FunPackVideoRefinerV2.refine_v2(), and
FunPackConditioningAdjust.adjust(). No code duplication.
- Added optional _seed parameter to FunPackVideoRefinerV2.refine_v2()
so Studio can generate a seed and output it for wiring to samplers.
- Added /funpack/available_loras endpoint for the LoRA picker.
JS (funpack_studio.js):
- Tabbed popup: Session, Scene, Refiner, Advisor, LoRA, Adjustments.
- Session: refinement key, reset toggle, scene builder mode selector.
- Scene: scene load/save, phrase bank from memory, positive prompt
editor (active when scene builder is not Pass-through).
- Refiner: all V2 settings, feedback prompt, intent override.
- Advisor: LLM enable toggle, model path, dtype.
- LoRA: dynamic list of LoRAs with name/type/model-weight/clip-weight,
loaded from /funpack/available_loras.
- Adjustments: phrase/strength pairs with session phrase bank.
- Settings serialized as JSON in hidden studio_settings widget.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The node was extracting pooled_output from the conditioning meta dict,
which is None for LTX/Gemma3 text encoders. Adjustments were silently
skipped for every phrase.
Use conditioning.mean(dim=(0,1)) on the sequence tensor itself - same
approach V2 uses for all its conditioning memory operations. Phrase
vectors are encoded the same way (mean of their sequence tensor). The
accumulated direction shift is then applied as a uniform broadcast
across all sequence positions so the tensor shape stays intact.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>