Rework the README as a showcase, and put SWAP in the changelog
Browse filesThe page opened with ten version blockquotes -- roughly 145 lines of patch
notes, back to 0.4.1 -- before it said what the pack does. Somebody arriving
from the registry read four months of small updates first.
**The changelog is now one section at the bottom**, five short paragraphs
instead of ten quoted blocks, with the detail left in CHANGELOG.md where it
belongs. Above it, the page leads: what it gives you, install, the one law,
WRITE, SWAP, the tester's study, then the join and the tone anchor, and only
then a **Reference** section holding the shot-plan schema, directives,
register, media, cache, instruments, defaults, nodes and limits.
New sections:
- **SWAP.** It shipped in this release with no user-facing documentation
anywhere -- not in the README, and not one mention in CHANGELOG.md. Both
now carry the four modes and, more importantly, the two usage rules that
cost renders to establish: do not describe the clip on MEDIA before a swap
(that caption reaches the encoder as what `<Video 1>` IS, and naming a
person asks for the person being replaced), and drop the clip to about
0.3 MP, because a clip's decode area is its token count and its token
count is its influence.
- **Tested in public**, with @urlilgoddess 's scorecard and her
background-detail frames, published with her consent. Her ten-run nine-hop
study is why three of this release's behaviours exist, and her framing --
relay convergence with no content anchor -- explains why the scale knobs
did nothing better than ours did. The section also says plainly what she
could not fix and this release does not claim to: texture still ratchets,
3-5 hops is still the honest limit.
- **Credits**, naming @urlilgoddess , @frankyi and @Sean3884 of the Sulphur
discord alongside rkfg and Motion-Context.
CHANGELOG.md gains SWAP in all three of its lists -- the feature, what a GPU
confirmed, and what it has not. The wardrobe plate and the picture
background go in the unverified list: they could not have rendered before
the fix two commits ago, and that fix is covered offline, not on a GPU.
Both tester images are LFS-tracked (`docs/img/*.png`), so their URLs use
media.githubusercontent.com; the page has no raw.githubusercontent link.
Every image it references exists, every internal anchor resolves, and
nothing links to a README anchor that this rewrite removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RVz8qNGn2NhrP3KG2Lh5yT
- CHANGELOG.md +39 -0
- README.md +618 -596
- docs/img/tester-background-drift.png +3 -0
- docs/img/tester-scorecard.png +3 -0
|
@@ -37,6 +37,38 @@ four new behaviours.
|
|
| 37 |
for the register default (unscheduled stills on chain starts, off
|
| 38 |
continuations). `[]` is none. A filled list is those tags only, in that
|
| 39 |
order. Unknown tags fail on the queue.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
### Already on the 1.2 tree, now in the release
|
| 42 |
|
|
@@ -80,6 +112,9 @@ take it; a third-party node that assumes fp32 has not been tested against it.
|
|
| 80 |
- `last_frame_guide` turns the restart jump into a match cut, and `still`
|
| 81 |
fights an authored framing as described above.
|
| 82 |
- fp16 survives delivery.
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
### Still not verified
|
| 85 |
|
|
@@ -89,6 +124,10 @@ take it; a third-party node that assumes fp32 has not been tested against it.
|
|
| 89 |
- Anything longer than 4 hops at 8 s. The texture ratchet is unchanged and
|
| 90 |
3-5 hops is still the honest limit.
|
| 91 |
- Three reference clips or three voices at once, on a card.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
Saved 1.1 workflows load. New widgets were appended, not inserted, and no
|
| 94 |
existing default changed.
|
|
|
|
| 37 |
for the register default (unscheduled stills on chain starts, off
|
| 38 |
continuations). `[]` is none. A filled list is those tags only, in that
|
| 39 |
order. Unknown tags fail on the queue.
|
| 40 |
+
- **SWAP**, a fifth tab. A one-hop identity swap from a reference clip: the
|
| 41 |
+
clip supplies the motion and the scene, a still from the REFERENCES rail
|
| 42 |
+
supplies the person. **Write** drafts it, **Accept** writes exactly one
|
| 43 |
+
shot plus the clip's description and never touches `ref_plan` -- your
|
| 44 |
+
register is not rewritten. Contributed by @frankyi, then rebuilt from its
|
| 45 |
+
own prose rather than merged.
|
| 46 |
+
|
| 47 |
+
Four named modes, because at cfg 1.0 there is no negative branch and a
|
| 48 |
+
mode that merely *omits* the swap line does not keep the clip's person --
|
| 49 |
+
the identity photograph is in front of the encoder either way and governs
|
| 50 |
+
the subject anyway. Each mode says positively what stays:
|
| 51 |
+
`replace_person` (face, build, hairstyle and wardrobe),
|
| 52 |
+
`head_swap` (face, hair and skin tone; the body, posture, hands and every
|
| 53 |
+
garment stay with the clip), `face_only` (features only), and
|
| 54 |
+
`keep_person` (swaps nobody -- the clip is a scene and motion plate, and
|
| 55 |
+
the identity picker greys out). The taxonomy follows PromptMasterLD's edit
|
| 56 |
+
laws; the prose is written fresh for H3 beats.
|
| 57 |
+
|
| 58 |
+
Alongside them: **background** from the clip, from a `@tag` picture, or
|
| 59 |
+
free; and an optional **wardrobe plate**, a `@tag` whose garment is *worn*
|
| 60 |
+
-- draping on the body in frame and creasing where it bends -- rather than
|
| 61 |
+
pasted.
|
| 62 |
+
|
| 63 |
+
**Two things to get right, both of which cost renders to find out.** Do
|
| 64 |
+
not run MEDIA's describe on the clip before a swap: that caption reaches
|
| 65 |
+
the encoder as what `<Video 1>` *is*, and a caption naming a person asks
|
| 66 |
+
for the person you are replacing. Four consecutive "head swap doesn't
|
| 67 |
+
work" reports traced to that, a missing frame sequence and a weak
|
| 68 |
+
citation -- no broken code among them. SWAP now warns when a caption names
|
| 69 |
+
somebody. And drop the clip to about 0.3 MP: a reference clip's decode
|
| 70 |
+
area is its token count, and its token count is its influence, so a
|
| 71 |
+
full-size plate out-argues a single photograph.
|
| 72 |
|
| 73 |
### Already on the 1.2 tree, now in the release
|
| 74 |
|
|
|
|
| 112 |
- `last_frame_guide` turns the restart jump into a match cut, and `still`
|
| 113 |
fights an authored framing as described above.
|
| 114 |
- fp16 survives delivery.
|
| 115 |
+
- SWAP `head_swap` on a 0.3 MP clip with no clip caption: the head is
|
| 116 |
+
replaced, the body and its motion stay with the clip, and no colour or
|
| 117 |
+
hair from the plate bleeds through.
|
| 118 |
|
| 119 |
### Still not verified
|
| 120 |
|
|
|
|
| 124 |
- Anything longer than 4 hops at 8 s. The texture ratchet is unchanged and
|
| 125 |
3-5 hops is still the honest limit.
|
| 126 |
- Three reference clips or three voices at once, on a card.
|
| 127 |
+
- SWAP's wardrobe plate and picture background. Both were cited-but-not-
|
| 128 |
+
scheduled until the fix in this release and could never have rendered;
|
| 129 |
+
the fix is covered offline, not on a GPU. `face_only` and `keep_person`
|
| 130 |
+
have not been run either.
|
| 131 |
|
| 132 |
Saved 1.1 workflows load. New widgets were appended, not inserted, and no
|
| 133 |
existing default changed.
|
|
@@ -13,7 +13,9 @@ tags:
|
|
| 13 |
|
| 14 |
# Hand Tie Clips
|
| 15 |
|
| 16 |
-
|
|
|
|
|
|
|
| 17 |
|
| 18 |

