Nodes/H3 Motion Context/H3 Join Match (experimental)
ComfyUI Node

H3 Join Match (experimental)

Your H3 chain gets darker at every clip — this node measures the step and takes it out

By NikoDemon80·Created 2 months ago·Updated a day ago· 1,275
H3 Join Match (experimental)
  • samples
  • context_latent
  • samples
◄strength1.00►
◄context_length22►
◄min_excess1.5►
◄match_spreadtrue►
◄min_excess_spread1.5►
◄match_detailtrue►
◄min_excess_detail1.5►
◄tuningauto►

MiniMax H3 makes picture and sound together, which is why people started chaining clips: generate eight seconds, pin the tail, generate eight more. It works, then you watch the whole thing back and the first clip looks right while by clip five the shot is grayer, flatter, slightly hazy - and nobody can point at the seam where it happened.

H3 Join Match is the experimental node from the H3 Motion Context pack that goes after that leak. It doesn't touch your prompt, your sampler or the pin. It measures, in the latent, how far this clip's first genuinely new frames sit from the previous clip's last frames, and moves the whole clip back by that much.

What's actually going wrong

On a locked-off shot, the author's own measurement puts that gap around 2.8× the distance the picture normally travels over the same stretch inside a clip. A normal step, then one nearly three times as big. Repeat. That's your slow darkening: each join nudges brightness, contrast and fine texture, and the nudges stack because clip N+1 is pinned to already-nudged frames.

The step is mostly channels the model sets for itself in the new frames, so nothing you do to the pin before sampling can reach it. That's why the fix lives after the sampler.

The mechanism

H3 packs frames into latent steps on a five-step cycle - one frame, then four, four, four, four - and each position in that cycle behaves a little differently, so the node compares like with like: the previous clip's last step against this clip's first new step at the same cycle position. The per-channel difference is the step. It subtracts it, scaled by strength, from the whole clip including the pinned rows, which keeps the clip continuous inside itself - and those rows get trimmed off anyway.

Two finer corrections ride along, both capped at 10% in either direction, so this node can't rescue a disaster: match_spread matches how far each channel swings around its average (contrast, coarse texture), and match_detail splits each frame with a 3×3 blur and rescales only the fine part - grain, skin texture, wrinkles.

Then the guard that stops it eating legitimate motion: the node makes the same comparison inside this clip, five steps further on, and asks how much the picture normally moves. If the join doesn't move more than min_excess times that, it leaves it alone. Motion looks like a step when you're measuring a step; this is the bouncer.

The settings you actually touch

samples is the clip your sampler just made. Optional context_latent is the previous clip - the same latent you loaded for Motion Context. Leave it unwired on clip one and everything passes through untouched.

context_length is the one that matters, and don't type in it: wire Motion Context's trim_frames output into it. That output is the pinned length on a chained clip and 0 on the first, so the two nodes can't drift apart. Hardcode 22, then change Motion Context to 39, and your correction gets measured against the wrong window - vaguely plausible, quietly wrong.

Leave tuning on auto. It reads the shot from this clip's new frames and sets the three min_excess guards itself, with the cutoff at 0.48 - from test chains where a locked-off guitarist read 0.37–0.40, a tracking shot 0.58–0.63 and a slow orbit 0.82–0.87. Version 0.7.1 fixed this; 0.7.0's guess sat too low and called every shot moving. Read the console line it prints rather than trusting the widget. manual is for when you know your shot better than a threshold does - 1.0 for locked-off, 1.5 for anything that moves.

One output, samples. Feed it to Save Latent and both decodes, so the video you keep matches the latent the next link continues from. If Refine Blend is in the graph, Join Match goes after it.

Install

ComfyUI Manager, search "H3 Motion Context", or:

cd ComfyUI/custom_nodes
git clone https://github.com/NikoDemon80/ComfyUI-H3-Motion-Context

Restart. No model downloads, no pip installs - plain Python against the torch, numpy and aiohttp ComfyUI already has. You need ComfyUI 0.34.0 or newer; on 0.33.4 you'd run pack version 0.3.1, which doesn't have this node.

Where people get burned

It does nothing on a moving shot, by design. The docs are blunt that correcting moving shots measured worse than every attempt at it. Handheld chain? Skip this node.

The duplicate install. A fork, a manual clone next to a Manager install, a renamed backup - ComfyUI loads all of them. Confusing symptoms; delete the extra.

Upgrading scrambles widget values. ComfyUI stores them by position, so a workflow saved against an older version loads numbers into the wrong slots. Delete the node, re-add it, rewire.

Strength 0 is a byte-identical no-op. So is an unwired context_latent, and so is context_length 0. If it looks like nothing's happening, check those before blaming the maths.

Worth knowing what this is: one person's measurements, one machine, one resolution, on scenes they chose. On a locked-off shot it's the difference between a chain that grays out and one that holds. On a moving camera, leave it off.

Categorylatent/minimax

Inputs (10)

NameTypeDefaultDescription
samplesLATENTThe clip the sampler just made.
strengthFLOAT1.000–1.5How much of the measured step to remove. 1.0 removes all of it, 0.0 turns it off.
context_lengthINT220–4096Frames pinned by Motion Context. Wire Motion Context's trim_frames here so the two always agree. 0 passes the clip through. Has to be a whole number of latent steps (5, 22, 39, 56).
min_excessFLOAT1.51–5Manual tuning only. Averages: only correct when the join moves more than this many times what the picture normally moves over the same stretch inside the clip. Guards against treating motion as a step.
match_spreadBOOLEANtrueAlso match how far each channel swings around its average, which is where contrast and fine texture live. Off matches the averages only.
min_excess_spreadFLOAT1.51–5Manual tuning only. Spread: only correct when it changes more than this many times its normal change inside the clip. 1.5 is the safe default for any shot. On a locked-off shot, 1.0 lets it correct at every join. The correction is capped at 10% either way.
match_detailBOOLEANtrueAlso match the fine, pixel-to-pixel part of each channel, where grain, skin texture and wrinkles live. It builds up at every join even when the averages and spread hold.
min_excess_detailFLOAT1.51–5Manual tuning only. Fine detail: only correct when it changes more than this many times its normal change inside the clip. 1.5 is the safe default; 1.0 suits a locked-off shot. Capped at 10% either way.
tuningCOMBOautoAuto reads the shot from this clip (still camera or moving) and sets the three min_excess guards itself. Manual uses them as set.
context_latentoptLATENTThe previous clip, the same latent loaded for Motion Context. Leave it unwired on the first clip.

Outputs (1)

NameTypeDescription
samplesLATENT—