diff --git a/CHANGELOG.md b/CHANGELOG.md index cd8c0b81..98efeaf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,14 @@ `$style` while the encoded prompt had it resolved. ### Fixed +- **A wired reference conditioning is no longer thrown away by the scene split.** The split + re-encodes every scene from text and its entries replace the output wholesale. It + re-establishes H3's visual conditioning from `source_image` / `h3_references` — neither of + which anything in the editor pipeline sets — so on a reference run the identity was + replaced by text-only encodes and the character came out as a different person entirely. + With CLIP disconnected the split returned None and the wired conditioning survived, which + is why disconnecting CLIP appeared to fix it. Studio cannot split a conditioning it did not + build, so it now keeps the one it was given and says so. - **A reference image's encoded rows are no longer steered.** H3 tokenizes a reference INTO the conditioning — comfy marks the whole vision block as 0 in `minimax_token_tags` and text as 1 — and those rows are Qwen's encoding of the picture. Every steering path (learned diff --git a/conditioning.py b/conditioning.py index 877b13e1..d22740a4 100644 --- a/conditioning.py +++ b/conditioning.py @@ -13094,7 +13094,23 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): ) if _conditioning_plan_reason: print(f"[FunPackVideoRefinerV2] {_conditioning_plan_reason}") - if split_by_transitions: + # The split RE-ENCODES every scene from text and its entries replace + # `output_conditioning` wholesale. It re-establishes H3's visual conditioning from + # `source_image` / `h3_references` — but a wired CONDITIONING carries a reference this + # node never received: nothing in the editor pipeline sets either of those, so the + # re-encode is textually blind to the picture and the identity is simply gone. Not + # drift — a different person. Studio cannot split a conditioning it did not build, so + # it keeps the one it was given. + if split_by_transitions and conditioning_owner == "CONDITIONING-owned": + _log.note_on_change( + "studio:split_vs_wired", "FunPackStudio", + "the positive CONDITIONING is wired, so the scene split is SKIPPED — " + "splitting means re-encoding each scene from text, and this node cannot " + "reproduce a reference it never received. Every scene uses the wired " + "conditioning. Disconnect it to let Studio split and encode per scene.") + status = status + ("\nTransition split: skipped — the wired positive " + "CONDITIONING owns the prompt") + enhancement_status + elif split_by_transitions: try: scene_texts = split_scene_texts if scene_texts: diff --git a/tests/test_reference_conditioning_ownership.py b/tests/test_reference_conditioning_ownership.py index b9dfc7e6..367bac27 100644 --- a/tests/test_reference_conditioning_ownership.py +++ b/tests/test_reference_conditioning_ownership.py @@ -7,6 +7,7 @@ so the vision block precedes the prompt and every prompt row is a hidden state t already read the picture. Studio cannot reproduce that: nothing in the editor pipeline wires `h3_references` or `source_image`, so its encode never sees an image at all. """ +import inspect import sys import types @@ -83,3 +84,34 @@ def test_an_invalid_wired_conditioning_falls_back_to_clip(studio, monkeypatch, c assert owner == "CLIP-owned" assert torch.equal(cond, torch.ones(1, 4, 8)) assert "invalid" in capsys.readouterr().out + + +# --- the scene split cannot rebuild what it did not encode ----------------- + +def test_the_split_is_skipped_when_the_conditioning_is_wired(monkeypatch, capsys): + """The split re-encodes every scene from text and its entries replace the output + WHOLESALE. It re-establishes H3's visual conditioning from source_image / + h3_references — neither of which the editor pipeline ever sets — so a wired reference + conditioning was replaced by text-only encodes and the character became someone else. + + With CLIP disconnected the split returns None and the wired conditioning survived, which + is exactly why disconnecting CLIP 'fixed' it. + """ + import conditioning + node = conditioning.FunPackVideoRefinerV2() + called = [] + monkeypatch.setattr(node, "_v2_transition_scene_conditionings", + lambda *a, **k: called.append(a) or None, raising=False) + + src = inspect.getsource(conditioning.FunPackVideoRefinerV2.refine_v2) + # The guard is on ownership, not on CLIP being absent. + assert 'split_by_transitions and conditioning_owner == "CONDITIONING-owned"' in src + assert "elif split_by_transitions:" in src + + +def test_the_skip_is_reported_not_silent(): + """A silently unsplit multi-scene run looks like the scene texts were ignored.""" + import conditioning + src = inspect.getsource(conditioning.FunPackVideoRefinerV2.refine_v2) + assert "the scene split is SKIPPED" in src + assert "Disconnect it to let Studio split and encode per scene." in src