πͺ Int (Constrained)
An INT widget that won't let you type 83 frames
- INT
Wan generates video in 4n+1 frame chunks. Ask for 83 and it doesn't error - it quietly rounds down to 81, and you find out when the clip comes back short. VACE Join has the same deal: context_frames, replace_frames and new_frames all have to be multiples of 4.
Int (Constrained) is the pack's answer to that class of mistake. It's a plain INT value node - one number in, one INT out - except you attach a min, a max and a step, and it refuses to hold anything off that grid. The canvas snaps; the backend snaps again at execution, which is the part that matters.
What it actually is
Core ComfyUI already has plumbing for this: PrimitiveInt, a typed value node you fan out so a frame count is defined once instead of retyped into five widgets. What it lacks is bounds - you get free rein, and the downstream node is the one that complains.
This is that with the guardrails welded on. Reach for it anywhere a number has a rule attached: a frame count that has to satisfy VACE's multiple-of-4 convention, a resolution on a 16px grid. It's the second of the pack's two pure-arithmetic nodes, alongside MiniMax H3 Duration; everything else here is video prep, and this is what you wire into it.
How the snapping works
All the behaviour falls out of one function, snap_int(value, lo, hi, step):
- The step grid is anchored at
minwhen a min is set, and at 0 otherwise. Somin=1, step=4gives you 1, 5, 9, 13 - not 4, 8, 12. - A value exactly halfway between two grid points rounds up.
- The result clamps to the outermost grid points that still fall inside
[min, max]. Withmin=1, max=10, step=4the legal values are 1, 5 and 9; ask for 10 and you get 9. - If
minis greater thanmax, or no multiple ofsteplands in the range at all, execution fails with an error that names the problem.
web/constrained_int.js mirrors that maths - both files carry a "keep these in step" comment - and overrides the widget's bounds so arrows, drag and typing land on the grid. It re-snaps on workflow load, so a saved off-grid value shows what the backend will really output.
Where min, max and step are hiding
They're not on the node, which trips everyone up.
Under Nodes 2.0 they are advanced inputs: open the properties panel and turn on Show advanced inputs, or pick Show Advanced from the node's right-click menu. On the legacy canvas they're hidden outright and mirrored into the node's properties, edited through the legacy Properties Panel (right-click the node) as min, max and step - leave a field empty for no limit.
Widgets stay the source of truth either way - they're what gets serialized and sent to the backend, so hand-editing the mirrored property in a saved workflow file won't stick.
Wiring it up
Four INT inputs: value (the number, default 0), min (schema name min_value), max (max_value), and step - the snap multiple, default 1, which means no snapping at all. One output, INT, which accepts any INT socket: a converted frame-count widget on a sampler, context_frames on VACE Join, the length input on the other nodes in this pack.
One quirk: value has control-after-generate pinned to fixed - a value source, not a dice roller, so there's no randomize/increment dropdown like a KSampler seed gets.
Install
No dependencies, no model downloads - pyproject.toml lists dependencies = [], a real selling point in a Wan workflow where half the help threads are people untangling custom-node installs. From ComfyUI Manager, search Wan VACE Prep. Manually:
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/stuttlepress/ComfyUI-Wan-VACE-Prep
Restart ComfyUI and it shows up as πͺ Int (Constrained) under Wan VACE Prep/utils.
The author is goddess_peeler, of Wan VACE Clip Joiner fame; this node was built to replace the "square meters of awkward node math" in that pipeline. Since v1.1.0 the pack targets ComfyUI's V3 node API (comfy_api.latest), so it wants a reasonably current ComfyUI; v1.0.25 is the last legacy release.
Troubleshooting
Can't find min, max or step. Hidden by design - see above. Nothing is broken.
Setting min to -9007199254740991 (or max to 9007199254740991) does nothing. Those are the "unset" sentinel, not a bound: the widget round-trips through JavaScript doubles, so the pack uses Β±(2^53 β 1) to mean "no limit". You can't use them as real constraints.
The step seems to do nothing. Check whether min is set. Without one, the grid counts from 0 - usually right for multiples-of-4 rules, but it catches people who expected counting from the current value.
Execution fails with an error about min, max or step. Your range contains no valid value: min above max, or a step too coarse to land between them.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| value | INT | 0-9223372036854776000β9223372036854776000 | β |
| min_value | INT | -9007199254740991-9007199254740991β9007199254740991 | Lowest allowed value. Also anchors the step grid when set. |
| max_value | INT | 9007199254740991-9007199254740991β9007199254740991 | Highest allowed value. |
| step | INT | 11β9007199254740991 | Values snap to the nearest multiple of step (counted from min when min is set, else from 0). 1 means no snapping. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| INT | INT | β |