From dacfeeea0ebd90fd590688a80768a6f30eea5033 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sat, 22 Aug 2026 22:43:36 +0300 Subject: [PATCH] fix(sampler): JoyAI-Echo overwrote the i2v anchor on H3 H3 keys keyframe pins by frame index and takes only first/last, so memory frame 0 evicted the anchor and the rest were refused. Forced off with a reason; hidden in Engine Settings. Tooltips now state the LoRA requirement. --- CHANGELOG.md | 10 +++++++ movie_editor/frontend/pipeline_caps.js | 13 ++++++++- movie_editor/frontend/pipeline_caps.test.js | 29 ++++++++++++++++++--- samplers.py | 15 +++++++++-- 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aade9000..9aea86a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,16 @@ looked exactly like one where the feature was switched off. ### Fixed +- **JoyAI-Echo is switched off and hidden on MiniMax H3, where it was doing damage.** The + memory bank places frame *i* at sequence position *i*, but H3's packed layout pins only the + first or last frame — so every frame past the first was refused, and the one that landed + *replaced the scene's i2v anchor*, because H3 keys pins by frame index. Not an inert toggle: + a harmful one. The sampler now forces it off on H3 and says why, and Engine Settings stops + offering the whole group. +- **JoyAI-Echo's tooltips now say it requires the JoyAI-Echo LoRA.** Without it the injected + memory frames change nothing on any model, because the base weights were never trained to + read them as memory — the controls read as a working feature. + - **Closing Models & Pipeline mid-edit no longer leaves its edit buffer behind.** Escape, the ✕, or switching to another section tore the section down without ending the edit, so the next visit inherited a dirty flag and a baseline belonging to a config that had already diff --git a/movie_editor/frontend/pipeline_caps.js b/movie_editor/frontend/pipeline_caps.js index 86b670fa..606bd192 100644 --- a/movie_editor/frontend/pipeline_caps.js +++ b/movie_editor/frontend/pipeline_caps.js @@ -128,8 +128,19 @@ bounded_attention_enabled: "Bounded Attention masks text cross-attention; H3 puts text in the same self-attention " + "stream as the video, where the mask would be far too large to hold.", + joyai_memory: + "JoyAI-Echo places memory frame i at sequence position i. H3's packed layout pins only " + + "the FIRST or LAST frame, so every frame past the first is refused — and the one that " + + "lands replaces the scene's i2v anchor, because pins are keyed by frame index. This " + + "one is not merely inert on H3, it is harmful, so the sampler forces it off.", }; + // JoyAI's own sub-settings: they configure a bank that cannot be built here. + const JOYAI_SUB_INPUTS = [ + "joyai_memory_size", "joyai_fix_frames", "joyai_frame_select", "joyai_memory_strength", + "joyai_audio_memory", + ]; + // Same idea for non-boolean knobs whose neutral value means "off". // The latent ops (second_pass_op, segmented detailing) are NOT listed here any more: what // they need is an upsampler whose latents are the same width as the model's, which depends @@ -171,7 +182,7 @@ if (!usesChainSampler(st)) return new Set(); if (!isH3(st)) return new Set(Object.keys(H3_ONLY_SAMPLER_INPUTS)); return new Set([...Object.keys(H3_DEAD_SAMPLER_INPUTS), ...IDENTITY_SUB_INPUTS, - ...H3_DEAD_VALUE_INPUTS]); + ...JOYAI_SUB_INPUTS, ...H3_DEAD_VALUE_INPUTS]); } // Returns [{short, detail}] for settings that are ON but cannot do anything on H3. diff --git a/movie_editor/frontend/pipeline_caps.test.js b/movie_editor/frontend/pipeline_caps.test.js index 3fc04eb0..7b175cad 100644 --- a/movie_editor/frontend/pipeline_caps.test.js +++ b/movie_editor/frontend/pipeline_caps.test.js @@ -26,11 +26,13 @@ test("H3 hides identity transfer's sub-settings with it, not just the toggle", ( } }); -test("H3 hides v2a_grad_scale but keeps the feature it couples to", () => { +test("H3 hides the whole JoyAI group, coupling knob included", () => { + // This used to assert the opposite for joyai_audio_memory, on the reading that only the + // LTXAV coupling knob was dead. That was wrong: the VIDEO half does not work on H3 + // either, and audio memory is gated on it, so nothing in the group can function. const inert = PC.familyInertInputs(h3); assert.ok(inert.has("v2a_grad_scale")); - // JoyAI audio memory itself works on H3 — only the LTXAV coupling knob is dead. - assert.ok(!inert.has("joyai_audio_memory")); + assert.ok(inert.has("joyai_audio_memory")); }); test("LTX hides the H3-only audio clock", () => { @@ -70,3 +72,24 @@ test("the main-window chips no longer name a control that is now hidden", () => assert.ok(!chips.some((c) => /can't run/.test(c.short)), "a hidden setting should not also be chipped"); }); + +// ── JoyAI-Echo is hidden on H3 ──────────────────────────────────────────────── +// Not because it fails to fire, but because it fires and does the wrong thing: H3 keys +// keyframe pins by frame index and accepts only the first or last, so the one memory frame +// that lands REPLACES the scene's i2v anchor and the rest are refused. + +test("every JoyAI control disappears on H3", () => { + const inert = PC.familyInertInputs(h3); + for (const k of ["joyai_memory", "joyai_memory_size", "joyai_fix_frames", + "joyai_frame_select", "joyai_memory_strength", "joyai_audio_memory", + "v2a_grad_scale"]) { + assert.ok(inert.has(k), `${k} should be hidden on H3`); + } +}); + +test("JoyAI stays available on LTX", () => { + const inert = PC.familyInertInputs(ltx); + for (const k of ["joyai_memory", "joyai_memory_size", "joyai_audio_memory"]) { + assert.ok(!inert.has(k), `${k} must remain reachable on LTX`); + } +}); diff --git a/samplers.py b/samplers.py index bc532587..73a1dd0c 100644 --- a/samplers.py +++ b/samplers.py @@ -2740,7 +2740,7 @@ class FunPackLTXAVSceneChainSampler: # mapping and lands a numeric on a combo (e.g. joyai_frame_select got 0). "joyai_memory": ("BOOLEAN", { "default": False, - "tooltip": "JoyAI-Echo cross-shot memory bank. Generalizes mid_scene_guide from one anchor to a managed set of clean prior-shot frames injected into each scene via LTX guide attention, so character/scene identity carries across the whole chain (JoyAI-Echo's story-level consistency). The first joyai_fix_frames scenes are pinned permanently as a global anchor; the rest is a rolling most-recent window capped at joyai_memory_size. Supersedes mid_scene_guide when on. Video memory only; pair it with joyai_audio_memory for the soundtrack.", + "tooltip": "JoyAI-Echo cross-shot memory bank. REQUIRES the JoyAI-Echo LoRA — without it the injected memory frames change nothing, because the base model was never trained to read them as memory. Generalizes mid_scene_guide from one anchor to a managed set of clean prior-shot frames injected into each scene via LTX guide attention, so character/scene identity carries across the whole chain (JoyAI-Echo's story-level consistency). The first joyai_fix_frames scenes are pinned permanently as a global anchor; the rest is a rolling most-recent window capped at joyai_memory_size. Supersedes mid_scene_guide when on. Video memory only; pair it with joyai_audio_memory for the soundtrack.", }), "joyai_memory_size": ("INT", { "default": 7, "min": 1, "max": 32, @@ -2762,7 +2762,7 @@ class FunPackLTXAVSceneChainSampler: # builder's positional reference-workflow mapping (extract_widgets) stays aligned. "joyai_audio_memory": ("BOOLEAN", { "default": False, - "tooltip": "JoyAI-Echo PAIRED AUDIO memory. Alongside each video memory frame, pin the prior shot's clean audio latent into the audio stream so voice/timbre/ambience carry across shots the way the face now does. Deliberately breaks the audio pass-through invariant — off by default. Requires joyai_memory on; no effect on single-stream (video-only) LTXV.", + "tooltip": "JoyAI-Echo PAIRED AUDIO memory. Needs the JoyAI-Echo LoRA, like the video memory it accompanies. Alongside each video memory frame, pin the prior shot's clean audio latent into the audio stream so voice/timbre/ambience carry across shots the way the face now does. Deliberately breaks the audio pass-through invariant — off by default. Requires joyai_memory on; no effect on single-stream (video-only) LTXV.", }), "v2a_grad_scale": ("FLOAT", { "default": 1.0, "min": 0.0, "max": 4.0, "step": 0.25, @@ -6680,6 +6680,12 @@ class FunPackLTXAVSceneChainSampler: "against LTX's 4096-wide cross-attention context, and the overlap " "tokens need LTX's patchifier; H3's native ref2va reference blocks " "are the equivalent and are not wired yet)") + if joyai_memory: + _dead.append("joyai_memory / JoyAI-Echo (the memory bank places frame i at " + "sequence position i, and H3's packed layout pins only the FIRST " + "or LAST frame — so every frame past the first is refused, and " + "the one that lands REPLACES the scene's i2v anchor, since pins " + "are keyed by frame index. Not inert on H3: harmful)") # Gated on joyai_audio_memory, not on the value alone: v2a_grad_scale is that # feature's coupling knob and is never installed without it (see the call site), # so reporting a left-over value as an H3 limitation blames the model for a knob @@ -6698,6 +6704,11 @@ class FunPackLTXAVSceneChainSampler: # that dies three minutes in is worse than a knob that says why it is inert. bounded_attention_enabled = False identity_transfer_enabled = False + # Off for real, not merely hidden in the UI: unlike the two above, this one does + # not fail to fire — it fires and overwrites the anchor pin. A stored value from + # an LTX project must not keep doing that here. + joyai_memory = False + joyai_audio_memory = False v2a_grad_scale = 1.0 # second_pass_op is NOT switched off here. It only needs a latent upsampler that # matches this model's latent width; which file that is depends on what is