Sandpies Claude Opus 5 commited on
Commit
7dcfb40
Β·
1 Parent(s): 944cb85

Rework the README as a showcase, and put SWAP in the changelog

Browse files

The 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 CHANGED
@@ -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.
README.md CHANGED
@@ -13,7 +13,9 @@ tags:
13
 
14
  # Hand Tie Clips
15
 
16
- One node. Write a shot plan, drop in your reference stills, queue.
 
 
17
 
18
  ![Six consecutive frames across a join](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/seam-frames.png)
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
- > **Renamed 2026-08-29.** This pack was `ComfyUI-H3-Ref-Chain`. The nodes now
25
- > live under the **Hand Tie Clips** category and register as `HandTieClips`,
26
- > `HTCChainPreview`, `HTCToneCompensate` and `HTCContinuityState`. The old ids
27
- > are still registered as deprecated aliases, so **every workflow saved before
28
- > the rename keeps loading** β€” they are just hidden from node search. Nothing
29
- > needs migrating.
30
-
31
- > **2.0.0 β€” 2026-09-05.** Full release. One continuous take every hop can
32
- > lip-sync to (`master_audio_file`, empty = off). An opt-in last-frame
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
- | **`shot_plan`** | The script, as JSON. One shot per hop β€” **the shot count is the hop count**, so `chains` can no longer disagree with it. |
169
- | **`directives`** | `join` / `camera` / `framing` / `pace` / `tail` per shot, compiled to vetted prose. Improving a phrase improves every plan you have ever written. |
170
- | **`ref_plan`** | Stable `@tags` for reference stills, grouped into subjects, each naming a picture file. Removing one can no longer silently renumber `<Picture N>`. |
171
- | **`cache_hops`** | Lossless per-hop cache. Re-roll one shot, resume after a crash, and hold roughly one hop in RAM instead of the whole clip. Edit hop 5 of 8 and only 5-8 re-render. [How to use it](PROMPTING.md#re-rolling-one-hop). |
172
- | **`WRITE`** | A plan writer on the node. Point it at any OpenAI-compatible server β€” LM Studio, llama-server β€” describe the scene in plain language, and it fills the script and the reference rows, reading the pictures you have already dropped in. Server settings stay on your machine, never in the workflow. |
173
- | **the editor** | Cards on the node, not JSON in a textarea. Hover any directive to read the exact sentence it puts in the prompt, or hit **Templates** for a ready-made pattern. |
 
 
174
 
175
- Simple mode keeps the old one-prompt workflow; the speed stack (LoRAs, AdaLN fix, low-VRAM, SLA) stays as four ordinary nodes on the MODEL wire, where you can see it.
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, if that is where you found it -- the two are mirrors of the same repository and either is fine:
189
 
190
  ```
191
  git clone https://huggingface.co/sandpies/ComfyUI-Hand-Tie-Clips
192
  ```
193
 
194
- Cloning creates the folder at the right depth for you, which is the mistake the zip route invites. **Updating later is then one command** from inside the pack folder:
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 -- `torch`, `numpy`, `PIL`, `av`, `aiohttp` -- already ships with ComfyUI, which is why `dependencies` in `pyproject.toml` is empty.
 
 
219
 
220
- Then:
221
-
222
- 1. **Restart ComfyUI.** Python changed.
223
- 2. **Hard-refresh the browser** (Ctrl+Shift+R / Cmd+Shift+R). The editor is served from `/extensions/ComfyUI-Hand-Tie-Clips/`, and a stale cache is the single most common reason the node mounts with no UI on a fresh install.
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 listed once
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
- The two example graphs are in `workflows/` inside the pack folder -- open them with the **Workflow > Open** menu, or copy them into `ComfyUI/user/default/workflows/` to have them in the sidebar. Start with `HandTieClips_Starter.json`; it ships with no references on purpose, so it runs before you have supplied any pictures.
 
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
- - **Optional but recommended:** [ComfyUI-H3-Motion-Context](https://github.com/NikoDemon80/ComfyUI-H3-Motion-Context). Hops after the first are guided by the previous hop's sampler AV latent through it. Without it the node falls back to stock `MiniMaxH3AddGuide`, which still works and still chains -- it is a different join, so a seam you are comparing against someone else's render may not be the same code path. The node detects it at runtime and prints which one it took.
 
 
 
241
 
242
- The two shipped workflows wire the **turbo stack** this node is actually run with, because an example without it is not the graph anyone uses:
 
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
- On disk as well: the **turbo LoRA** named in the loader stack, and `taeh3.safetensors` for the live preview (or set `tiny_vae` to `none`).
 
 
 
 
 
 
 
 
 
 
 
255
 
256
- Both shipped workflows are saved pointing at the exact files they were rendered with. These are quantised builds and are **not** the only ones that work -- any ref2va or hybrid ref2va set will do. Repoint the loaders at what you have; a loader showing an empty or red filename is naming a file you do not have, not a broken workflow.
 
257
 
258
- | loader | file the example names |
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
- **CLIP reaches the node from the LoRA loader, not from the encoder** β€” that is what makes the text half of every LoRA land. Do not rewire it back.
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
- ![The editor's four tabs, with SCRIPT open on a ten-shot plan](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/editor-tabs.png)
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
- The cards write this; you rarely see it. It is under **JSON** on the node if you want to copy a plan between workflows.
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
- ## Writing beats
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
- > The full authoring guide, including how to have a model write plans for you, is **[PROMPTING.md](PROMPTING.md)**. What follows is the short version.
373
 
374
- Sampling runs at cfg 1.0 with no negative branch, so **the prompt is additive:
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
- **Point the action line at the reference you want used.** Describing a reference
379
- in the register is not enough -- the beat is what drives the frame. Write the tag
380
- into the action:
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. Describe the state you want as a pose plus a sound:
394
 
395
- ```
396
- The cook leans back against the counter with her lips closed, and lets her eyes
397
- move slowly across the room. The kitchen is quiet apart from the hum of the
398
- refrigerator and faint street noise through the window.
399
- ```
400
 
401
- H3 generates audio for the whole hop no matter what, so silence must be written
402
- as a sound -- room tone, a fridge, traffic. Written as an absence, you get speech.
 
 
403
 
404
- **Put a state change at the end of the previous shot.** Every hop after the first
405
- opens by holding the frames it was handed, and the audio pin carries the tail of
406
- the previous hop across the join. If shot 2 ends mid-sentence, no wording in shot
407
- 3 will make it start quiet. Finish the line where it actually finishes:
 
408
 
409
- ```
410
- shot 2: The cook sets the knife down, turns toward the window, and finishes her
411
- last word as she looks out.
412
- shot 3: The cook leans back against the counter with her lips closed...
413
- ```
 
 
 
414
 
415
- To be silent, still, or somewhere else in a shot, arrive there before the
416
- previous shot ends.
 
417
 
418
- **Pick ambience that is narrowband and specific.** After the first shot, official
419
- soundscape fields are stripped and ambience is plain prose, so the exact words
420
- matter. "faint street noise through the window" is broadband and renders as a
421
- five-second hiss; "the low hum of the refrigerator" does not. If a continuous bed
422
- still misbehaves, name a single discrete event instead -- "a single click from the
423
- refrigerator, then stillness".
424
 
425
- **Set `tail` on your last shot.** `settle` and `hold` change the final sentence of
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** on the node, point it at any OpenAI-compatible server, say what
434
- you want in one sentence, and press **Write plan**. It fills the SCRIPT cards
435
- and the REFERENCES rows together β€” and the pictures already on those rows go
436
- with the request, so the model describes what it is actually looking at rather
437
- than guessing from a filename.
438
 
439
  ![The WRITE bar](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/write-panel.png)
440
 
441
- - **Context 32768.** The system prompt alone is ~6,000 tokens, the reply another
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 β€” they are not part of the
448
- workflow, so a shared `.json` never points at your server.
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 β€” and that is the part that is tedious by hand.
453
- Two things are worth reading every time: each reference's `desc`, which can be
454
- confidently wrong about its own photograph, and the spoken words in every beat.
455
- The node lints both and prints what it finds before you render.
456
 
457
- `prompt_pack/` is the same writer as a copy-paste prompt for a chat window.
458
 
459
- ## Reference media: three clips, three voices
460
 
461
- H3 takes **9 reference pictures, 3 reference videos and 3 standalone reference
462
- audios**. Pictures go through the REFS rail (see below). The clips and voices
463
- are in **MEDIA**, one slot each until 1.2:
 
 
464
 
465
- | slot | cited as | notes |
466
- |---|---|---|
467
- | `reference clip` 1-3 | `<Video 1..3>` | motion/look plates the whole chain reads |
468
- | `voice` 1-3 | `<Audio 1..3>` | timbre references for hop 1 |
469
 
470
- **Numbering is dense.** Fill slots 1 and 3 and you get `<Video 1>` and
471
- `<Video 2>` β€” there is no gap. That means **clearing a slot renumbers the ones
472
- after it**, so a beat that names an ordinal will cite a different clip. Refer to
473
- media by what it is, not by its number, unless you are sure the slots above it
474
- are filled.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
- **Reference clips now carry their own sound.** Each clip's audio track is
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
- **Trim them.** Every reference audio is attended on every step of every hop, and
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
- ## Reference register
 
 
 
487
 
488
- ```json
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
- `file` is a picture in `ComfyUI/input/h3_refs`, set by the rail. `tag` is what you write in your beats β€” `@hero_face` β€” and the node resolves it to the right `<Picture N>` **per hop**, so pulling a still out of the middle no longer breaks every later reference.
 
 
 
 
 
 
502
 
503
- `subject` groups pictures per person. This matters: declaring every picture as a photo of `<Subject 1>` makes the model render the *average* of two different people.
504
 
505
- `retention` says how much of a picture carries over β€” `fully_preserved` (face and bone structure exactly), `partially_copy` (the garment and its cut), `reference` (layout, surfaces and light, i.e. a place). Refs with a subject default to `fully_preserved`; everything else defaults to `reference`.
 
 
 
 
 
 
506
 
507
- `mp` caps one picture's pixel budget in megapixels. It is a **token dial, not a quality one**: H3 turns every reference into `latent_h Γ— latent_w` entries β€” pixel area Γ· 256 β€” and attends over all of them on every step of every hop, so a location plate costing what a face costs is waste. A 0.3 MP place plate is ~1,170 tokens; a 2 MP portrait is ~7,800. Absent (or `0`) means no cap. The rail offers 0.3–2.0; `ref_plan` accepts up to 16.
508
 
509
- > **The dial is inert at the default.** On `ref_image_size=match` every reference is first scaled down to the output's pixel area, and `mp` only ever caps *further* β€” so at 768p (~1.03 MP) the 1.5 and 2.0 settings change nothing. The real per-reference resolution control is `ref_image_size=max` **plus** `mp`, never `mp` on its own.
 
 
 
 
 
 
 
510
 
511
- Add `"shots": [1, 2]` to a ref to keep it out of the hops it does not belong in. On a continuation chain (`hop_script=next` / a shot plan), omitting `shots` means **hop 1 only** β€” right for a place plate, which beats the pin if it rides a hop set somewhere else. **Put face plates on every hop:** a hop with no face reference comes back a different person and no later hop recovers.
 
 
 
 
512
 
513
- ## Hop cache
514
 
515
- `cache_hops=on` writes every rendered hop to ComfyUI's temp dir as lossless FFV1 video plus a float32 `.npy` waveform, evicting least-recently-used above `cache_budget_gb`. The key is chained and includes the model fingerprint and, per hop, which pin mechanism was used -- a hop pinned by the AddGuide fallback is not the same render as one pinned by Motion-Context.
 
516
 
517
- The key **chains** β€” each hop's key includes the previous hop's β€” because hops are causally dependent. So:
518
 
519
- - edit shot 3 and re-queue β†’ shots 1 and 2 load from cache, only 3 renders;
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
- That last one is worth knowing about. The node cannot read the settings on your LoRA and attention nodes, so instead it fingerprints what they *did* to the model β€” which weight keys were patched, at what strengths, and the attention overrides β€” plus the base model itself: its class, its dtype and its parameter count. Change a LoRA strength, or load the int8 build where you had the bf16 one, and the cache correctly invalidates. Nodes that configure themselves by closure or by object are read the same way, so changing a setting on one moves the key rather than only installing it. Two remaining gaps: two different LoRAs touching exactly the same keys at exactly the same strengths, and two different builds of the same architecture at the same dtype and parameter count.
527
 
528
- Set `locked: true` on a shot to pin it to its last render regardless.
 
 
 
 
 
 
 
529
 
530
- ## Reading a plan before you render it
 
531
 
532
- `dry_run=on` compiles every hop's prompt and stops. No model, no sampler,
533
- seconds instead of minutes. The compiled text comes out on `info`; the same
534
- thing comes out on `contact_sheet` as a page you can read at a glance.
 
 
535
 
536
- This is the only way to see what the text encoder will actually receive. The
537
- directive layer, the continuation scaffolding, the identity lock and the
538
- `<Picture N>` citations are all assembled at render time, so until now the first
539
- sight of the real prompt was in the log of a render you had already paid for.
 
 
540
 
541
- ![`lock` and the range button on the shot cards](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/shot-lock-range.png)
542
 
543
- *`lock` freezes a take you like; the `⏡` beside it sets the render range to that shot alone.*
544
 
545
- `render_through=N` stops after N hops. With `cache_hops=on` the hops you already
546
- rendered stay on disk, so 3 β†’ 5 β†’ 8 builds a chain up in stages and only ever
547
- renders the new hops. The plan is not truncated: shot 4 still knows it is shot
548
- 4, keeps its own seed, and keys the same way it will in the full run.
 
 
 
 
549
 
550
- `quality=draft` forces the 448p tier and 6 steps β€” enough to read blocking, camera and
551
- whether a join lands. Resolution and steps are both in the cache key, so a draft
552
- never overwrites the final it stands in for; the two simply cost two entries.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
 
554
- Treat it as a **fidelity** lever rather than a speed one. Measured at ~42 s/hop
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
- ## Contact sheet
 
 
 
 
 
561
 
562
- `contact_sheet=on` adds an image on the fourth output: one row per hop, that
563
- hop's first and last **delivered** frame side by side, its beat, its directives,
564
- and what actually happened to it β€” seed, steps, whether it came from cache, what
565
- the tone correction did. Wire it to a Save Image.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
 
567
- ![Contact sheet](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/contact-sheet-vlog.png)
 
 
568
 
569
- On a chain of any length this is the fastest way to find the hop that broke. The
570
- one hard cut in the 114-second reference chain sat in a plan that passed every
571
- automated check; it is obvious in one row of a contact sheet and invisible in a
572
- progress bar.
 
 
 
 
 
 
 
 
 
 
573
 
574
- ## Brightness drift, and the two things that fix it
575
 
576
- These are different problems and they need different settings.
577
 
578
- **The step at a join** is the denoiser's tone bias on a fresh generation.
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
- **The slide across a whole chain** is different. Each hop also darkens across
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
- `tone_compensate=anchor` is frame_shift plus a pull back toward a target.
589
- `tone_anchor_ref` chooses which.
590
 
591
- **`hop1` (default) holds whatever hop 1 rendered.** That was the original
592
- reasoning: hop 1 is the one tone in the chain nothing has drifted into yet. An
593
- outside ten-run 9-hop study measured that this is false. Before any relay has
594
- happened:
595
 
596
- | | reference still | hop 1 |
597
- |---|---|---|
598
- | chroma | 33.6 | 30 |
599
- | b* (warmth) | 26.6 | 22 |
600
- | fine detail | 1.00 | 0.72–0.99 |
 
 
 
 
 
 
 
 
 
601
 
602
- Hop 1 is not the reference β€” it is the **first casualty**. A chain anchored on
603
- it converges on a target that already fell short.
 
604
 
605
- **Reach for `still` when you want the chain to hold the photograph**, not hop
606
- 1's already-short copy. It uses `start_image` instead, which does not drift,
607
- and it pulls hop 1 itself β€” the only way that 33.6-against-30 gap ever closes.
 
 
 
 
 
 
 
 
608
 
609
- Costs: needs `start_image_file` (the queue refuses otherwise). Under the
610
- Motion-Context join the correction still only reaches the delivered frames,
611
- not the next hop's pin β€” `pin_mech=addguide` is what closes that loop.
 
 
612
 
613
- Two things keep the pull from causing the problem it is fixing:
 
 
614
 
615
- - the pull **ramps in** across the first two seconds of each hop, so frame 0
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
- A scene that is *meant* to get darker looks exactly like drift from the
623
- inside, so a shot can opt out:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
 
625
  ```json
626
- { "beat": "She steps down into the cellar.", "tone": "rebase" }
 
 
 
 
 
 
 
 
 
627
  ```
628
 
629
- `"tone": "free"` skips the pull for that hop only. `"tone": "rebase"` also moves
630
- the anchor onto that hop, which is how a scene that is genuinely darker from
631
- here on stops being fought for the rest of the film.
 
632
 
633
- Set `tone_anchor` to 0 to get plain frame_shift back.
 
 
634
 
635
- **Measured** on three hops from one seed and one cache β€” the hop store writes
636
- before the tone stage runs, so flipping the mode re-grades the same renders and
637
- only the correction differs:
 
638
 
639
- | `tone_anchor` | drift across the chain | worst seam |
640
- |---|---|---|
641
- | off | 13.5/255 | 2.1/255 |
642
- | 0.15 | 7.4 | 1.3 |
643
- | 0.35 | 5.1 | 1.9 |
644
- | 0.60 | 2.9 | 2.6 |
645
 
646
- Both columns are **H3 Seam Report's own numbers**, so what you measure matches
647
- what this table says.
 
 
 
648
 
649
- ![Seam report chart](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/seam-report.png)
 
 
 
 
 
650
 
651
- Drift falls evenly β€” 45%, 62%, 78% of the uncorrected slide. The seam does not:
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
- One caveat the numbers cannot capture: a scene that brightens *for a reason* β€”
658
- walking toward a window β€” is indistinguishable from drift from the inside, and
659
- anchor will flatten it. That is what the per-shot `tone` field is for.
660
 
661
- ## The MODEL wire
 
 
 
662
 
663
- Four ordinary nodes, in this order:
 
 
664
 
665
- UNETLoader β†’ LoRA stack (mode=minimax) β†’ H3 AdaLN Fix β†’ MiniMax Low VRAM Attention β†’ H3 SLA Attention β†’ H3 Ref2VA Chain
 
 
666
 
667
- **Take CLIP from the LoRA loader's second output**, not straight from the encoder, or the text-side keys H3 LoRAs carry are silently dropped.
 
 
 
 
668
 
669
- They briefly lived inside this node as a `model_patches` JSON widget. That made the graph shorter and the node harder to read, so it was reverted β€” these belong where you can see them.
670
 
671
- ## Pinning the previous hop
 
672
 
673
- `pin_mech` chooses the join. `auto` (default) is Motion-Context when the pack
674
- is installed, the overlap has a matching `context_length`, and the previous hop
675
- left a sampler latent; AddGuide pixels otherwise. Forcing `motion_context` or
676
- `addguide` does not fall back β€” it fails with the reason, because a lever that
677
- silently becomes the other setting cannot be compared against it.
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
- Three more widgets tune the pin, all defaulting to their pre-existing behaviour:
 
683
 
684
- | widget | default | what it does |
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
- The DiT pin is the previous hop’s **sampler latent** through Motion-Context when present (no decode/re-encode; audio window ends at the join). AddGuide on decoded frames is the fallback. `pin_to_qwen` still shows the incoming state to the text encoder:
 
 
 
691
 
692
- | | |
693
- |---|---|
694
- | `off` | Pin stays DiT-only. |
695
- | `last frame` (default) | Previous hop's last frame is `<Picture 1>`. Identity stills that still ride this hop shift to Picture 2+. |
696
- | `pin clip` | Overlap frames become an extra `<Video>` at ~2 fps. No soundtrack on it, so `voice` stays `<Audio 1>`. |
697
- | `both` | Last frame + pin clip. |
 
 
 
 
 
 
 
 
 
 
 
698
 
699
- `@tags` in beats resolve per hop, so stills shifting to Picture 2+ does not break prose. Literal `<Picture N>` in hop 2+ beats would.
700
 
701
- ## Last-frame guide
702
 
703
- `last_frame_guide` plants `start_image` at a hop's last **pixel** frame
704
- (AddGuide `frame_idx=-1`, not latent T-1). It does not become the next hop's
705
- frame 0. Needs a start image. Ships `off`.
 
 
 
706
 
707
- **Reach for `before_restart`.** It guides only a hop whose *next* shot is
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
- 1.58s close 3.67s close 4.92s close 5.96s close-ish 6.58s WIDE 7.04s WIDE
 
 
 
724
 
725
- Safe when no shot authors a framing; visibly wrong when they do.
 
 
 
 
 
 
 
 
 
 
 
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` (holds hop 1's already-short copy). `still` holds the photograph; needs a start image |
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). A set file is the lip-sync lock |
745
  | quality | final |
746
 
747
- Three shots at 10 s with a 0.9 s overlap is about 28 s of master after the overlap is dropped.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
748
 
749
- ## Voice, music and trims
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
- ![The MEDIA tab with a reference clip loaded](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/reference-clip.png)
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
- **H3 Tone Compensate** β€” `images` out. Corrects a generated segment's tone against the previous one, estimated on the overlap they share. **For hand-built chains only.** It cannot fix `H3 Ref2VA Chain`'s output: that node joins its hops internally and drops each hop's first `overlap` frames at the seam, so the regenerated copies this needs are already gone by the time images leave it. Use the chain node's `tone_compensate` widget instead. Estimator ported from [rkfg/ComfyUI-MiniMaxH3-ToneCompensate](https://github.com/rkfg/ComfyUI-MiniMaxH3-ToneCompensate) (MIT).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
790
 
791
- **H3 Seam Report** β€” `report` (STRING) + `chart` (IMAGE). **Ships wired on the Starter canvas.** Set `hops` to your shot count: it derives hop length from frames, hops and overlap, so a wrong `hops` does not error β€” it returns a plausible length and puts every seam where no join exists. Wire the chain's `images` into it and it measures the brightness step at every join, says whether each is invisible / marginal / visible, and totals the chain's cumulative drift. A single reading includes whatever the scene did across the cut β€” the frames either side are ~0.9 s apart in scene time β€” so treat one number as an upper bound; to isolate the seam itself, render twice from the same seed and cache changing only `tone_compensate`, and compare.
792
 
793
- **H3 Continuity State** β€” `continuity_state` (STRING) out. **Setting only**: `setting_locked` / `setting_context` / `setting_mutable`. Characters belong in `ref_plan`.
 
 
 
 
794
 
795
- ## Docs
796
 
797
- [`CHANGELOG.md`](CHANGELOG.md) is what 2.0.0 contains, written for users. [`CLAUDE.md`](CLAUDE.md) is the current map of the pack if you are changing it. `docs/DEVLOG.md` is the engineering log. `docs/HANDOVER_*.md`, root `HANDOVER.md`, and `BETA_NOTES.md` are historical session notes β€” do not take them as the state of this release.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
  ![Six consecutive frames across a join](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/seam-frames.png)
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
+ ![The editor's tabs, with SCRIPT open on a ten-shot plan](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/editor-tabs.png)
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
  ![The WRITE bar](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/write-panel.png)
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
+ ![Scorecard across the ten runs](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/tester-scorecard.png)
 
 
 
 
 
 
 
 
 
 
 
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
+ ![Background detail across hops](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/tester-background-drift.png)
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
+ ![Seam report chart](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/seam-report.png)
 
 
 
 
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
+ ![The MEDIA tab with a reference clip loaded](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/reference-clip.png)
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
+ ![`lock` and the range button on the shot cards](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/shot-lock-range.png)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ![Contact sheet](https://media.githubusercontent.com/media/dntpi/ComfyUI-Hand-Tie-Clips/main/docs/img/contact-sheet-vlog.png)
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.
docs/img/tester-background-drift.png ADDED

Git LFS Details

  • SHA256: 3cd74b9f2b9f894dab40c9dbb61213b4cbd2da3793f6ed7ef136620268e023d4
  • Pointer size: 131 Bytes
  • Size of remote file: 438 kB
docs/img/tester-scorecard.png ADDED

Git LFS Details

  • SHA256: 75414190752ecb3a3d8f811febac1e4e0e5d421b7296ff37c9461f39250e2f0d
  • Pointer size: 131 Bytes
  • Size of remote file: 329 kB