H3 Refine Blend (experimental)
The second pass that stops your chained characters aging
- original
- refined
- latent
Chain enough MiniMax H3 clips and the face changes. Nobody turns into a different person, but the skin polishes, fine detail accumulates, and after five or six links your lead looks lightly airbrushed and lightly aged at once. Every clip continues from a copy of the previous one, so each copy's small errors compound. Photocopy of a photocopy.
The fix is obvious in hindsight and fiddly by hand: give each clip a light second pass so it re-reads your references instead of only copying its predecessor. The catch is that a second pass moves the pinned head, which reopens the seam the chaining setup exists to close. H3 Refine Blend resolves that. Hold the pinned frames exactly as sampled, ramp into the refined version over the frames after them, take the refine neat from there on.
What the refine should be
This is where people go wrong. H3's noise shift is 12, so the denoise numbers you know from SDXL mean something else entirely: 0.55 restarts at noise 0.94, essentially a full re-render, and even 0.025 restarts at 0.24. Three options, measured across six-clip chains of the same scenes:
- 0.55 - face held, background redrawn, color up 18%.
- 0.025 - background held, fine detail up 43%, but the face aged and got harsh.
- One step from the stock schedule's last level (about 0.63) - face held, background held, color flat, least detail build-up.
So the recommended refine is literally one step. Split Sigmas on the main BasicScheduler output - before any Sigmas Tail - take step 7 of an 8-step schedule, and feed its low end into a second sampler. Give that sampler the plain Reference to Video conditioning, not the Motion Context output, and its own random seed. That last bit isn't a throwaway: the point is re-reading your character sheet, and conditioning with the previous clip baked in can't do that.
A refine also runs entirely at the clean end of the schedule - where a distilled model like FastH3 was never trained to work, so re-running its own last step stays inside what it knows.
The node itself
original is the clip straight from the sampler; its head is what matches the previous clip. refined is the same latent after the second sampler. Same shape or the node refuses, naming both, because a different render isn't a refine.
hold_frames is how much of the head is kept exactly as sampled - set it to Motion Context's context_length, or drive it from the same place and stop thinking about it. blend_frames is the ramp after the hold; 0 is a hard cut, which shows up as a pop. The 22/22 defaults are what the author settled on: 0.7.1 deleted an auto-tuning widget here because the one-step refine moved the clip about the same amount across all three test chains, and auto kept picking 22 anyway.
Leave audio on original. The picture pin comes back at roughly 0.997 correlation, so held and refined frames are near-identical renderings of the moment and crossfading them is invisible. Audio comes back at 0.88 at best, and without dialogue the model latches onto whatever transient is in the pinned second and stamps it forward. Blending two audio latents that far apart isn't a crossfade in any acoustic sense. It's a smear.
use_refine is the A/B switch, and it's well built: the refined input is declared lazy, so switching it off means ComfyUI never runs the second sampler and you don't pay the render time.
One output, latent. Wire it into H3 Join Match, then Save Latent and both decodes. Order matters - Join Match belongs after this node, so it also sees the detail the refine adds at the join; the author measured that reordering cutting join detail by roughly three quarters.
clip_index, and why it's a widget
At clip_index 0 there's no previous clip, so no join to protect: the whole clip comes from the refined latent and the hold is skipped. There's no wire for it - the frontend mirrors Load Latent's index into any Refine Blend sharing the same canvas group. Outside that group it stays at 1, treating every clip as chained, including the first.
Install
ComfyUI Manager, search "H3 Motion Context", or:
cd ComfyUI/custom_nodes
git clone https://github.com/NikoDemon80/ComfyUI-H3-Motion-Context
Restart. Nothing to download - no models, no pip packages beyond what ComfyUI already ships. Needs ComfyUI 0.34.0 or newer, and install it once: a fork or a renamed duplicate in custom_nodes loads alongside it.
Real failure modes
"A 44 frame hold is not a whole number of latent steps." Valid counts come from the 1,4,4,4,4 cycle - 1, 5, 9, 13, 17, 18, 22, 26, 30, 39 - not just the four windows Motion Context offers, and the error prints the nearest. blend_frames is more forgiving, since weights are per latent step, but 44 still doesn't exist.
Nothing is refined. If the warning says the hold and ramp cover the whole clip, you've held 22 and ramped 22 on a clip with nothing left over. Shorten one.
A pop 22 frames in. That's blend_frames at 0.
And after any pack upgrade, delete and re-add the node rather than debugging scrambled numbers - the widgets have changed across versions and ComfyUI stores values by position.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| original | LATENT | Straight from the sampler, untouched. Its head is what matches the previous clip. | |
| hold_frames | INT | 220–512 | Kept exactly as sampled: the frames the pin established. Match the Motion Context context_length, or drive it from the trim so the two cannot drift apart. Has to be a whole number of latent steps (5, 22, 39, 56). |
| blend_frames | INT | 220–512 | Frames to ramp from held to refined after the hold. 0 is a hard cut, which will show as a pop. |
| audio | COMBO | original | Leave on original. The audio pin returns at 0.88 at best and stamps transients forward on material without dialogue, so refining it is a separate experiment. |
| use_refine | BOOLEAN | true | Off ignores the refined input and passes the original through, so you can A/B the refine without unwiring the second sampler. |
| clip_index | INT | 10–9999 | Driven by the Chain node, the same way Load and Save are, as long as this node sits in their canvas group. 0 is the first clip: nothing pinned, so the whole thing gets refined and the hold is skipped. |
| refinedopt | LATENT | The same latent after a second sampler: one step from the main schedule's last level. With use_refine off, whatever feeds this is not executed at all. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| latent | LATENT | — |