From ce9e679f0e7d05cf2680d0b341ccf29b98fe24c4 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sun, 23 Aug 2026 04:50:23 +0300 Subject: [PATCH] fix: three silent fallbacks from tonight now say what they did Unreadable modality tags widened the erase to every position including an image span; a partly-failed erase left some scenes changed; an unreadable sigma disabled sharpening while still reporting it active. A malformed reference slot claimed nothing was marked. Co-Authored-By: Claude Opus 5 --- movie_editor/backend/builder.py | 10 +++++++++- movie_editor/tests/test_builder.py | 7 +++++-- negative_erase.py | 19 ++++++++++++++++--- samplers.py | 7 ++++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/movie_editor/backend/builder.py b/movie_editor/backend/builder.py index d56d1d90..e5317f13 100644 --- a/movie_editor/backend/builder.py +++ b/movie_editor/backend/builder.py @@ -628,7 +628,15 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No n = int(num) except ValueError: n = 0 - ref = _reference_by_slot(kind, n) if n > 0 else None + if n <= 0: + # A malformed slot is not "no reference marked" — reporting it as that + # sends the user to the Media Bin to fix a value that is wrong in the + # node. Say which it is. + report["unsatisfied"].append( + f"{s.get('node_class')}.{ci_name}: input source '{source}' is not a " + f"reference slot (expected e.g. 'ref#image:1').") + continue + ref = _reference_by_slot(kind, n) if ref is None: # Nothing marked in that slot. The socket is simply left unconnected — # an unused reference slot is a normal state, not a setup mistake, so it diff --git a/movie_editor/tests/test_builder.py b/movie_editor/tests/test_builder.py index d2245cca..5a2b4622 100644 --- a/movie_editor/tests/test_builder.py +++ b/movie_editor/tests/test_builder.py @@ -881,11 +881,14 @@ def test_an_empty_slot_is_not_quietly_filled_with_something_else(): assert not isinstance(graph["slot_r"]["inputs"].get("ref_images.ref_image0"), list) -def test_a_malformed_slot_reference_does_not_crash_the_build(): +def test_a_malformed_slot_says_it_is_malformed_not_that_nothing_is_marked(): + """"No reference marked" sends the user to the Media Bin to fix a value that is wrong in + the node. The two are different problems and must not read the same.""" _graph, report = builder.build( REF_OI, _slot_models(**{"ref_images.ref_image0": "ref#image:nope"}), _ref_params(_img("m1", "a.png"))) - assert not _ref_problems(report) + assert any("not a reference slot" in u for u in report["unsatisfied"]) + assert not any("none marked" in w for w in report["wired"]) def test_the_loader_matches_what_the_destination_socket_asks_for(): diff --git a/negative_erase.py b/negative_erase.py index bd42ea22..3260ef9f 100644 --- a/negative_erase.py +++ b/negative_erase.py @@ -50,7 +50,13 @@ def _text_positions(meta, length): return None # tags and conditioning disagree — touch nothing selectively idx = (flat == 1).nonzero(as_tuple=False).reshape(-1) return idx if int(idx.numel()) else None - except Exception: + except Exception as exc: # noqa: BLE001 + # NOT silent: returning None here widens the operation from "text positions only" to + # "every position", which on H3 means erasing a text direction out of the reference + # image's conditioning. A caller that cannot see this happen cannot explain the + # result it gets. + print(f"[FunPack] negative_erase: could not read the modality tags ({exc}) — the " + f"erase applies to EVERY position, including any image span") return None @@ -154,7 +160,7 @@ def apply(positive, negative, strength, mode="project", renorm=True): if unit is None: return positive, "negative_erase: the negative conditioning has no usable direction" - out, touched = [], 0 + out, touched, failed = [], 0, [] for entry in positive: try: tensor, meta = entry[0], (entry[1] if len(entry) > 1 else {}) @@ -162,8 +168,15 @@ def apply(positive, negative, strength, mode="project", renorm=True): if new is not tensor: touched += 1 out.append((new, meta) + tuple(entry[2:])) - except Exception: # noqa: BLE001 + except Exception as exc: # noqa: BLE001 + failed.append(str(exc)) out.append(entry) + if failed: + return positive, (f"negative_erase: {len(failed)} conditioning entr" + f"{'y' if len(failed) == 1 else 'ies'} could not be modified " + f"({failed[0]}) — NOTHING was changed, because a prompt where only " + f"some scenes had the negative removed is worse than one where none " + f"did") if not touched: return positive, "negative_erase: no conditioning entry could be modified" return out, (f"negative_erase: {mode} {float(strength):.2f} on {touched} " diff --git a/samplers.py b/samplers.py index 5ea333af..b966b1b1 100644 --- a/samplers.py +++ b/samplers.py @@ -1379,7 +1379,12 @@ class _SharpenDenoiser: denoised = self._inner(x, sigma, **kwargs) try: s = float(sigma.flatten()[0]) if hasattr(sigma, "flatten") else float(sigma) - except Exception: # noqa: BLE001 + except Exception as _e: # noqa: BLE001 + # Once, not per evaluation — but never nothing. Silently returning here would + # leave quality_sharpness switched on, reported as active, and doing nothing for + # the whole run. + _log.failed("FunPackStudio", "quality sharpness (unreadable sigma)", _e, + "this sampler's steps are NOT being sharpened") return denoised if s > self._thr: # Outside the window: nothing to sharpen, but the prediction is still KEPT. The