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.
This commit is contained in:
digital-garbage
2026-08-22 22:43:36 +03:00
parent bd92dbc555
commit dacfeeea0e
4 changed files with 61 additions and 6 deletions
+10
View File
@@ -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
+12 -1
View File
@@ -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.
+26 -3
View File
@@ -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`);
}
});
+13 -2
View File
@@ -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