|
| 19 |
|
|
@@ -21,225 +23,78 @@ One node. Write a shot plan, drop in your reference stills, queue.
|
|
| 21 |
is the last frame the first generation produced and the next is the first frame
|
| 22 |
of a second, separate generation. The pack exists so that you cannot tell which.*
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
> AddGuide (`last_frame_guide`) pins `start_image` at a hop's last pixel;
|
| 34 |
-
> default `off`, recommended `before_restart` (only the hop before an
|
| 35 |
-
> `anchor: restart`, so the cut meets on one image). `still` plants it on
|
| 36 |
-
> every hop and fights an authored `framing`. `anchor: "restart"` is a
|
| 37 |
-
> chain start and now writes its full length instead of dropping 0.9 s of
|
| 38 |
-
> new content. A shot can name which stills ride it (`refs`); omitted is
|
| 39 |
-
> the register default, `[]` is none. `tone_anchor_ref=still` holds the
|
| 40 |
-
> photograph rather than hop 1, which already missed it. Three
|
| 41 |
-
> reference-clip slots, three voice slots, `pin_mech`, and a hop cache
|
| 42 |
-
> that no longer pickles. New widgets were appended, so saved 1.1 graphs
|
| 43 |
-
> keep their values. What a GPU has not confirmed yet is named in
|
| 44 |
-
> [`CHANGELOG.md`](CHANGELOG.md).
|
| 45 |
-
|
| 46 |
-
> **0.4.1 β 2026-08-30.** The 0.4.0 feature set was built without a browser or a
|
| 47 |
-
> GPU and verified offline only. It has now been run in ComfyUI, and two things
|
| 48 |
-
> were broken: the five new dials were never added to the run panel's widget list
|
| 49 |
-
> (they worked, but rendered as raw dials on the node body), and a dry run
|
| 50 |
-
> returned a 1Γ1 placeholder image that **libx264 cannot encode** β so every dry
|
| 51 |
-
> run wired to `SaveVideo`, which is what the Starter ships, died in
|
| 52 |
-
> `avcodec_open2`. Both fixed. The Starter now also ships the seam report wired.
|
| 53 |
-
> Measurements are in [`docs/DEVLOG.md`](docs/DEVLOG.md) section 21.
|
| 54 |
-
|
| 55 |
-
> **0.4.4 β 2026-08-30.** Two local models were given `prompt_pack/SYSTEM_PROMPT.md`
|
| 56 |
-
> and the same scene, and both made the same two mistakes β so both were the
|
| 57 |
-
> prompt's fault, not theirs. The register example had no place tag in it, so
|
| 58 |
-
> each model invented its own convention (`kitchen_plate`) while leaving
|
| 59 |
-
> `@kitchen` in the beat, and the run stopped on an unknown reference. One model
|
| 60 |
-
> also wrote `"name": "@cook_face"` into `subjects`, which parsed, rendered, and
|
| 61 |
-
> quietly put a literal at-sign in front of the text encoder on every
|
| 62 |
-
> continuation hop. The prompt now shows a place tag on both sides of the round
|
| 63 |
-
> trip and says the two spellings are one string; `refs.py` now rejects an
|
| 64 |
-
> `@tag` in `name`, `locked` or `context` instead of encoding it. The RUN panel
|
| 65 |
-
> also has its tooltips back β it read them from `widget.options`, where this
|
| 66 |
-
> frontend does not keep them. Section 22 of the devlog has the A/B.
|
| 67 |
-
|
| 68 |
-
> **0.4.5 β 2026-08-30.** The hop cache no longer needs `ffmpeg` on PATH. It
|
| 69 |
-
> used to shell out to an ffmpeg binary to write its lossless FFV1, so the
|
| 70 |
-
> feature that makes a tone A/B cost 14 seconds instead of 164 raised a
|
| 71 |
-
> `RuntimeError` for anyone who did not happen to have ffmpeg installed β
|
| 72 |
-
> which ComfyUI itself never requires. It now encodes in process through PyAV,
|
| 73 |
-
> which ComfyUI already depends on. **The format is unchanged** (ffv1 /
|
| 74 |
-
> `rgb48le` / level 3), verified bit-exact in both directions, so caches
|
| 75 |
-
> written by the old path still read and a resumed chain still matches an
|
| 76 |
-
> uninterrupted one. This also clears the Comfy registry's security scan, which
|
| 77 |
-
> flags any spawn of an external command from a custom node regardless of how
|
| 78 |
-
> it is invoked. Section 23 of the devlog.
|
| 79 |
-
|
| 80 |
-
> **1.0.0 β 2026-09-02.** First full release. The headline is **WRITE**: a plan
|
| 81 |
-
> writer on the node itself, pointed at any OpenAI-compatible server, that fills
|
| 82 |
-
> the script and the reference rows together and sends your reference pictures
|
| 83 |
-
> with the request. Around it, the parts that make a written plan survive
|
| 84 |
-
> contact with the model β the schema the server is handed now *requires* both
|
| 85 |
-
> documents, so a reply carrying only half of one stopped being a silent
|
| 86 |
-
> failure; the reference rail owns each picture's pixel budget, which a plan
|
| 87 |
-
> used to reset to full and quietly triple the load against a fixed canvas; and
|
| 88 |
-
> the writer is told the hop length, so beats are sized to the clip instead of
|
| 89 |
-
> to a guess. Three plan lints were measured against real renders and found to
|
| 90 |
-
> be warning about correct work β a restated framing read as a cut, a
|
| 91 |
-
> single-room chain read as an abandoned location β and were narrowed. Rule 3
|
| 92 |
-
> of the prompt pack grew the case that cost the most renders: a hop that
|
| 93 |
-
> speaks *later* has opening seconds with a picture and no sound assigned, and
|
| 94 |
-
> the model fills them with dialogue nobody wrote. Ending the previous hop
|
| 95 |
-
> quiet buys a quiet pin, not a quiet opening. Sections 31 and 32 of the devlog
|
| 96 |
-
> have the measurements.
|
| 97 |
-
|
| 98 |
-
> **1.1.1 β 2026-09-05.** Fixes a crash on hop 1 for anyone whose ComfyUI
|
| 99 |
-
> orders `MiniMaxH3ReferenceToVideo`'s parameters differently from the build
|
| 100 |
-
> this was written against β reported as `got multiple values for argument
|
| 101 |
-
> 'ref_image_size'`, with nothing sampled and nothing written. The pack was
|
| 102 |
-
> passing Core's first seven arguments positionally; it now passes every
|
| 103 |
-
> argument by name at all three Core call sites, so a reordering upstream
|
| 104 |
-
> cannot rebind anything. If a future Core changes the parameter *names*, the
|
| 105 |
-
> pack now says which node, which arguments it passed and which the installed
|
| 106 |
-
> Core takes, instead of raising a bare `TypeError`. No behaviour changes.
|
| 107 |
-
>
|
| 108 |
-
> **1.1.0 β 2026-09-03.** The panel is a 4:3 box with tabs instead of one long
|
| 109 |
-
> scrolling column, and RUN stays pinned at the bottom. The **WRITE** draft is
|
| 110 |
-
> readable β it used to clip every beat at 110 characters and throw away the
|
| 111 |
-
> duration, seed, directives and per-shot references before rendering, so what
|
| 112 |
-
> you were asked to Accept was a row of sentences ending in an ellipsis β and it
|
| 113 |
-
> now survives leaving the tab or reloading the page. Shot cards carry `β΅` and
|
| 114 |
-
> `lock` buttons driving `render_from` / `render_through`, the new one being the
|
| 115 |
-
> other end of a range `render_through` has had since 0.4: everything before the
|
| 116 |
-
> start is replayed from the hop cache rather than re-rendered.
|
| 117 |
-
>
|
| 118 |
-
> The bug worth upgrading for: **a reference pinned to any hop but the first
|
| 119 |
-
> tended to be rendered as the shot.** `retention_analysis:` β the text that
|
| 120 |
-
> tells the encoder what a picture is *for* β was emitted on hop 1 only, so a
|
| 121 |
-
> still scheduled onto hop 3 arrived uncited; and the identity lock never read
|
| 122 |
-
> `retention`, so a wardrobe plate carrying a subject was announced as *"the
|
| 123 |
-
> only identity β¦ that face, bone structure, and hairstyle match the photograph
|
| 124 |
-
> exactly"* about a photograph of an apron, while the closer simultaneously said
|
| 125 |
-
> clothing follows the live frame. Both fixed. The reference *clip* had the same
|
| 126 |
-
> gap β it went in as `<Video 1>` with nothing naming it β and now has a
|
| 127 |
-
> description field.
|
| 128 |
-
>
|
| 129 |
-
> Output size is computed rather than tabulated: eleven aspect ratios from 21:9
|
| 130 |
-
> to 9:21 across five short-edge tiers, every one on H3's 32 px grid and under
|
| 131 |
-
> its 768Γ1344 cap. H3 is a 768-short-edge model, so **the top rung is 768p and
|
| 132 |
-
> 16:9 there is 1344Γ768** β the size MiniMax states as native, and the same
|
| 133 |
-
> number core's own `adapt_canvas` produces. The hop cache invalidates once
|
| 134 |
-
> because of it. Saved 1.0.x workflows keep the
|
| 135 |
-
> exact pixels they were built with. And changing a reference picture now
|
| 136 |
-
> re-renders only the hops that picture rides, instead of the whole chain.
|
| 137 |
-
|
| 138 |
-
> **1.0.1 β 2026-09-02.** Three fixes, all found by the first people to use
|
| 139 |
-
> 1.0.0. The **WRITE** panel never saved your model on a fresh install: with
|
| 140 |
-
> nothing stored, no entry in the dropdown was ever *selected* and the browser
|
| 141 |
-
> simply displayed the first one, so the panel showed a model the backend had
|
| 142 |
-
> never been told about. That one empty string produced every symptom β WRITE
|
| 143 |
-
> answering *no model is selected*, Just-In-Time loading never firing because
|
| 144 |
-
> nothing was ever asked of the server, and Free VRAM working anyway, which
|
| 145 |
-
> made it stranger still. Underneath it, a failed write of `htc_llm.json` was
|
| 146 |
-
> swallowed and reported as success, so a pack folder the ComfyUI account
|
| 147 |
-
> cannot write to β a system-wide install, a container β gave the same
|
| 148 |
-
> complaint from a different cause with the evidence in a console nobody
|
| 149 |
-
> reads; it now says so. And writing a plan no longer spends attempts on
|
| 150 |
-
> filenames: your photographs are named by the camera, a model that has just
|
| 151 |
-
> looked at one renames it after the tag you gave it, and the node rejected the
|
| 152 |
-
> whole plan over a value the rail was already holding. Worse, a filename
|
| 153 |
-
> complaint is not a prose gap, so it suppressed the repair that actually works
|
| 154 |
-
> for a full round. Sections 34 and 35 of the devlog.
|
| 155 |
-
|
| 156 |
-
**Writing for it:** [PROMPTING.md](PROMPTING.md) is the authoring guide β the rules that come from what this model actually does, not from taste. The node's **WRITE** bar hands the whole job to a local model: describe the scene in a sentence and it fills the script and the reference rows for you. [prompt_pack/](prompt_pack/) is the same writer as a copy-paste prompt, for when you would rather work in a chat window.
|
| 157 |
-
|
| 158 |
-
Each hop is native **MiniMax H3 Reference-to-Video**. Hops after the first are guided by the **previous hop's sampler AV latent** via `ComfyUI-H3-Motion-Context` when that pack is installed (22 picture frames + 24-frame end-aligned audio). Stock `MiniMaxH3AddGuide` is the fallback when Motion-Context is missing or the previous hop was a pixel cache hit. Voice rides every hop under `hop_script=verbatim`; under `next` β which a shot plan forces, so it is the default path whenever you use SHOTS β the voice is a hop 1 reference only, and the console says `voice ref stays off this continue` when it drops. Identity stills ride hop 1; later hops use the pin for wardrobe and room unless a ref lists those hops in `shots`. A 5 s hop drops the airlock on a continuous join β validate seams at 8 s or 15 s.
|
| 159 |
-
|
| 160 |
-
This is not the seamless-chain pack. No airlock script, no Motion-Context, no interior patch.
|
| 161 |
-
|
| 162 |
-
## What v1.0 changed
|
| 163 |
-
|
| 164 |
-
The prompt is no longer a wall of `---`-delimited text, and the MODEL wire is no longer four extra nodes.
|
| 165 |
|
| 166 |
| | |
|
| 167 |
|---|---|
|
| 168 |
-
| **
|
| 169 |
-
| **
|
| 170 |
-
| **
|
| 171 |
-
| **
|
| 172 |
-
| **
|
| 173 |
-
| **
|
|
|
|
|
|
|
| 174 |
|
| 175 |
-
|
| 176 |
|
| 177 |
## Install
|
| 178 |
|
| 179 |
-
### Clone it (recommended)
|
| 180 |
-
|
| 181 |
From your `ComfyUI/custom_nodes/` folder:
|
| 182 |
|
| 183 |
```
|
| 184 |
-
cd ComfyUI/custom_nodes
|
| 185 |
git clone https://github.com/dntpi/ComfyUI-Hand-Tie-Clips.git
|
| 186 |
```
|
| 187 |
|
| 188 |
-
Or from HuggingFace,
|
| 189 |
|
| 190 |
```
|
| 191 |
git clone https://huggingface.co/sandpies/ComfyUI-Hand-Tie-Clips
|
| 192 |
```
|
| 193 |
|
| 194 |
-
Cloning
|
| 195 |
-
|
| 196 |
-
``
|
| 197 |
-
git pull
|
| 198 |
-
```
|
| 199 |
-
|
| 200 |
-
followed by a restart of ComfyUI. No re-downloading, and you can see exactly what changed with `git log`.
|
| 201 |
-
|
| 202 |
-
### Or unzip it
|
| 203 |
-
|
| 204 |
-
Unzip the folder into `ComfyUI/custom_nodes/`, so that it lands as:
|
| 205 |
-
|
| 206 |
-
```
|
| 207 |
-
ComfyUI/custom_nodes/ComfyUI-Hand-Tie-Clips/
|
| 208 |
-
__init__.py
|
| 209 |
-
h3_ref_chain.py
|
| 210 |
-
js/
|
| 211 |
-
workflows/
|
| 212 |
-
```
|
| 213 |
-
|
| 214 |
-
One folder level too deep (`custom_nodes/ComfyUI-Hand-Tie-Clips/ComfyUI-Hand-Tie-Clips/`) is the usual mistake, and the pack simply will not appear.
|
| 215 |
-
|
| 216 |
-
### Either way
|
| 217 |
|
| 218 |
-
No dependencies to install. Everything it imports
|
|
|
|
|
|
|
| 219 |
|
| 220 |
-
Then
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
|
| 225 |
It is installed correctly when all three are true:
|
| 226 |
|
| 227 |
- the startup log carries a line beginning `[HandTieClips]`
|
| 228 |
- the browser console says `[HandTieClips] editor ui v2.0.0 loaded`
|
| 229 |
-
- node search shows a **Hand Tie Clips** category with five nodes, each
|
| 230 |
-
|
| 231 |
-
Workflows saved before the 2026-08-29 rename keep loading: the old ids are still registered as deprecated aliases. Nothing needs migrating.
|
| 232 |
|
| 233 |
-
|
|
|
|
| 234 |
|
| 235 |
## Needs
|
| 236 |
|
| 237 |
- ComfyUI new enough to include **Add Guide for MiniMax H3** (`MiniMaxH3AddGuide`)
|
| 238 |
- A **ref2va** (or hybrid ref2va) checkpoint β fl2va has no reference rows
|
| 239 |
- Video VAE + audio VAE + MiniMax text encoder
|
| 240 |
-
- **
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
-
The two shipped workflows wire the **turbo stack** this node is actually run
|
|
|
|
| 243 |
|
| 244 |
```
|
| 245 |
UNETLoader -> LoRA Loader Stack -> H3 AdaLN LoRA Fix -> MiniMax H3 Low VRAM
|
|
@@ -251,11 +106,23 @@ UNETLoader -> LoRA Loader Stack -> H3 AdaLN LoRA Fix -> MiniMax H3 Low VRAM
|
|
| 251 |
| [ComfyUI-PlagueKind-Nodes](https://github.com/PlagueKind/ComfyUI-PlagueKind-Nodes) | LoRA Loader Stack, H3 AdaLN LoRA Fix, H3 SLA Attention |
|
| 252 |
| [ComfyUI-KJNodes](https://github.com/kijai/ComfyUI-KJNodes) | MiniMax H3 Low VRAM Attention (experimental), Model Preview Override |
|
| 253 |
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
-
|
|
|
|
| 257 |
|
| 258 |
-
| loader | file
|
| 259 |
|---|---|
|
| 260 |
| `UNETLoader` | `minimax_h3_hybrid_fl2va_ref2va_b30-49-int8.safetensors` |
|
| 261 |
| `CLIPLoader` | `qwen3vl_32b_minimax_h3_int8_convrot.safetensors` |
|
|
@@ -264,467 +131,548 @@ Both shipped workflows are saved pointing at the exact files they were rendered
|
|
| 264 |
| LoRA Loader Stack | `minimax_h3_ref2v_turbo_4step_v0.1_comfyui_bf16.safetensors` |
|
| 265 |
| `tiny_vae` | `taeh3.safetensors` (or `none`) |
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
None of it is required by the node itself. Missing a pack, its nodes load as red boxes: delete them, wire the loader straight into `model` and the encoder into `clip`, and raise `steps` from 7 to around 20.
|
| 270 |
-
|
| 271 |
-
## Use
|
| 272 |
-
|
| 273 |
-
1. Restart ComfyUI and load **`workflows/HandTieClips_Starter.json`** β two hops, no references, runs as soon as the loaders are pointed at your files. It carries a six-card **READ ME** board on its own canvas, to the left of the loaders: the three laws, the directive table, the reference rules and the failure table, where you need them rather than in another file. `workflows/HandTieClips_Showcase.json` is the six-hop version and needs three pictures of your own. Both carry the turbo stack listed above.
|
| 274 |
-
2. Point the loaders at your ref2va DiT, encoder, and both VAEs
|
| 275 |
-
3. Add a row per identity still in the **REFERENCES** rail, then **drop a picture onto its thumbnail** (or click to browse, or pick one already uploaded). Give it a `@tag` and group photos of the same person under one subject number. There are no `Load Image` nodes to wire β files land in `ComfyUI/input/h3_refs`.
|
| 276 |
-
4. In **SHOTS**, write one card per hop. Shot 1 is the whole opening; every later card is only the new beat.
|
| 277 |
-
5. Optional: a `voice_file`, a `start_image_file`, a look `reference_video_file` β all picked in the **MEDIA** strip, all files under `input/h3_refs`
|
| 278 |
-
6. Queue, and wire the **`info`** output to a Preview Text node β it prints the fully assembled prompt for every hop
|
| 279 |
-
|
| 280 |
-

|
| 281 |
-
|
| 282 |
-
*Four tabs -- SCRIPT, REFS, MEDIA, WRITE -- each with its count, and RUN pinned below them. SCRIPT is one card per hop: the beat, its duration, and its own join, camera, framing, pace and tail, with `lock` and the range button on the card header.*
|
| 283 |
-
|
| 284 |
-
Keep later-hop beats on **what happens next**. Do not re-describe the face; the photos, the register and the pin already carry it.
|
| 285 |
-
|
| 286 |
-
Two modes:
|
| 287 |
-
|
| 288 |
-
- **Simple** β one prompt box and a hop count, the way it always worked. Later hops advance the same action rather than replaying the opening.
|
| 289 |
-
- **Shots** β one card per hop with directives. Switching from Simple carries your prompt over and splits it on `---`.
|
| 290 |
-
|
| 291 |
-
Only one of them is on screen at a time, so there is never a text box quietly doing nothing.
|
| 292 |
-
|
| 293 |
-
Under the script sits **RUN**, collapsed, holding everything that is not per-shot: output size and length, sampling, the join and pin controls (`pin_mech`, `last_frame_guide`, tone), and the hop cache. Its title line summarises the run β `1344x768 Β· 10s Γ3 Β· 14 steps res_multistep Β· cache off` β so you can read the setup without opening it. In Shots mode `chains` and `hop_script` are not offered there, because the shot list already decides both. `last_frame_guide` ships `off`; reach for `before_restart` when the plan has a restart, and leave `still` alone unless no shot authors a framing (see [Last-frame guide](#last-frame-guide)).
|
| 294 |
-
|
| 295 |
-
**`tone_compensate`** lives in that panel's *join & pin* group. The H3 denoiser biases each hop's tone, so a chain gets steadily brighter; this measures the bias on the overlap each hop regenerated and undoes it, correcting each hop against the previous **corrected** one so the whole chain lands on hop 1's tone. `frame_shift` is the mode to reach for: all three modes remove the drift equally well (within 0.4/255 of each other), but `gain_bias` and `lut` pair pixels between a frame and its *regeneration*, which fits a slope that is not really there and flattens contrast a little more with every hop. `frame_shift` uses frame averages only, so it can shift but never distort. **Measured on a 3-hop render: chain drift 5.6/255 without it, 0.3/255 with it.** Worth turning on for anything past two hops. It ships off because enabling it also clamps the master to 0..1, and because the correction grows with hop count β by hop 10 it is subtracting ~23/255 and will start crushing blacks. Switching modes never invalidates the hop cache, so it is free to A/B. Do **not** judge it by whether the seams flatten to zero: real scene brightness changes across a cut should survive, and they do.
|
| 296 |
-
|
| 297 |
-
## Shot plan
|
| 298 |
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
```json
|
| 302 |
-
{
|
| 303 |
-
"shots": [
|
| 304 |
-
{
|
| 305 |
-
"beat": "The cook stands at the counter, looks up, and speaks one short line.",
|
| 306 |
-
"directives": {"camera": "hold", "framing": "medium", "pace": "steady", "tail": "ongoing"}
|
| 307 |
-
},
|
| 308 |
-
{
|
| 309 |
-
"beat": "The cook sets the knife down and turns toward the window, still talking.",
|
| 310 |
-
"directives": {"join": "continuous", "camera": "push_in", "framing": "close"}
|
| 311 |
-
}
|
| 312 |
-
]
|
| 313 |
-
}
|
| 314 |
-
```
|
| 315 |
-
|
| 316 |
-
Shot 1 is the whole opening. Every later shot is **only the new beat** β the node supplies the identity lock, the live-frame citation and the join itself.
|
| 317 |
-
|
| 318 |
-
Fields, all optional except `beat`:
|
| 319 |
-
|
| 320 |
-
| | |
|
| 321 |
-
|---|---|
|
| 322 |
-
| `beat` | What happens this hop. |
|
| 323 |
-
| `directives` | The five axes below. |
|
| 324 |
-
| `prose` | Free text appended verbatim, for anything the vocabulary lacks. |
|
| 325 |
-
| `seed`, `steps`, `duration` | Per-shot overrides. `duration` takes the same labels as the widget (`"8 s"`). |
|
| 326 |
-
| `locked` | Reuse this shot's cached render even when its inputs changed -- freeze a take you like while you rewrite the hops around it. Needs `cache_hops=on`, and give the shot an `id`. Not to be confused with `subjects.N.locked`, which is identity text. |
|
| 327 |
-
| `tone` | `"free"` skips the chain-wide tone pull once; `"rebase"` also moves the anchor onto this hop. Omit it unless a scene is deliberately darker or brighter from here. |
|
| 328 |
-
| `anchor` | `"restart"` makes this hop a chain start: the start image is frame 0, nothing is relayed. It is a **cut**, so it belongs where a cut is motivated β a pause, a change of thought, a new beat β not on a hop interval. Pair with `join: hard_cut` or `match_cut`; `continuous` is refused. Name the room in the beat: a pin-less hop has nothing else telling it where it is. Never on shot 1. Needs a start image. |
|
| 329 |
-
| `refs` | Which register stills ride this hop, as tags. Omit for the register default. `[]` is none. A list is those tags only, in that order. |
|
| 330 |
-
| `id` | Stable name, used as the cache pointer. Generated if absent. |
|
| 331 |
-
|
| 332 |
-
### Hops of different lengths
|
| 333 |
-
|
| 334 |
-
`duration` is per shot, and everything downstream sizes itself around it β the master buffer, the reference-video decode and the per-hop cache key all read the real lengths:
|
| 335 |
-
|
| 336 |
-
```json
|
| 337 |
-
{
|
| 338 |
-
"shots": [
|
| 339 |
-
{"beat": "She slams the drawer shut.", "duration": "5 s", "directives": {"join": "hard_cut", "pace": "urgent"}},
|
| 340 |
-
{"beat": "She crosses to the window, still talking.", "duration": "15 s", "directives": {"join": "continuous", "camera": "push_in"}},
|
| 341 |
-
{"beat": "She stops and looks back."}
|
| 342 |
-
]
|
| 343 |
-
}
|
| 344 |
-
```
|
| 345 |
-
|
| 346 |
-
The third shot has no `duration`, so it takes the widget's. Labels are the widget's too β `5 s`, `7 s`, `8 s`, `10 s`, `15 s`. That set is fixed, not arbitrary: every value has to land on H3's frame grid (`n % 17 == 5` at 24 fps), so there is no `6.5 s`. Editing one shot's length invalidates that hop and the hops after it, and nothing before it.
|
| 347 |
-
|
| 348 |
-
**Short hops cut, long hops flow.** Overlap is chain-wide β 0.9 s by default β so a 5 s hop asking for `join: continuous` spends a fifth of itself on the airlock, and the node prints a note saying so. That constraint matches how you would edit anyway: quick beats take a cut, flowing takes want length.
|
| 349 |
-
|
| 350 |
-
### Directives
|
| 351 |
-
|
| 352 |
-
| axis | options |
|
| 353 |
-
|---|---|
|
| 354 |
-
| `join` | `continuous`, `match_cut`, `hard_cut` β ignored on shot 1, which has nothing to join to |
|
| 355 |
-
| `camera` | `hold`, `pan_follow`, `push_in`, `pull_back`, `orbit`, `handheld` |
|
| 356 |
-
| `framing` | `keep`, `wide`, `medium`, `close` |
|
| 357 |
-
| `pace` | `slow`, `steady`, `brisk` |
|
| 358 |
-
| `tail` | `ongoing` (default), `settle`, `hold` |
|
| 359 |
-
|
| 360 |
-
An unset axis emits nothing rather than asserting a default, so it costs no tokens.
|
| 361 |
-
|
| 362 |
-
**Everything is phrased affirmatively, on purpose.** Sampling runs at cfg 1.0 with no negative branch, so every concept named is additive β "no cut" puts the word `cut` in front of the encoder. If you add prose of your own, say what the shot *is* doing.
|
| 363 |
-
|
| 364 |
-
`join=continuous` together with a framing change and a held camera will warn: with the camera still, the only way to reach a new framing is a cut, so the two are asking for opposite things. Use `push_in` / `pull_back` / `pan_follow` to earn the framing on the move, or `framing: keep`.
|
| 365 |
-
|
| 366 |
-
A camera move that points the opposite way from the framing (`push_in` with `wide`, `pull_back` with `close`) warns too -- those are physically contradictory whatever the join.
|
| 367 |
-
|
| 368 |
-
When `join=continuous` and the camera *is* moving, the framing sentence is compiled as a **landing** ("The move settles into a close shot...") rather than as the shot's opening state, so it does not fight the pin that still holds the previous framing.
|
| 369 |
|
| 370 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
|
| 372 |
-
|
| 373 |
|
| 374 |
-
|
| 375 |
-
anything you name is added, and nothing can be removed by mentioning it.** Three
|
| 376 |
-
rules follow, each confirmed against renders rather than reasoned from the code.
|
| 377 |
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
```
|
| 383 |
-
The cook stands at the counter in @kitchen, looks up from the chopping board,
|
| 384 |
-
and speaks one short line to someone off-frame.
|
| 385 |
-
```
|
| 386 |
-
|
| 387 |
-
`@kitchen` resolves to the right `<Picture N>` on every hop it is active, so
|
| 388 |
-
rewiring references never breaks the text. Phrase it as a place that is depicted,
|
| 389 |
-
not as a container to be placed inside -- otherwise you can get a literal
|
| 390 |
-
composite of the photograph.
|
| 391 |
|
| 392 |
**Never name the thing you want to end.** `The cook stops talking` keeps her
|
| 393 |
-
talking.
|
| 394 |
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
refrigerator and faint street noise through the window.
|
| 399 |
-
```
|
| 400 |
|
| 401 |
-
H3 generates audio for the whole hop no matter what, so silence
|
| 402 |
-
as a sound
|
|
|
|
|
|
|
| 403 |
|
| 404 |
-
**
|
| 405 |
-
|
| 406 |
-
the
|
| 407 |
-
|
|
|
|
| 408 |
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
shot 3
|
| 413 |
-
|
|
|
|
|
|
|
|
|
|
| 414 |
|
| 415 |
-
|
| 416 |
-
|
|
|
|
| 417 |
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
still misbehaves, name a single discrete event instead -- "a single click from the
|
| 423 |
-
refrigerator, then stillness".
|
| 424 |
|
| 425 |
-
|
| 426 |
-
the prompt, which governs the terminal state of the clip. Left on `ongoing`, the
|
| 427 |
-
model is told action is still underway at the final frame and will invent
|
| 428 |
-
something to satisfy it -- on a last shot that means a stray gesture or a stray
|
| 429 |
-
line of dialogue in the closing second.
|
| 430 |
|
| 431 |
## Let a model write the plan
|
| 432 |
|
| 433 |
-
Open **WRITE**
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
|
| 439 |

|
| 440 |
|
| 441 |
-
- **Context 32768.** The system prompt alone is ~6,000 tokens, the reply
|
| 442 |
-
1,000β2,000, and every reference picture costs ~260 on top.
|
| 443 |
- **Reasoning off.** Thinking tokens come out of the same budget; a reply that
|
| 444 |
stops before the JSON closes is the tell.
|
| 445 |
- **Temperature 0.3.** Higher and the JSON grows trailing commas and smart quotes.
|
| 446 |
|
| 447 |
-
Server settings are saved on this machine only β
|
| 448 |
-
|
| 449 |
|
| 450 |
**Treat what comes back as a strong draft, not a finished plan.** It gets the
|
| 451 |
structure right β hop count, join types, which reference rides which hop, a
|
| 452 |
-
sound bed on every quiet hop β
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
|
| 457 |
-
|
| 458 |
|
| 459 |
-
##
|
| 460 |
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
|
|
|
|
|
|
| 464 |
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
|
| 476 |
-
|
| 477 |
-
decoded and handed to the model alongside its picture. Before 1.2 a reference
|
| 478 |
-
clip arrived silent even when the file had sound. A clip with no usable audio
|
| 479 |
-
track just passes silent and says so in the log.
|
| 480 |
|
| 481 |
-
|
| 482 |
-
H3 encodes the whole file. With one voice the trim window was an optimisation;
|
| 483 |
-
with three it is not. Slots 2 and 3 share slot 1's `reference video size`, which
|
| 484 |
-
is a decode budget rather than a creative setting.
|
| 485 |
|
| 486 |
-
|
|
|
|
|
|
|
|
|
|
| 487 |
|
| 488 |
-
|
| 489 |
-
{
|
| 490 |
-
"refs": [
|
| 491 |
-
{"tag": "hero_face", "file": "cook_face.jpg", "subject": 1, "retention": "fully_preserved"},
|
| 492 |
-
{"tag": "hero_outfit", "file": "cook_apron.jpg", "subject": 1, "retention": "partially_copy"},
|
| 493 |
-
{"tag": "kitchen", "file": "kitchen_wide.jpg", "retention": "reference", "mp": 0.3}
|
| 494 |
-
],
|
| 495 |
-
"subjects": {
|
| 496 |
-
"1": {"name": "the cook", "locked": "the same face, the same short dark hair"}
|
| 497 |
-
}
|
| 498 |
-
}
|
| 499 |
-
```
|
| 500 |
|
| 501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
|
| 503 |
-
|
| 504 |
|
| 505 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
|
| 507 |
-
|
| 508 |
|
| 509 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
|
| 511 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
|
| 513 |
-
|
| 514 |
|
| 515 |
-
|
|
|
|
| 516 |
|
| 517 |
-
|
| 518 |
|
| 519 |
-
|
| 520 |
-
- edit shot 1 β all three re-render, which is correct, not a bug;
|
| 521 |
-
- change resolution, sampler, the checkpoint, a LoRA, or an attention setting β the whole chain re-renders;
|
| 522 |
-
- change `pin_to_qwen` or `overlap` β only hops 2+ re-render, because neither can reach hop 1. A restart hop is a start, so overlap does not reach it either;
|
| 523 |
-
- set `last_frame_guide=still` or set `master_audio_file` β every hop re-renders (both reach hop 1). `before_restart` only moves the hop that actually gets the guide; empty / `off` do not move existing keys;
|
| 524 |
-
- change a reference picture β only the hops that picture rides re-render. Swapping the file behind `@outfit` when it rides hop 5 leaves hops 1-4 on cache. Before 1.1 this invalidated everything.
|
| 525 |
|
| 526 |
-
|
| 527 |
|
| 528 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
|
| 530 |
-
|
|
|
|
| 531 |
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
|
|
|
|
|
|
| 535 |
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
|
|
|
|
|
|
| 540 |
|
| 541 |
-
|
| 542 |
|
| 543 |
-
|
| 544 |
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 553 |
|
| 554 |
-
|
| 555 |
-
against ~45 s/hop at 7 steps: if you already render at 448p and 6β8 steps β
|
| 556 |
-
the regime this pack targets β draft saves almost nothing, and `dry_run` is the
|
| 557 |
-
fast button. Draft earns its place when your final is genuinely heavier, 768p
|
| 558 |
-
at 14 steps.
|
| 559 |
|
| 560 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
|
| 567 |
-
|
|
|
|
|
|
|
| 568 |
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 573 |
|
| 574 |
-
|
| 575 |
|
| 576 |
-
|
| 577 |
|
| 578 |
-
|
| 579 |
-
`tone_compensate=frame_shift` measures it on the overlap the hop regenerated and
|
| 580 |
-
cancels it, which is why the seams in a corrected chain read as invisible.
|
| 581 |
|
| 582 |
-
|
| 583 |
-
its *own* frames, hands that darker tail to the next hop, and the next hop
|
| 584 |
-
starts from there. Seam correction cannot see this β every individual join is
|
| 585 |
-
exact while the film gets steadily dimmer. The 8Γ15 s reference chain slid from
|
| 586 |
-
luma 46 to 11 across hops 2β6 with every seam already corrected.
|
| 587 |
|
| 588 |
-
|
| 589 |
-
`tone_anchor_ref` chooses which.
|
| 590 |
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
outside ten-run 9-hop study measured that this is false. Before any relay has
|
| 594 |
-
happened:
|
| 595 |
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 601 |
|
| 602 |
-
|
| 603 |
-
|
|
|
|
| 604 |
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
|
|
|
|
|
|
| 612 |
|
| 613 |
-
|
|
|
|
|
|
|
| 614 |
|
| 615 |
-
|
| 616 |
-
still matches the previous hop's last frame exactly and the seam stays as
|
| 617 |
-
clean as frame_shift left it;
|
| 618 |
-
- it is **capped** per hop (`tone_anchor`, default 0.35 = about a third of the
|
| 619 |
-
gap), so a long slide is corrected over several hops instead of one hop
|
| 620 |
-
snapping back and pumping.
|
| 621 |
|
| 622 |
-
|
| 623 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 624 |
|
| 625 |
```json
|
| 626 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 627 |
```
|
| 628 |
|
| 629 |
-
`
|
| 630 |
-
|
| 631 |
-
|
|
|
|
| 632 |
|
| 633 |
-
|
|
|
|
|
|
|
| 634 |
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
|
|
|
| 638 |
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
| 0.60 | 2.9 | 2.6 |
|
| 645 |
|
| 646 |
-
|
| 647 |
-
|
|
|
|
|
|
|
|
|
|
| 648 |
|
| 649 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
|
| 651 |
-
|
| 652 |
-
`0.15` pulls it *tighter* than the uncorrected chain, and it grows from there at
|
| 653 |
-
roughly 0.6/255 per step of strength. Hop 1 is byte-identical in all four.
|
| 654 |
-
**0.35 is the default and stays** β it halves the drift while every seam still
|
| 655 |
-
reads as marginal or better.
|
| 656 |
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
anchor will flatten it. That is what the per-shot `tone` field is for.
|
| 660 |
|
| 661 |
-
|
|
|
|
|
|
|
|
|
|
| 662 |
|
| 663 |
-
|
|
|
|
|
|
|
| 664 |
|
| 665 |
-
|
|
|
|
|
|
|
| 666 |
|
| 667 |
-
**
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
|
| 669 |
-
|
| 670 |
|
| 671 |
-
|
|
|
|
| 672 |
|
| 673 |
-
`
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
`
|
| 677 |
-
|
| 678 |
-
`motion_context` is a latent join, no decode/re-encode. `addguide` re-encodes
|
| 679 |
-
decoded pixels, which is itself a VAE round trip. Switching re-renders hops 2+
|
| 680 |
-
and leaves hop 1 on disk. A restart hop pins nothing.
|
| 681 |
|
| 682 |
-
|
|
|
|
| 683 |
|
| 684 |
-
|
| 685 |
-
|---|---|---|
|
| 686 |
-
| `audio_pin_frames` | `24` | Audio context handed to the pin, in frames. 24 is one second on the model's 40 Hz grid. Longer audio context costs conditioning rows but **no delivered frames**, so it is the cheap lever on speech that breaks across a join β try `96` (4 s) for continuous dialogue. |
|
| 687 |
-
| `pin_renorm` | `off` | Rescales each pinned latent back toward the first pinned hop's, to fight the texture ratchet β measured at +4.2% mid-band per join, flat inside each hop. Both modes are scalar rescales, so neither moves structure or can blur detail, and video and audio are corrected separately because their statistics drift independently. **`band`** matches the high-band fraction, which is the statistic the ratchet actually moves β a 12.74% band drift went to β0.04% under it. **`sigma`** matches total spread: it is the original lever, kept for old workflows (saved as `on` before 0.5) and measurably the wrong statistic β total sigma *falls* across a chain whose picture is baking, so it corrects the wrong way and left that same 12.74% drift unchanged. Use `band` for 3+ hops. |
|
| 688 |
-
| `pin_noise` | `0.0` | Mixes seeded noise into the pin β the other half of the same fix. Small values only; measured gains reverse above `0.10`, which is why the range stops there. |
|
| 689 |
|
| 690 |
-
|
|
|
|
|
|
|
|
|
|
| 691 |
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 698 |
|
| 699 |
-
`
|
| 700 |
|
| 701 |
-
##
|
| 702 |
|
| 703 |
-
`
|
| 704 |
-
|
| 705 |
-
|
|
|
|
|
|
|
|
|
|
| 706 |
|
| 707 |
-
|
| 708 |
-
`anchor=restart`. That restart opens on the same photograph, so both sides of
|
| 709 |
-
the cut meet on one image and it reads as a match cut rather than a jump β
|
| 710 |
-
hop 3 used to end tight and smiling, hop 4 open wide and neutral; with the
|
| 711 |
-
guide, hop 3 *arrives* at the still's framing. Measured: the four hop endings
|
| 712 |
-
of a 4-hop chain converge to 3.8/255 of each other against 39.1/255 unguided,
|
| 713 |
-
while mid-hop frames stay as varied as ever (65.8 against 61.1). Two people
|
| 714 |
-
watched it in motion and could not see the convergence.
|
| 715 |
-
|
| 716 |
-
**`still` is the "I know what I am doing" setting.** It plants the photograph
|
| 717 |
-
on every hop, unconditionally. A shot authored `framing: close` plays as a
|
| 718 |
-
close-up for six seconds and then snaps to the still's wider framing in about
|
| 719 |
-
0.6 s at its ending. Then the next hop pushes back in. Then snaps again. A
|
| 720 |
-
user watching this described it, unprompted, as "the camera kept cutting in
|
| 721 |
-
and out." Frame-by-frame from that run, hop 3, `framing: close`:
|
| 722 |
|
| 723 |
-
|
|
|
|
|
|
|
|
|
|
| 724 |
|
| 725 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 726 |
|
| 727 |
-
## Defaults
|
| 728 |
|
| 729 |
| | |
|
| 730 |
|---|---|
|
|
@@ -738,60 +686,134 @@ Safe when no shot authors a framing; visibly wrong when they do.
|
|
| 738 |
| cache budget | 20 GB |
|
| 739 |
| tone_compensate | off (both shipped workflows set `frame_shift`) |
|
| 740 |
| tone_anchor | 0.35, used only by `tone_compensate=anchor` |
|
| 741 |
-
| tone_anchor_ref | `hop1`
|
| 742 |
| pin_mech | `auto` |
|
| 743 |
| last_frame_guide | `off`. Recommended `before_restart` when the plan has a restart |
|
| 744 |
-
| master_audio_file | empty (generated voice)
|
| 745 |
| quality | final |
|
| 746 |
|
| 747 |
-
Three shots at 10 s with a 0.9 s overlap is about 28 s of master
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
The **MEDIA** strip takes a first frame, up to three look reference clips, up
|
| 752 |
-
to three voice references, a music bed, and an optional **master audio** take
|
| 753 |
-
every hop lip-syncs to β all files under `input/h3_refs`, all picked in the
|
| 754 |
-
panel, none of them a `Load Image` node you have to wire. Empty master audio
|
| 755 |
-
is off and leaves generated voice as before.
|
| 756 |
-
|
| 757 |
-

|
| 758 |
-
|
| 759 |
-
*A reference clip with its in/out scrubber, the description that tells the encoder what the clip is **for**, and `video input size` -- which decodes the plate straight to the size you pick instead of loading it at source resolution first.*
|
| 760 |
-
|
| 761 |
-
Each audio slot has a scrubber with an in/out window, and it is worth using.
|
| 762 |
-
H3 encodes the **whole** voice file into the conditioning with no cap, and every
|
| 763 |
-
latent frame of it is attended over on every step of every hop β so a
|
| 764 |
-
three-minute take is a large invisible tax on a clip that only needed four
|
| 765 |
-
seconds. The soundtrack window is cut from the track first, then `music_fit`
|
| 766 |
-
loops or trims that to the chain, which is what stops a mastered track always
|
| 767 |
-
starting the chain on its intro.
|
| 768 |
-
|
| 769 |
-
An end of `0` always means *to the end of the file*, so a longer replacement
|
| 770 |
-
file still plays out rather than being silently cropped to the old one.
|
| 771 |
-
|
| 772 |
-
## Limits
|
| 773 |
-
|
| 774 |
-
- **A workflow saved before 2026-08-28 loses its reference pictures.** The old `ref_image_N` sockets carried tensors, so there is no filename to recover. The rail names each affected ref and asks you to pick its picture; nothing else about the plan is lost.
|
| 775 |
-
- Shot-level `refs` is a choice, default omitted. A ref still activates itself through its own `shots` list; the shot field overrides that list for one hop, including the empty list (no stills). Unknown tags fail on the queue.
|
| 776 |
-
- The reference `desc` and subject `locked` text go to the encoder verbatim, every hop. At cfg 1.0 there is no negative branch, so a detail that is not in the photograph is **asked for**, not ignored. Describe what you actually wired.
|
| 777 |
-
- `HTCContinuityState` is **setting only**. Characters live in the reference register; the node's `characters_*` fields were removed because filling in both injected identity text twice.
|
| 778 |
-
- Each join hard-cuts video but crossfades audio ~40 ms, so A/V drifts ~40 ms per hop.
|
| 779 |
-
- Texture still ratchets on long chains. Stay around 3β5 hops until that is handled.
|
| 780 |
-
- A 22-frame pin clip is ~2 Qwen frames at 2 fps. It is a live-state hint, not a full previous-clip watch.
|
| 781 |
-
- A longer overlap does not fix continuity β it can pin whatever content happens to be in that longer tail.
|
| 782 |
-
|
| 783 |
-
## Nodes
|
| 784 |
-
|
| 785 |
-
**H3 Ref2VA Chain** β `images`, `audio`, `info`, `contact_sheet` out. Wire `CreateVideo` + `SaveVideo` as in the example workflow, `info` to a Preview Text node, and `contact_sheet` to a Save Image.
|
| 786 |
-
|
| 787 |
-
**H3 Chain Preview** β a passthrough panel for the IMAGE (and optionally AUDIO) wire, placed **between the chain and `CreateVideo`**. Images and audio come out unchanged, so adding or removing it changes no pixels. It shows the live sample, the seam (the previous hop's last frame beside this hop's first), a chain-wide progress bar, cache hit / seed / steps per hop, **which pin mechanism each hop actually used** β a latent Motion-Context pin or the AddGuide pixel fallback β and end-of-run A/V drift. Drag the grip to resize the stats panel; double-click it to reset.
|
| 788 |
|
| 789 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 790 |
|
| 791 |
-
|
| 792 |
|
| 793 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 794 |
|
| 795 |
-
|
| 796 |
|
| 797 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
# Hand Tie Clips
|
| 15 |
|
| 16 |
+
**Multi-hop MiniMax H3 video from one node.** Write a shot plan, drop in your
|
| 17 |
+
reference stills, queue. The pack's whole job is the *join* β the place where
|
| 18 |
+
one generation ends and the next begins.
|
| 19 |
|
| 20 |

|
| 21 |
|
|
|
|
| 23 |
is the last frame the first generation produced and the next is the first frame
|
| 24 |
of a second, separate generation. The pack exists so that you cannot tell which.*
|
| 25 |
|
| 26 |
+
Each hop is native **MiniMax H3 Reference-to-Video**. Hops after the first are
|
| 27 |
+
guided by the previous hop's sampler AV latent through
|
| 28 |
+
[ComfyUI-H3-Motion-Context](https://github.com/NikoDemon80/ComfyUI-H3-Motion-Context)
|
| 29 |
+
when it is installed; stock `MiniMaxH3AddGuide` is the fallback, and the console
|
| 30 |
+
says which one each hop took.
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## What it gives you
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
| | |
|
| 37 |
|---|---|
|
| 38 |
+
| **One node, five tabs** | SCRIPT, REFS, MEDIA, WRITE and SWAP on the node body. No `Load Image` chains, no JSON in a textarea. |
|
| 39 |
+
| **A shot plan** | One card per hop β the shot count *is* the hop count. Beat, duration, seed, and five directive axes compiled to vetted prose. |
|
| 40 |
+
| **A reference register** | Stable `@tags` for your stills, grouped per subject, each with a retention rule and its own pixel budget. Pulling one out of the middle no longer renumbers `<Picture N>`. |
|
| 41 |
+
| **WRITE** | A plan writer on the node. Point it at any OpenAI-compatible server, describe the scene in a sentence, and it fills the script *and* the reference rows β reading the pictures you already dropped in. |
|
| 42 |
+
| **SWAP** | One-hop identity swap from a reference clip. Four named modes, so what *stays* with the clip is stated rather than left to omission. |
|
| 43 |
+
| **A lossless hop cache** | Re-roll shot 5 of 8 and only 5β8 re-render. Resume after a crash. Hold about one hop in RAM instead of the whole film. |
|
| 44 |
+
| **A lip-sync lock** | `master_audio_file` β one continuous take every hop locks to, delivered as a passthrough. |
|
| 45 |
+
| **Instruments** | Dry run, contact sheet, seam report and a live preview panel, so you can find the hop that broke without scrubbing the file. |
|
| 46 |
|
| 47 |
+
---
|
| 48 |
|
| 49 |
## Install
|
| 50 |
|
|
|
|
|
|
|
| 51 |
From your `ComfyUI/custom_nodes/` folder:
|
| 52 |
|
| 53 |
```
|
|
|
|
| 54 |
git clone https://github.com/dntpi/ComfyUI-Hand-Tie-Clips.git
|
| 55 |
```
|
| 56 |
|
| 57 |
+
Or from the HuggingFace mirror, which is the same repository:
|
| 58 |
|
| 59 |
```
|
| 60 |
git clone https://huggingface.co/sandpies/ComfyUI-Hand-Tie-Clips
|
| 61 |
```
|
| 62 |
|
| 63 |
+
Cloning puts the folder at the right depth, which is the mistake the zip route
|
| 64 |
+
invites β one level too deep
|
| 65 |
+
(`custom_nodes/ComfyUI-Hand-Tie-Clips/ComfyUI-Hand-Tie-Clips/`) and the pack
|
| 66 |
+
simply will not appear. Updating later is `git pull` and a restart.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
+
**No dependencies to install.** Everything it imports β `torch`, `numpy`,
|
| 69 |
+
`PIL`, `av`, `aiohttp` β already ships with ComfyUI, which is why
|
| 70 |
+
`dependencies` in `pyproject.toml` is empty.
|
| 71 |
|
| 72 |
+
Then **restart ComfyUI** (Python changed) and **hard-refresh the browser**
|
| 73 |
+
(Ctrl+Shift+R). The editor is served from
|
| 74 |
+
`/extensions/ComfyUI-Hand-Tie-Clips/`, and a stale cache is the single most
|
| 75 |
+
common reason the node mounts with no UI on a fresh install.
|
| 76 |
|
| 77 |
It is installed correctly when all three are true:
|
| 78 |
|
| 79 |
- the startup log carries a line beginning `[HandTieClips]`
|
| 80 |
- the browser console says `[HandTieClips] editor ui v2.0.0 loaded`
|
| 81 |
+
- node search shows a **Hand Tie Clips** category with five nodes, each once
|
|
|
|
|
|
|
| 82 |
|
| 83 |
+
Workflows saved before the 2026-08-29 rename keep loading β the old ids are
|
| 84 |
+
registered as deprecated aliases. Nothing needs migrating.
|
| 85 |
|
| 86 |
## Needs
|
| 87 |
|
| 88 |
- ComfyUI new enough to include **Add Guide for MiniMax H3** (`MiniMaxH3AddGuide`)
|
| 89 |
- A **ref2va** (or hybrid ref2va) checkpoint β fl2va has no reference rows
|
| 90 |
- Video VAE + audio VAE + MiniMax text encoder
|
| 91 |
+
- **Recommended:** [ComfyUI-H3-Motion-Context](https://github.com/NikoDemon80/ComfyUI-H3-Motion-Context)
|
| 92 |
+
for the latent join. Without it the node falls back to `MiniMaxH3AddGuide`,
|
| 93 |
+
which still works and still chains β it is a different join, so a seam you
|
| 94 |
+
are comparing against someone else's render may not be the same code path.
|
| 95 |
|
| 96 |
+
The two shipped workflows wire the **turbo stack** this node is actually run
|
| 97 |
+
with, because an example without it is not the graph anyone uses:
|
| 98 |
|
| 99 |
```
|
| 100 |
UNETLoader -> LoRA Loader Stack -> H3 AdaLN LoRA Fix -> MiniMax H3 Low VRAM
|
|
|
|
| 106 |
| [ComfyUI-PlagueKind-Nodes](https://github.com/PlagueKind/ComfyUI-PlagueKind-Nodes) | LoRA Loader Stack, H3 AdaLN LoRA Fix, H3 SLA Attention |
|
| 107 |
| [ComfyUI-KJNodes](https://github.com/kijai/ComfyUI-KJNodes) | MiniMax H3 Low VRAM Attention (experimental), Model Preview Override |
|
| 108 |
|
| 109 |
+
**CLIP reaches the node from the LoRA loader, not from the encoder** β that is
|
| 110 |
+
what makes the text half of every LoRA land. Do not rewire it back.
|
| 111 |
+
|
| 112 |
+
None of it is required by the node itself. Missing a pack, its nodes load as
|
| 113 |
+
red boxes: delete them, wire the loader straight into `model` and the encoder
|
| 114 |
+
into `clip`, and raise `steps` from 7 to around 20.
|
| 115 |
+
|
| 116 |
+
Both shipped workflows are saved pointing at the exact quantised files they
|
| 117 |
+
were rendered with. These are **not** the only builds that work β any ref2va or
|
| 118 |
+
hybrid ref2va set will do. Repoint the loaders at what you have; a loader
|
| 119 |
+
showing an empty or red filename is naming a file you do not have, not a broken
|
| 120 |
+
workflow.
|
| 121 |
|
| 122 |
+
<details>
|
| 123 |
+
<summary>The exact files the examples name</summary>
|
| 124 |
|
| 125 |
+
| loader | file |
|
| 126 |
|---|---|
|
| 127 |
| `UNETLoader` | `minimax_h3_hybrid_fl2va_ref2va_b30-49-int8.safetensors` |
|
| 128 |
| `CLIPLoader` | `qwen3vl_32b_minimax_h3_int8_convrot.safetensors` |
|
|
|
|
| 131 |
| LoRA Loader Stack | `minimax_h3_ref2v_turbo_4step_v0.1_comfyui_bf16.safetensors` |
|
| 132 |
| `tiny_vae` | `taeh3.safetensors` (or `none`) |
|
| 133 |
|
| 134 |
+
</details>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
+
## Start here
|
| 139 |
+
|
| 140 |
+
1. Load **`workflows/HandTieClips_Starter.json`** β two hops, no references,
|
| 141 |
+
runs as soon as the loaders point at your files. It carries a **READ ME**
|
| 142 |
+
board on its own canvas, to the left of the loaders: the laws, the directive
|
| 143 |
+
table, the reference rules and the failure table, where you need them rather
|
| 144 |
+
than in another file.
|
| 145 |
+
2. Point the loaders at your ref2va DiT, encoder, and both VAEs.
|
| 146 |
+
3. In **REFS**, add a row per identity still and **drop a picture onto its
|
| 147 |
+
thumbnail**. Give it a `@tag`; group photos of the same person under one
|
| 148 |
+
subject number. Files land in `ComfyUI/input/h3_refs` β there is nothing to
|
| 149 |
+
wire.
|
| 150 |
+
4. In **SCRIPT**, write one card per hop. Shot 1 is the whole opening; every
|
| 151 |
+
later card is **only the new beat**.
|
| 152 |
+
5. Optional: a first frame, up to three reference clips, up to three voices, a
|
| 153 |
+
music bed, a master audio take β all in **MEDIA**.
|
| 154 |
+
6. Queue. Wire **`info`** to a Preview Text node to read the fully assembled
|
| 155 |
+
prompt for every hop.
|
| 156 |
+
|
| 157 |
+
`workflows/HandTieClips_Showcase.json` is the six-hop version and needs three
|
| 158 |
+
pictures of your own. Both carry the turbo stack above.
|
| 159 |
+
|
| 160 |
+

|
| 161 |
+
|
| 162 |
+
*Each tab carries its count, and RUN stays pinned below them. SCRIPT is one
|
| 163 |
+
card per hop: the beat, its duration, and its own join, camera, framing, pace
|
| 164 |
+
and tail, with `lock` and the range button on the card header.*
|
| 165 |
+
|
| 166 |
+
Under the script sits **RUN**, collapsed, holding everything that is not
|
| 167 |
+
per-shot: output size and length, sampling, the join and pin controls
|
| 168 |
+
(`pin_mech`, `last_frame_guide`, tone), and the hop cache. Its title line
|
| 169 |
+
summarises the run β `1344x768 Β· 10s Γ3 Β· 14 steps res_multistep Β· cache off` β
|
| 170 |
+
so you can read the setup without opening it.
|
| 171 |
+
|
| 172 |
+
Two script modes: **Simple** (one prompt box and a hop count, the way it always
|
| 173 |
+
worked) and **Shots** (one card per hop with directives; switching from Simple
|
| 174 |
+
carries your prompt over and splits it on `---`). Only one is on screen at a
|
| 175 |
+
time, so no text box is ever quietly doing nothing.
|
| 176 |
+
|
| 177 |
+
Keep later-hop beats on **what happens next**. Do not re-describe the face; the
|
| 178 |
+
photos, the register and the pin already carry it.
|
| 179 |
|
| 180 |
+
---
|
| 181 |
|
| 182 |
+
## The one law
|
|
|
|
|
|
|
| 183 |
|
| 184 |
+
Sampling runs at **cfg 1.0 with no negative branch**, so the prompt is purely
|
| 185 |
+
additive: **anything you name is added, and nothing can be removed by
|
| 186 |
+
mentioning it.** Almost every rule in this pack is a consequence, and each of
|
| 187 |
+
the rules below was confirmed against renders rather than reasoned from code.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
**Never name the thing you want to end.** `The cook stops talking` keeps her
|
| 190 |
+
talking. Write the state you want as a pose plus a sound:
|
| 191 |
|
| 192 |
+
> The cook leans back against the counter with her lips closed, and lets her
|
| 193 |
+
> eyes move slowly across the room. The kitchen is quiet apart from the hum of
|
| 194 |
+
> the refrigerator.
|
|
|
|
|
|
|
| 195 |
|
| 196 |
+
H3 generates audio for the whole hop no matter what, so silence has to be
|
| 197 |
+
written as a sound. Written as an absence, you get speech. The ban is on the
|
| 198 |
+
idea, not on a word list: *fades, subsides, dies down* all name an ending as
|
| 199 |
+
surely as *stops* does.
|
| 200 |
|
| 201 |
+
**Point the action line at the reference you want used.** Describing a
|
| 202 |
+
reference in the register is not enough β the beat is what drives the frame.
|
| 203 |
+
Write the tag into the action: `The cook stands at the counter in @kitchen,
|
| 204 |
+
looks upβ¦`. Phrase a place as depicted, not as a container to be placed inside,
|
| 205 |
+
or you can get a literal composite of the photograph.
|
| 206 |
|
| 207 |
+
**Put a state change at the end of the previous shot.** Every hop after the
|
| 208 |
+
first opens by holding the frames it was handed, and the audio pin carries the
|
| 209 |
+
previous hop's tail across the join. If shot 2 ends mid-sentence, no wording in
|
| 210 |
+
shot 3 will make it start quiet β arrive there before the previous shot ends.
|
| 211 |
+
|
| 212 |
+
**Pick ambience that is narrowband and specific.** "faint street noise through
|
| 213 |
+
the window" is broadband and renders as a five-second hiss; "the low hum of the
|
| 214 |
+
refrigerator" does not.
|
| 215 |
|
| 216 |
+
**Set `tail` on your last shot.** Left on `ongoing`, the model is told action is
|
| 217 |
+
still underway at the final frame and will invent something to satisfy it β a
|
| 218 |
+
stray gesture, or a stray line of dialogue in the closing second.
|
| 219 |
|
| 220 |
+
> The full authoring guide is **[PROMPTING.md](PROMPTING.md)**. Its rules come
|
| 221 |
+
> from what this model actually does, not from taste. `prompt_pack/` is the
|
| 222 |
+
> same writer as a copy-paste prompt, for when you would rather work in a chat
|
| 223 |
+
> window.
|
|
|
|
|
|
|
| 224 |
|
| 225 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
## Let a model write the plan
|
| 228 |
|
| 229 |
+
Open **WRITE**, point it at any OpenAI-compatible server, say what you want in
|
| 230 |
+
one sentence, and press **Write plan**. It fills the SCRIPT cards and the
|
| 231 |
+
REFERENCES rows together β and the pictures already on those rows go with the
|
| 232 |
+
request, so the model describes what it is actually looking at rather than
|
| 233 |
+
guessing from a filename.
|
| 234 |
|
| 235 |

|
| 236 |
|
| 237 |
+
- **Context 32768.** The system prompt alone is ~6,000 tokens, the reply
|
| 238 |
+
another 1,000β2,000, and every reference picture costs ~260 on top.
|
| 239 |
- **Reasoning off.** Thinking tokens come out of the same budget; a reply that
|
| 240 |
stops before the JSON closes is the tell.
|
| 241 |
- **Temperature 0.3.** Higher and the JSON grows trailing commas and smart quotes.
|
| 242 |
|
| 243 |
+
Server settings are saved on this machine only β a shared `.json` never points
|
| 244 |
+
at your server.
|
| 245 |
|
| 246 |
**Treat what comes back as a strong draft, not a finished plan.** It gets the
|
| 247 |
structure right β hop count, join types, which reference rides which hop, a
|
| 248 |
+
sound bed on every quiet hop β which is the part that is tedious by hand. Read
|
| 249 |
+
two things every time: each reference's `desc`, which can be confidently wrong
|
| 250 |
+
about its own photograph, and the spoken words in every beat. The node lints
|
| 251 |
+
both and prints what it finds before you render.
|
| 252 |
|
| 253 |
+
---
|
| 254 |
|
| 255 |
+
## SWAP
|
| 256 |
|
| 257 |
+
A **one-hop identity swap** from a reference clip: the clip supplies the motion
|
| 258 |
+
and the scene, a still from your rail supplies the person. Trim the clip in
|
| 259 |
+
MEDIA, pick an identity, press **Write**, then **Accept** β which writes exactly
|
| 260 |
+
one shot and the clip's description, and never touches `ref_plan`. Your
|
| 261 |
+
register is not rewritten.
|
| 262 |
|
| 263 |
+
Four modes, because at cfg 1.0 **a mode that merely omits the swap line does
|
| 264 |
+
not keep the clip's person** β the identity still is in front of the encoder
|
| 265 |
+
either way and governs the subject anyway. Each mode states positively what
|
| 266 |
+
stays:
|
| 267 |
|
| 268 |
+
| mode | what the photograph contributes |
|
| 269 |
+
|---|---|
|
| 270 |
+
| `replace_person` | Face, build, hairstyle **and** wardrobe. |
|
| 271 |
+
| `head_swap` | Face, hair and skin tone. The body stays with the clip: build, posture, hands and every garment. |
|
| 272 |
+
| `face_only` | Facial features only. Hair, ears, expression, build and clothes stay with the clip. |
|
| 273 |
+
| `keep_person` | Nobody is swapped; the clip is a scene and motion plate. The identity picker greys out. |
|
| 274 |
+
|
| 275 |
+
Alongside them: **background** (from the clip / from a picture `@tag` / free)
|
| 276 |
+
and an optional **wardrobe plate** β a `@tag` whose garment is *worn*, draping
|
| 277 |
+
on the body in frame and creasing where it bends, not pasted.
|
| 278 |
+
|
| 279 |
+
**Two things to get right, both of which cost renders to find out:**
|
| 280 |
+
|
| 281 |
+
- **Do not run MEDIA's describe on the clip before a swap.** That caption
|
| 282 |
+
reaches the encoder as what `<Video 1>` *is*, and β being additive β a
|
| 283 |
+
caption naming a person asks for the person you are about to replace. Four
|
| 284 |
+
consecutive "head swap doesn't work" reports came down to that, a missing
|
| 285 |
+
frame sequence and a weak citation; none of them was broken code. SWAP now
|
| 286 |
+
warns when a caption names somebody.
|
| 287 |
+
- **Drop the clip to ~0.3 MP.** A reference clip's decode area is its token
|
| 288 |
+
count, and its token count is its influence. A full-size plate out-argues a
|
| 289 |
+
single photograph.
|
| 290 |
|
| 291 |
+
---
|
|
|
|
|
|
|
|
|
|
| 292 |
|
| 293 |
+
## Tested in public
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
+
Before 2.0 shipped, a tester ran **ten controlled nine-hop chains** β 65 s each,
|
| 296 |
+
one variable per run, same model, LoRA, references, locked audio and seed β and
|
| 297 |
+
measured them end to end with her own instruments rather than by eye. The
|
| 298 |
+
results below are hers, used with permission.
|
| 299 |
|
| 300 |
+

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
+
Her headline: **`anchor: "restart"` won.** It is the only run type whose last
|
| 303 |
+
ten seconds is still on the reference still's side of its own hop 1 β colour,
|
| 304 |
+
texture, background and framing all hold. The cost is that a restart is a hard
|
| 305 |
+
cut. Second best was a small `pin_noise`, one run each way. **Nothing else moved
|
| 306 |
+
the needle** β not `pin_renorm=band`, not reference protection, not a different
|
| 307 |
+
DiT, not `ref_image_size=match`, not a fresh seed β and the plain control was
|
| 308 |
+
the worst of the ten.
|
| 309 |
|
| 310 |
+

|
| 311 |
|
| 312 |
+
Her diagnosis is sharper than ours was: **relay convergence with no content
|
| 313 |
+
anchor.** Each hop inherits its predecessor's end state and nothing pulls it
|
| 314 |
+
back toward the reference. It also explains why the scale knobs did nothing β
|
| 315 |
+
the pin's *statistics* never drifted (sigma stayed within Β±6% across nine hops)
|
| 316 |
+
while the picture lost a fifth of its chroma and doubled its background edges.
|
| 317 |
+
The drift is in the latent's **content**, not its scale, and `pin_renorm` and
|
| 318 |
+
`pin_noise` only rescale.
|
| 319 |
|
| 320 |
+
Three things in 2.0 come straight from that study:
|
| 321 |
|
| 322 |
+
- **Restart hops write their full length.** They used to drop 0.9 s of new
|
| 323 |
+
content as though they were continuations.
|
| 324 |
+
- **`last_frame_guide=before_restart`**, so both sides of a restart cut meet on
|
| 325 |
+
the same photograph and it reads as a match cut rather than a jump.
|
| 326 |
+
- **`tone_anchor_ref=still`.** The anchor used to hold hop 1, on the reasoning
|
| 327 |
+
that hop 1 is the one tone in the chain nothing has drifted into yet. Her
|
| 328 |
+
measurements say that is false β hop 1 is the *first casualty*, already short
|
| 329 |
+
of the still before any relay has happened:
|
| 330 |
|
| 331 |
+
| | reference still | hop 1 |
|
| 332 |
+
|---|---|---|
|
| 333 |
+
| chroma | 33.6 | 30 |
|
| 334 |
+
| b\* (warmth) | 26.6 | 22 |
|
| 335 |
+
| fine detail | 1.00 | 0.72β0.99 |
|
| 336 |
|
| 337 |
+
A chain anchored on hop 1 converges on a target that already fell short.
|
| 338 |
|
| 339 |
+
**What her study could not fix, this release does not claim to fix.** Texture
|
| 340 |
+
still ratchets on long chains, and 3β5 hops is still the honest limit.
|
| 341 |
|
| 342 |
+
---
|
| 343 |
|
| 344 |
+
## Holding a chain together
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
|
| 346 |
+
### The join
|
| 347 |
|
| 348 |
+
`pin_mech` chooses it. `auto` (default) is Motion-Context when the pack is
|
| 349 |
+
installed, the overlap has a matching `context_length`, and the previous hop
|
| 350 |
+
left a sampler latent; AddGuide pixels otherwise. Forcing `motion_context` or
|
| 351 |
+
`addguide` does **not** fall back β it fails with the reason, because a lever
|
| 352 |
+
that silently becomes the other setting cannot be compared against it.
|
| 353 |
+
`motion_context` is a latent join with no decode/re-encode; `addguide`
|
| 354 |
+
re-encodes decoded pixels, which is itself a VAE round trip. Switching
|
| 355 |
+
re-renders hops 2+ and leaves hop 1 on disk. A restart hop pins nothing.
|
| 356 |
|
| 357 |
+
<details>
|
| 358 |
+
<summary>Three more pin dials, all defaulting to their pre-existing behaviour</summary>
|
| 359 |
|
| 360 |
+
| widget | default | what it does |
|
| 361 |
+
|---|---|---|
|
| 362 |
+
| `audio_pin_frames` | `24` | Audio context handed to the pin, in frames; 24 is one second on the model's 40 Hz grid. Longer costs conditioning rows but **no delivered frames**, so it is the cheap lever on speech that breaks across a join β try `96` (4 s) for continuous dialogue. |
|
| 363 |
+
| `pin_renorm` | `off` | Rescales each pinned latent back toward the first pinned hop's, against the texture ratchet β measured at +4.2% mid-band per join, flat inside each hop. Both modes are scalar rescales, so neither moves structure or can blur detail, and video and audio are corrected separately. **`band`** matches the high-band fraction, which is the statistic the ratchet actually moves: a 12.74% drift went to β0.04%. **`sigma`** is the original lever, kept for old workflows and measurably the wrong statistic β total sigma *falls* across a chain whose picture is baking, so it corrects the wrong way. Use `band` for 3+ hops. |
|
| 364 |
+
| `pin_noise` | `0.0` | Mixes seeded noise into the pin β the other half of the same fix. Small values only; measured gains reverse above `0.10`, which is where the range stops. |
|
| 365 |
|
| 366 |
+
`pin_to_qwen` separately shows the incoming state to the *text* encoder: `off`,
|
| 367 |
+
`last frame` (default β the previous hop's last frame becomes `<Picture 1>`, and
|
| 368 |
+
identity stills shift to Picture 2+), `pin clip` (overlap frames as an extra
|
| 369 |
+
`<Video>` at ~2 fps, no soundtrack), or `both`. `@tags` in beats resolve per
|
| 370 |
+
hop, so stills shifting never breaks prose; a literal `<Picture N>` in a hop 2+
|
| 371 |
+
beat would.
|
| 372 |
|
| 373 |
+
</details>
|
| 374 |
|
| 375 |
+
### Brightness drift β two different problems
|
| 376 |
|
| 377 |
+
**The step at a join** is the denoiser's tone bias on a fresh generation.
|
| 378 |
+
`tone_compensate=frame_shift` measures it on the overlap the hop regenerated and
|
| 379 |
+
cancels it, which is why the seams in a corrected chain read as invisible.
|
| 380 |
+
Measured on a 3-hop render: chain drift 5.6/255 without it, 0.3/255 with it.
|
| 381 |
+
All three modes remove the drift equally well, but `gain_bias` and `lut` pair
|
| 382 |
+
pixels between a frame and its *regeneration*, fitting a slope that is not
|
| 383 |
+
really there; `frame_shift` uses frame averages only, so it can shift but never
|
| 384 |
+
distort.
|
| 385 |
|
| 386 |
+
**The slide across a whole chain** is different. Each hop also darkens across
|
| 387 |
+
its *own* frames, hands that darker tail to the next hop, and the next hop
|
| 388 |
+
starts from there. Seam correction cannot see this β every individual join is
|
| 389 |
+
exact while the film gets steadily dimmer. An 8Γ15 s chain slid from luma 46 to
|
| 390 |
+
11 across hops 2β6 with every seam already corrected.
|
| 391 |
+
|
| 392 |
+
`tone_compensate=anchor` is frame_shift **plus** a pull back toward a target,
|
| 393 |
+
matched in **Lab** β L\*, a\*, b\* and L\* spread β because chroma loss is the
|
| 394 |
+
largest measured drift and a per-channel RGB mean cannot restore it.
|
| 395 |
+
`tone_anchor_ref` picks the target: `hop1` (default) or **`still`**, which holds
|
| 396 |
+
the photograph and is the only setting that ever closes the 33.6-against-30 gap
|
| 397 |
+
above. `still` needs `start_image_file`, and under the Motion-Context join the
|
| 398 |
+
correction still only reaches the delivered frames, not the next hop's pin β
|
| 399 |
+
`pin_mech=addguide` is what closes that loop.
|
| 400 |
+
|
| 401 |
+
Two things keep the pull from causing the problem it is fixing: it **ramps in**
|
| 402 |
+
across the first two seconds of each hop, so frame 0 still matches the previous
|
| 403 |
+
hop's last frame exactly and the seam stays as clean as frame_shift left it;
|
| 404 |
+
and it is **capped** per hop (`tone_anchor`, default 0.35 β a third of the gap),
|
| 405 |
+
so a long slide is corrected over several hops instead of one hop snapping back
|
| 406 |
+
and pumping.
|
| 407 |
|
| 408 |
+

|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
+
| `tone_anchor` | drift across the chain | worst seam |
|
| 411 |
+
|---|---|---|
|
| 412 |
+
| off | 13.5/255 | 2.1/255 |
|
| 413 |
+
| 0.15 | 7.4 | 1.3 |
|
| 414 |
+
| 0.35 | 5.1 | 1.9 |
|
| 415 |
+
| 0.60 | 2.9 | 2.6 |
|
| 416 |
|
| 417 |
+
Both columns are **H3 Seam Report's own numbers**, so what you measure matches
|
| 418 |
+
what this table says. Drift falls evenly β 45%, 62%, 78% of the uncorrected
|
| 419 |
+
slide. The seam does not: `0.15` pulls it *tighter* than the uncorrected chain
|
| 420 |
+
and it grows from there. Hop 1 is byte-identical in all four. **0.35 is the
|
| 421 |
+
default and stays** β it halves the drift while every seam still reads as
|
| 422 |
+
marginal or better. Set `tone_anchor` to 0 for plain frame_shift.
|
| 423 |
+
|
| 424 |
+
A scene that is *meant* to get darker looks exactly like drift from the inside,
|
| 425 |
+
so a shot can opt out: `"tone": "free"` skips the pull for that hop,
|
| 426 |
+
`"tone": "rebase"` also moves the anchor onto it β which is how a scene that is
|
| 427 |
+
genuinely darker from here on stops being fought for the rest of the film.
|
| 428 |
+
|
| 429 |
+
### Restarts, and the last-frame guide
|
| 430 |
+
|
| 431 |
+
`anchor: "restart"` on a shot makes that hop a chain start β the start image is
|
| 432 |
+
frame 0 and nothing is relayed. It is **a cut**, so it belongs where a cut is
|
| 433 |
+
motivated: a pause, a change of thought, a new beat, not on a hop interval.
|
| 434 |
+
Pair it with `join: hard_cut` or `match_cut`; `continuous` is refused. Name the
|
| 435 |
+
room in the beat, because a pin-less hop has nothing else telling it where it
|
| 436 |
+
is. Never on shot 1, and it needs a start image.
|
| 437 |
|
| 438 |
+
`last_frame_guide` plants `start_image` at a hop's last **pixel** frame
|
| 439 |
+
(AddGuide `frame_idx=-1`, not latent T-1). It does not become the next hop's
|
| 440 |
+
frame 0. Needs a start image. Ships `off`.
|
| 441 |
|
| 442 |
+
**Reach for `before_restart`.** It guides only a hop whose *next* shot is a
|
| 443 |
+
restart, so both sides of the cut meet on one image β hop 3 used to end tight
|
| 444 |
+
and smiling, hop 4 open wide and neutral; with the guide, hop 3 *arrives* at the
|
| 445 |
+
still's framing. Measured: the four hop endings of a 4-hop chain converge to
|
| 446 |
+
3.8/255 of each other against 39.1/255 unguided, while mid-hop frames stay as
|
| 447 |
+
varied as ever (65.8 against 61.1). Two people watched it in motion and could
|
| 448 |
+
not see the convergence.
|
| 449 |
+
|
| 450 |
+
**`still` is the "I know what I am doing" setting.** It plants the photograph on
|
| 451 |
+
every hop, unconditionally, which overrides an authored `framing` at every hop
|
| 452 |
+
ending β a shot set `framing: close` plays close for six seconds and then snaps
|
| 453 |
+
to the still's wider framing in about 0.6 s, and the next hop pushes back in. A
|
| 454 |
+
user watching this described it, unprompted, as *"the camera kept cutting in and
|
| 455 |
+
out."* Frame-by-frame from that run, hop 3, `framing: close`:
|
| 456 |
|
| 457 |
+
1.58s close 3.67s close 4.92s close 5.96s close-ish 6.58s WIDE 7.04s WIDE
|
| 458 |
|
| 459 |
+
Safe when no shot authors a framing; visibly wrong when they do.
|
| 460 |
|
| 461 |
+
---
|
|
|
|
|
|
|
| 462 |
|
| 463 |
+
## Reference
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
|
| 465 |
+
### Shot plan
|
|
|
|
| 466 |
|
| 467 |
+
The cards write this; you rarely see it. It is under **JSON** on the node if you
|
| 468 |
+
want to copy a plan between workflows.
|
|
|
|
|
|
|
| 469 |
|
| 470 |
+
```json
|
| 471 |
+
{
|
| 472 |
+
"shots": [
|
| 473 |
+
{
|
| 474 |
+
"beat": "The cook stands at the counter, looks up, and speaks one short line.",
|
| 475 |
+
"directives": {"camera": "hold", "framing": "medium", "pace": "steady", "tail": "ongoing"}
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"beat": "The cook sets the knife down and turns toward the window, still talking.",
|
| 479 |
+
"directives": {"join": "continuous", "camera": "push_in", "framing": "close"}
|
| 480 |
+
}
|
| 481 |
+
]
|
| 482 |
+
}
|
| 483 |
+
```
|
| 484 |
|
| 485 |
+
Shot 1 is the whole opening. Every later shot is **only the new beat** β the
|
| 486 |
+
node supplies the identity lock, the live-frame citation and the join itself.
|
| 487 |
+
Fields, all optional except `beat`:
|
| 488 |
|
| 489 |
+
| | |
|
| 490 |
+
|---|---|
|
| 491 |
+
| `beat` | What happens this hop. |
|
| 492 |
+
| `directives` | The five axes below. |
|
| 493 |
+
| `prose` | Free text appended verbatim, for anything the vocabulary lacks. |
|
| 494 |
+
| `seed`, `steps`, `duration` | Per-shot overrides. `duration` takes the widget's labels (`"8 s"`). |
|
| 495 |
+
| `refs` | Which register stills ride this hop, as tags. Omit for the register default; `[]` is none; a list is those tags only, in that order. Unknown tags fail on the queue. |
|
| 496 |
+
| `anchor` | `"restart"` makes this hop a chain start. See above. |
|
| 497 |
+
| `tone` | `"free"` skips the chain-wide tone pull once; `"rebase"` also moves the anchor. |
|
| 498 |
+
| `locked` | Reuse this shot's cached render even when its inputs changed. Needs `cache_hops=on` and a stable `id`. Not to be confused with `subjects.N.locked`, which is identity text. |
|
| 499 |
+
| `id` | Stable name, used as the cache pointer. Generated if absent. |
|
| 500 |
|
| 501 |
+
**Hops can differ in length.** `duration` is per shot and everything downstream
|
| 502 |
+
sizes itself around it. Labels are the widget's β `5 s`, `7 s`, `8 s`, `10 s`,
|
| 503 |
+
`15 s` β and that set is fixed, not arbitrary: every value has to land on H3's
|
| 504 |
+
frame grid (`n % 17 == 5` at 24 fps), so there is no `6.5 s`. Editing one shot's
|
| 505 |
+
length invalidates that hop and the hops after it, and nothing before it.
|
| 506 |
|
| 507 |
+
**Short hops cut, long hops flow.** Overlap is chain-wide β 0.9 s by default β
|
| 508 |
+
so a 5 s hop asking for `join: continuous` spends a fifth of itself on the
|
| 509 |
+
airlock, and the node prints a note saying so.
|
| 510 |
|
| 511 |
+
### Directives
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
|
| 513 |
+
| axis | options |
|
| 514 |
+
|---|---|
|
| 515 |
+
| `join` | `continuous`, `match_cut`, `hard_cut` β ignored on shot 1, which has nothing to join to |
|
| 516 |
+
| `camera` | `hold`, `pan_follow`, `push_in`, `pull_back`, `orbit`, `handheld` |
|
| 517 |
+
| `framing` | `keep`, `wide`, `medium`, `close` |
|
| 518 |
+
| `pace` | `slow`, `steady`, `brisk` |
|
| 519 |
+
| `tail` | `ongoing` (default), `settle`, `hold` |
|
| 520 |
+
|
| 521 |
+
An unset axis emits nothing rather than asserting a default, so it costs no
|
| 522 |
+
tokens. Everything is phrased affirmatively, for the reason in
|
| 523 |
+
[The one law](#the-one-law).
|
| 524 |
+
|
| 525 |
+
`join=continuous` with a framing change and a held camera warns: with the camera
|
| 526 |
+
still, the only way to reach a new framing is a cut. Earn it on the move, or use
|
| 527 |
+
`framing: keep`. A camera move pointing the opposite way from the framing
|
| 528 |
+
(`push_in` with `wide`, `pull_back` with `close`) warns too. When `continuous`
|
| 529 |
+
and the camera *is* moving, the framing sentence compiles as a **landing** β
|
| 530 |
+
"The move settles into a close shotβ¦" β so it does not fight the pin that still
|
| 531 |
+
holds the previous framing.
|
| 532 |
+
|
| 533 |
+
### Reference register
|
| 534 |
|
| 535 |
```json
|
| 536 |
+
{
|
| 537 |
+
"refs": [
|
| 538 |
+
{"tag": "hero_face", "file": "cook_face.jpg", "subject": 1, "retention": "fully_preserved"},
|
| 539 |
+
{"tag": "hero_outfit", "file": "cook_apron.jpg", "subject": 1, "retention": "partially_copy"},
|
| 540 |
+
{"tag": "kitchen", "file": "kitchen_wide.jpg", "retention": "reference", "mp": 0.3}
|
| 541 |
+
],
|
| 542 |
+
"subjects": {
|
| 543 |
+
"1": {"name": "the cook", "locked": "the same face, the same short dark hair"}
|
| 544 |
+
}
|
| 545 |
+
}
|
| 546 |
```
|
| 547 |
|
| 548 |
+
`file` is a picture in `ComfyUI/input/h3_refs`, set by the rail. `tag` is what
|
| 549 |
+
you write in beats, and the node resolves it to the right `<Picture N>` **per
|
| 550 |
+
hop**, so pulling a still out of the middle no longer breaks every later
|
| 551 |
+
reference.
|
| 552 |
|
| 553 |
+
`subject` groups pictures per person. **This matters:** declaring every picture
|
| 554 |
+
as a photo of `<Subject 1>` makes the model render the *average* of two
|
| 555 |
+
different people.
|
| 556 |
|
| 557 |
+
`retention` says how much of a picture carries over β `fully_preserved` (face
|
| 558 |
+
and bone structure exactly), `partially_copy` (the garment and its cut),
|
| 559 |
+
`reference` (layout, surfaces and light, i.e. a place). Refs with a subject
|
| 560 |
+
default to `fully_preserved`; everything else defaults to `reference`.
|
| 561 |
|
| 562 |
+
`mp` caps one picture's pixel budget in megapixels. It is a **token dial, not a
|
| 563 |
+
quality one**: H3 turns every reference into pixel area Γ· 256 entries and
|
| 564 |
+
attends over all of them on every step of every hop, so a location plate costing
|
| 565 |
+
what a face costs is waste. A 0.3 MP place plate is ~1,170 tokens; a 2 MP
|
| 566 |
+
portrait is ~7,800.
|
|
|
|
| 567 |
|
| 568 |
+
> **The dial is inert at the default.** On `ref_image_size=match` every
|
| 569 |
+
> reference is first scaled down to the output's pixel area, and `mp` only ever
|
| 570 |
+
> caps *further* β so at 768p (~1.03 MP) the 1.5 and 2.0 settings change
|
| 571 |
+
> nothing. The real per-reference resolution control is `ref_image_size=max`
|
| 572 |
+
> **plus** `mp`, never `mp` on its own.
|
| 573 |
|
| 574 |
+
Add `"shots": [1, 2]` to a ref to keep it out of the hops it does not belong in.
|
| 575 |
+
On a continuation chain, omitting `shots` means **hop 1 only** β right for a
|
| 576 |
+
place plate, which beats the pin if it rides a hop set somewhere else. **Put
|
| 577 |
+
face plates on every hop:** a hop with no face reference comes back a different
|
| 578 |
+
person and no later hop recovers. A shot's own `refs` overrides all of this for
|
| 579 |
+
that one hop.
|
| 580 |
|
| 581 |
+
### Reference media: three clips, three voices
|
|
|
|
|
|
|
|
|
|
|
|
|
| 582 |
|
| 583 |
+
H3 takes 9 reference pictures, 3 reference videos and 3 standalone reference
|
| 584 |
+
audios. Pictures go through REFS; the clips and voices are in **MEDIA**.
|
|
|
|
| 585 |
|
| 586 |
+
| slot | cited as | notes |
|
| 587 |
+
|---|---|---|
|
| 588 |
+
| `reference clip` 1β3 | `<Video 1..3>` | motion/look plates the whole chain reads |
|
| 589 |
+
| `voice` 1β3 | `<Audio 1..3>` | timbre references for hop 1 |
|
| 590 |
|
| 591 |
+
**Numbering is dense.** Fill slots 1 and 3 and you get `<Video 1>` and
|
| 592 |
+
`<Video 2>` β there is no gap, so **clearing a slot renumbers the ones after
|
| 593 |
+
it**. Refer to media by what it is, not by its number.
|
| 594 |
|
| 595 |
+
Reference clips carry their own sound: each clip's audio track is decoded and
|
| 596 |
+
handed to the model alongside its picture. A clip with no usable audio track
|
| 597 |
+
passes silent and says so in the log.
|
| 598 |
|
| 599 |
+
**Trim them.** Every reference audio is attended on every step of every hop, and
|
| 600 |
+
H3 encodes the whole file, so a three-minute take is a large invisible tax on a
|
| 601 |
+
clip that only needed four seconds. Slots 2 and 3 share slot 1's `reference
|
| 602 |
+
video size`, which is a decode budget rather than a creative setting β and, as
|
| 603 |
+
[SWAP](#swap) found the hard way, an influence dial.
|
| 604 |
|
| 605 |
+

|
| 606 |
|
| 607 |
+
*A reference clip with its in/out scrubber, the description that tells the
|
| 608 |
+
encoder what the clip is **for**, and `video input size`.*
|
| 609 |
|
| 610 |
+
`master_audio_file` is the odd one out: one continuous take every hop lip-syncs
|
| 611 |
+
to, delivered as a passthrough with no VAE round trip. Empty is off and leaves
|
| 612 |
+
generated voice as before. The beat still needs the words in `<d>[English]
|
| 613 |
+
β¦</d>` β the lock supplies the timing, not the script β and a take shorter than
|
| 614 |
+
the chain is refused on the queue rather than discovered as a mute final hop.
|
|
|
|
|
|
|
|
|
|
| 615 |
|
| 616 |
+
An end of `0` on any scrubber means *to the end of the file*, so a longer
|
| 617 |
+
replacement still plays out rather than being cropped to the old one.
|
| 618 |
|
| 619 |
+
### Hop cache
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
|
| 621 |
+
`cache_hops=on` writes every rendered hop to ComfyUI's temp dir as lossless FFV1
|
| 622 |
+
video plus a float32 `.npy` waveform, evicting least-recently-used above
|
| 623 |
+
`cache_budget_gb`. The key **chains** β each hop's key includes the previous
|
| 624 |
+
hop's β because hops are causally dependent. So:
|
| 625 |
|
| 626 |
+
- edit shot 3 and re-queue β shots 1 and 2 load from cache, only 3 renders;
|
| 627 |
+
- edit shot 1 β all three re-render, which is correct, not a bug;
|
| 628 |
+
- change resolution, sampler, the checkpoint, a LoRA, or an attention setting β
|
| 629 |
+
the whole chain re-renders;
|
| 630 |
+
- change `pin_to_qwen` or `overlap` β only hops 2+ re-render, because neither
|
| 631 |
+
can reach hop 1. A restart hop is a start, so overlap does not reach it either;
|
| 632 |
+
- set `last_frame_guide=still` or `master_audio_file` β every hop re-renders
|
| 633 |
+
(both reach hop 1). `before_restart` only moves the hop that gets the guide;
|
| 634 |
+
- change a reference picture β only the hops that picture rides re-render.
|
| 635 |
+
|
| 636 |
+
That last one is worth knowing about. The node cannot read the settings on your
|
| 637 |
+
LoRA and attention nodes, so it fingerprints what they *did* to the model β
|
| 638 |
+
which weight keys were patched, at what strengths, and the attention overrides β
|
| 639 |
+
plus the base model's class, dtype and parameter count. Two remaining gaps: two
|
| 640 |
+
different LoRAs touching exactly the same keys at exactly the same strengths,
|
| 641 |
+
and two different builds of the same architecture at the same dtype and
|
| 642 |
+
parameter count.
|
| 643 |
|
| 644 |
+
Set `locked: true` on a shot to pin it to its last render regardless.
|
| 645 |
|
| 646 |
+
### Reading a plan before you render it
|
| 647 |
|
| 648 |
+
`dry_run=on` compiles every hop's prompt and stops. No model, no sampler,
|
| 649 |
+
seconds instead of minutes. The compiled text comes out on `info`, and as a
|
| 650 |
+
readable page on `contact_sheet`. This is the only way to see what the text
|
| 651 |
+
encoder will actually receive β the directive layer, the continuation
|
| 652 |
+
scaffolding, the identity lock and the `<Picture N>` citations are all assembled
|
| 653 |
+
at render time.
|
| 654 |
|
| 655 |
+

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 656 |
|
| 657 |
+
`render_through=N` stops after N hops; with `cache_hops=on`, 3 β 5 β 8 builds a
|
| 658 |
+
chain up in stages and only ever renders the new hops. The plan is not
|
| 659 |
+
truncated: shot 4 still knows it is shot 4 and keys the same way it will in the
|
| 660 |
+
full run.
|
| 661 |
|
| 662 |
+
`quality=draft` forces the 448p tier and 6 steps. Treat it as a **fidelity**
|
| 663 |
+
lever rather than a speed one β measured at ~42 s/hop against ~45 s/hop at 7
|
| 664 |
+
steps, so if you already render at 448p and 6β8 steps it saves almost nothing
|
| 665 |
+
and `dry_run` is the fast button. Draft earns its place when your final is
|
| 666 |
+
genuinely heavier, 768p at 14 steps.
|
| 667 |
+
|
| 668 |
+
`contact_sheet=on` adds an image on the fourth output: one row per hop, that
|
| 669 |
+
hop's first and last **delivered** frame side by side, its beat, its directives,
|
| 670 |
+
and what actually happened to it. On a chain of any length this is the fastest
|
| 671 |
+
way to find the hop that broke.
|
| 672 |
+
|
| 673 |
+

|
| 674 |
|
| 675 |
+
### Defaults
|
| 676 |
|
| 677 |
| | |
|
| 678 |
|---|---|
|
|
|
|
| 686 |
| cache budget | 20 GB |
|
| 687 |
| tone_compensate | off (both shipped workflows set `frame_shift`) |
|
| 688 |
| tone_anchor | 0.35, used only by `tone_compensate=anchor` |
|
| 689 |
+
| tone_anchor_ref | `hop1`. `still` holds the photograph; needs a start image |
|
| 690 |
| pin_mech | `auto` |
|
| 691 |
| last_frame_guide | `off`. Recommended `before_restart` when the plan has a restart |
|
| 692 |
+
| master_audio_file | empty (generated voice) |
|
| 693 |
| quality | final |
|
| 694 |
|
| 695 |
+
Three shots at 10 s with a 0.9 s overlap is about 28 s of master.
|
| 696 |
+
|
| 697 |
+
### Nodes
|
| 698 |
+
|
| 699 |
+
**H3 Ref2VA Chain** β `images`, `audio`, `info`, `contact_sheet` out. Wire
|
| 700 |
+
`CreateVideo` + `SaveVideo` as in the example workflow, `info` to a Preview Text
|
| 701 |
+
node, and `contact_sheet` to a Save Image.
|
| 702 |
+
|
| 703 |
+
**H3 Chain Preview** β a passthrough panel for the IMAGE (and optionally AUDIO)
|
| 704 |
+
wire, placed between the chain and `CreateVideo`. Images and audio come out
|
| 705 |
+
unchanged, so adding or removing it changes no pixels. It shows the live sample,
|
| 706 |
+
the seam, a chain-wide progress bar, cache hit / seed / steps per hop, **which
|
| 707 |
+
pin mechanism each hop actually used**, and end-of-run A/V drift.
|
| 708 |
+
|
| 709 |
+
**H3 Tone Compensate** β `images` out. **For hand-built chains only.** It cannot
|
| 710 |
+
fix `H3 Ref2VA Chain`'s output: that node joins its hops internally and drops
|
| 711 |
+
each hop's first `overlap` frames at the seam, so the regenerated copies this
|
| 712 |
+
needs are already gone. Use the chain node's `tone_compensate` widget instead.
|
| 713 |
+
|
| 714 |
+
**H3 Seam Report** β `report` (STRING) + `chart` (IMAGE). **Ships wired on the
|
| 715 |
+
Starter canvas.** Wire the chain's `info` into it as well as `images`: with a
|
| 716 |
+
restart in the chain the hop lengths are no longer uniform, and the `hops` /
|
| 717 |
+
`overlap` widgets cannot describe that. It measures the brightness step at every
|
| 718 |
+
join, says whether each is invisible / marginal / visible, and totals the
|
| 719 |
+
chain's cumulative drift. A single reading includes whatever the scene did
|
| 720 |
+
across the cut, so treat one number as an upper bound.
|
| 721 |
+
|
| 722 |
+
**H3 Continuity State** β `continuity_state` (STRING) out. **Setting only**:
|
| 723 |
+
`setting_locked` / `setting_context` / `setting_mutable`. Characters belong in
|
| 724 |
+
`ref_plan`.
|
| 725 |
+
|
| 726 |
+
### Limits
|
| 727 |
+
|
| 728 |
+
- **Texture still ratchets on long chains.** Stay around **3β5 hops** until that
|
| 729 |
+
is handled. Nine-hop chains have been measured; they converge.
|
| 730 |
+
- A workflow saved before 2026-08-28 loses its reference pictures. The old
|
| 731 |
+
`ref_image_N` sockets carried tensors, so there is no filename to recover.
|
| 732 |
+
The rail names each affected ref and asks you to pick its picture.
|
| 733 |
+
- The reference `desc` and subject `locked` text go to the encoder verbatim,
|
| 734 |
+
every hop. A detail that is not in the photograph is **asked for**, not
|
| 735 |
+
ignored. Describe what you actually wired.
|
| 736 |
+
- `HTCContinuityState` is setting only; the `characters_*` fields were removed
|
| 737 |
+
because filling in both injected identity text twice.
|
| 738 |
+
- Each join hard-cuts video but crossfades audio ~40 ms, so A/V drifts ~40 ms
|
| 739 |
+
per hop.
|
| 740 |
+
- A 22-frame pin clip is ~2 Qwen frames at 2 fps. It is a live-state hint, not a
|
| 741 |
+
full previous-clip watch.
|
| 742 |
+
- A longer overlap does not fix continuity β it can pin whatever content happens
|
| 743 |
+
to be in that longer tail.
|
| 744 |
+
- A 5 s hop drops the airlock on a continuous join; validate seams at 8 s or 15 s.
|
| 745 |
|
| 746 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
|
| 748 |
+
## Credits
|
| 749 |
+
|
| 750 |
+
From the **Sulphur** Discord:
|
| 751 |
+
|
| 752 |
+
- **@urlilgoddess** β the ten-run degradation study this release is built on,
|
| 753 |
+
run and measured with her own instruments, and the sample frames in
|
| 754 |
+
[Tested in public](#tested-in-public). Three of 2.0's behaviours exist because
|
| 755 |
+
she measured that the reasoning behind the old ones was wrong.
|
| 756 |
+
- **@frankyi** β contributed the SWAP tab, which ships in 2.0 rebuilt from its
|
| 757 |
+
own design rather than merged as-is.
|
| 758 |
+
- **@Sean3884** β author of **PromptMasterLD**, which this pack has borrowed
|
| 759 |
+
from twice: its `song_lock` shaped `audio_lock.py`, and its edit laws shaped
|
| 760 |
+
SWAP's four modes. Technique, not code β the distinctions and the discipline
|
| 761 |
+
of stating an exclusion affirmatively, with the prose written fresh here.
|
| 762 |
+
|
| 763 |
+
Also: the tone estimator is ported from
|
| 764 |
+
[rkfg/ComfyUI-MiniMaxH3-ToneCompensate](https://github.com/rkfg/ComfyUI-MiniMaxH3-ToneCompensate)
|
| 765 |
+
(MIT, as is this pack), and the latent join comes from
|
| 766 |
+
[ComfyUI-H3-Motion-Context](https://github.com/NikoDemon80/ComfyUI-H3-Motion-Context).
|
| 767 |
+
The turbo stack in the shipped workflows is
|
| 768 |
+
[PlagueKind](https://github.com/PlagueKind/ComfyUI-PlagueKind-Nodes) and
|
| 769 |
+
[KJNodes](https://github.com/kijai/ComfyUI-KJNodes).
|
| 770 |
|
| 771 |
+
## Docs
|
| 772 |
|
| 773 |
+
[`CHANGELOG.md`](CHANGELOG.md) is what 2.0.0 contains, written for users.
|
| 774 |
+
[`PROMPTING.md`](PROMPTING.md) is the authoring guide. [`CLAUDE.md`](CLAUDE.md)
|
| 775 |
+
is the current map of the pack if you are changing it, and `docs/DEVLOG.md` is
|
| 776 |
+
the engineering log. `docs/HANDOVER_*.md`, root `HANDOVER.md` and `BETA_NOTES.md`
|
| 777 |
+
are historical session notes β do not take them as the state of this release.
|
| 778 |
|
| 779 |
+
---
|
| 780 |
|
| 781 |
+
## Changelog
|
| 782 |
+
|
| 783 |
+
Full notes in [`CHANGELOG.md`](CHANGELOG.md).
|
| 784 |
+
|
| 785 |
+
**2.0.0** β 2026-09-05. Full release. `master_audio_file`, one continuous take
|
| 786 |
+
every hop lip-syncs to. `last_frame_guide` (`before_restart` recommended).
|
| 787 |
+
`anchor: "restart"` as a real chain start, now writing its full length. `refs`
|
| 788 |
+
on a shot. The **SWAP** tab. Three reference-clip and three voice slots. A Lab
|
| 789 |
+
tone anchor with `tone_anchor_ref=still`. `pin_mech`. A hop cache that no longer
|
| 790 |
+
pickles, and an fp16 master buffer that halves the largest allocation in the
|
| 791 |
+
pack. New widgets were appended, so saved 1.1 graphs keep their values β but a
|
| 792 |
+
1.1 *hop cache* is fully invalidated on purpose, because the model fingerprint
|
| 793 |
+
now identifies the base checkpoint.
|
| 794 |
+
|
| 795 |
+
**1.1.x** β 2026-09-03/05. Tabbed editor with RUN pinned at the bottom.
|
| 796 |
+
`render_from` / `render_through` as a range. `retention_analysis` on every hop a
|
| 797 |
+
still rides, which fixes a reference pinned to any hop but the first being
|
| 798 |
+
rendered *as* the shot. Computed canvas β eleven aspect ratios on H3's 32 px
|
| 799 |
+
grid, so 16:9 at the top rung is 1344Γ768. Per-hop reference keys, so changing
|
| 800 |
+
one picture re-renders only the hops it rides. 1.1.1 passes every Core argument
|
| 801 |
+
by name, fixing `got multiple values for argument 'ref_image_size'` on builds
|
| 802 |
+
that order MiniMax H3's parameters differently.
|
| 803 |
+
|
| 804 |
+
**1.0.x** β 2026-09-02. First full release: the **WRITE** panel, the required
|
| 805 |
+
two-document schema, the reference rail owning each picture's pixel budget, and
|
| 806 |
+
the writer being told the hop length. Three plan lints were measured against
|
| 807 |
+
real renders, found to be warning about correct work, and narrowed. Patch
|
| 808 |
+
releases fixed the "no model is selected" bug on a fresh install and cleared
|
| 809 |
+
registry-scanner findings.
|
| 810 |
+
|
| 811 |
+
**0.4.x** β 2026-08-30. The five new dials reached the run panel; a dry run
|
| 812 |
+
stopped returning a 1Γ1 placeholder that libx264 cannot encode. The prompt pack
|
| 813 |
+
learned to show a place tag on both sides of its round trip. The hop cache
|
| 814 |
+
stopped shelling out to `ffmpeg` and encodes FFV1 in process through PyAV β
|
| 815 |
+
same format, bit-exact, and no external command for the registry scanner to flag.
|
| 816 |
+
|
| 817 |
+
**Renamed 2026-08-29** from `ComfyUI-H3-Ref-Chain`. The old node ids are still
|
| 818 |
+
registered as deprecated aliases, so every workflow saved before the rename
|
| 819 |
+
keeps loading. Nothing needs migrating.
|
|
Git LFS Details
|
|
Git LFS Details
|