fix(sampler): H3 scenes continue from the previous one, not just next to it

H3 has no latent conditioning, so a carried tail was noise it could overwrite:
the seam matched and the rest of the shot knew nothing about the scene before it.
The previous scene's last latent frame becomes this scene's frame-0 pin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-08-20 23:59:27 +03:00
co-authored by Claude Opus 5
parent 71777a9f29
commit d4a69eb873
2 changed files with 93 additions and 0 deletions
+44
View File
@@ -4940,6 +4940,42 @@ class FunPackLTXAVSceneChainSampler:
aug=None if aug >= 1.0 else aug)
return positive, True
def _h3_has_first_frame_pin(self, conditioning):
pins = self._conditioning_value(conditioning, "minimax_keyframes") or []
return any(int(p.get("resolved_frame_index", -1)) == 0 for p in pins)
def _h3_continuation_pin(self, positive, carry_source):
"""Continue an H3 scene from the previous one, the only way H3 can be told to.
LTX chains through the LATENT: the previous scene's tail is copied into the leading
frames and masked out of denoising, and the model treats those frames as context.
H3 has no latent conditioning at all — a pre-filled frame is, in its own terms, just
noise it is free to overwrite. The mask still holds those pixels in the output, so
the seam looked right while the rest of the shot was generated knowing nothing about
what came before: the chain produced a batch of unrelated clips.
The previous scene's last latent frame becomes this scene's frame-0 keyframe pin,
which is exactly the conditioning an i2v anchor uses. No VAE round trip: the frame is
already in latent space, and the pin path takes latents (the guide path already
relies on that). Skipped when something already pinned frame 0 — an explicit anchor
or a wired first_frame outranks a carried tail.
Returns (positive, applied).
"""
if carry_source is None or self._h3_has_first_frame_pin(positive):
return positive, False
try:
video = self._latent_tensors(carry_source)[0]
if getattr(video, "ndim", 0) < 5 or int(video.shape[2]) < 1:
return positive, False
frame = video[:1, :, -1:].clone()
except Exception as error: # noqa: BLE001
print(f"[FunPackSceneChain] H3: continuation pin skipped — {error}.")
return positive, False
positive = self._h3_add_keyframes(
positive, [{"resolved_frame_index": 0, "latent": frame}], self._h3_frame_count)
return positive, True
def _h3_external_pins(self, conditioning):
"""Rescue the keyframe pins from a MiniMax H3 Image to Video node's conditioning.
@@ -6714,6 +6750,14 @@ class FunPackLTXAVSceneChainSampler:
run_mechanisms.append(f"h3_wired_keyframes({'+'.join(_pin_labels)})")
if "first" in _pin_labels:
_h3_opening_anchored = True
# Nothing claimed frame 0 and a previous scene exists: continue from its last
# frame. Without this the carried latent tail is not conditioning on H3 — the
# seam matched and the rest of the shot knew nothing about the scene before it.
if _carry_source is not None:
scene_positive, _continued = self._h3_continuation_pin(
scene_positive, _carry_source)
if _continued:
run_mechanisms.append("h3_continuation_pin")
# ref2va reference blocks. Applied after the chunk is final because the blocks are
# sized against this scene's canvas, and to every scene branch (fresh / anchored /
+49
View File
@@ -556,3 +556,52 @@ def test_a_wired_first_frame_overrides_the_timeline_anchor_on_frame_zero():
out, _labels = node._apply_h3_external_pins(positive, pins, scene_index=0, scene_count=1)
kfs = out[0][1]["minimax_keyframes"]
assert len(kfs) == 1 and kfs[0]["latent"] is not anchor
# ── continuing a scene from the one before it ────────────────────────────────
def _pins(cond):
return (cond[0][1] or {}).get("minimax_keyframes") or []
def test_a_continuation_scene_is_pinned_to_the_previous_scenes_last_frame():
"""H3 has no latent conditioning: a carried tail is, in the model's terms, noise it may
overwrite. Without a pin the seam matched and the rest of the shot knew nothing about the
scene before it — the chain produced unrelated clips."""
node = h3_node()
previous = av_latent(video_t=37)
previous["samples"].unbind()[0][:, :, -1] = 7.0 # a mark only the last frame has
positive = [[torch.zeros(1, 8, 16), {}]]
out, applied = node._h3_continuation_pin(positive, previous)
assert applied is True
pins = _pins(out)
assert [p["resolved_frame_index"] for p in pins] == [0]
latent = pins[0]["latent"]
assert latent.shape == (1, 24, 1, 48, 84) # exactly one latent frame
assert float(latent.max()) == 7.0 # and it is the LAST one
def test_an_explicit_anchor_outranks_the_carried_tail():
"""An anchor image (or a wired first_frame) is the user saying where this scene starts;
the tail is inferred. Two pins cannot share frame 0."""
node = h3_node()
anchored = [[torch.zeros(1, 8, 16), {"minimax_keyframes": [
{"resolved_frame_index": 0, "latent": torch.full((1, 24, 1, 48, 84), 3.0)}]}]]
out, applied = node._h3_continuation_pin(anchored, av_latent())
assert applied is False
assert float(_pins(out)[0]["latent"].max()) == 3.0
def test_the_opening_scene_has_nothing_to_continue_from():
node = h3_node()
positive = [[torch.zeros(1, 8, 16), {}]]
out, applied = node._h3_continuation_pin(positive, None)
assert applied is False and _pins(out) == []
def test_an_unreadable_carry_source_skips_rather_than_failing_the_render():
node = h3_node()
positive = [[torch.zeros(1, 8, 16), {}]]
out, applied = node._h3_continuation_pin(positive, {"samples": torch.zeros(1, 24, 4)})
assert applied is False and _pins(out) == []