Repository navigation
Description
Idea, not approved work. Recorded from a comparative review; needs a decision before any implementation.
Gap
restore-*, refine-clip and upscale-clip are capped at one model bucket, and refine-clip trims a longer source. Nothing splits a long source into model-sized windows and joins the results again. dissolve_videos crossfades seams between separate clips only.
Idea
A window/join task pair, in pixel space:
- Split: each window is the last
overlapframes carried from the previous window plusstridenew frames.- The first window's prefix is frame 0 repeated.
- A short final window is padded with its last frame.
- The window length sits on the model grid (LTX 8n+1, H3 17n+5).
- Audio: slice the audio per window, with silence for the synthetic frames.
- Join: blend each window's head into the previous window's held tail with cosine, smoothstep or linear weights. Every source frame is emitted exactly once.
This pairs naturally with list-driven per_entry steps.
Relation to #597
#597 is latent-space spatial tiling inside one denoise. This is the complementary pixel-space temporal version, and it is far cheaper to build.
Their file: VRGDG_OverlapMetaBatch.py.
Source: concept observed in vrgamegirl19/comfyui-vrgamedevgirl (assessed 2026-10-05). That repo is under a source-available license that is incompatible with our Apache-2.0: re-implement from the concept only, do not copy code, prompt text, or presets verbatim. File references below are to their repo, for understanding the mechanism.
Activity
dkackman commentedon Oct 5, 2026
Approved by Don (2026-10-05) at priority:2. Build it as a split/join task pair in pixel space, composed with per_entry steps. The window grid comes from the workflow's variable_constraints. Handing to the feature lead for a plan.
dkackman commentedon Oct 5, 2026
Plan v3: overlap windowing for long video sources (
window_video+join_windows)Feature plan by the lead agent, model
claude-opus-5-5viaanthropic, 2026-10-05. Design read againstdevelop@ 21cd845. v2 records Don's approval answers (Q1–Q3) and his change to the stage 3 preflight, re-read againstdevelop@ 837fd24. v3 (2026-10-06, from stage 3's build, #630): the template ships unpriced, a new stage 4 records the measuredper_entry, and the plugin version bump is dropped.Verdict: build smaller
restore-*,refine-cliporupscale-clip. The gain is real but prospective. Today a source over one LTX bucket (121 frames ≈ 5 s) can't be restored or refined at all;refine-cliptrims it.dw/server/routes/system.py:86,dw_mcp/tools_catalog.py:135,ui/src/lib/api.ts:185). There is no new MCP tool, no schema change and no surface-budget cost (tests/test_mcp_server.py:1555is unaffected). Upkeep is twoAudioVideoconstruction sites (tests/test_shots.py:73), argument domains, aTASKS.mdsection, anARCHITECTURE.mdrow and a seam/sync contract forassess_output. No security boundary changes and no new concept: it composesfor_each/gather:.slice_audio-style trims per run anddissolve_videosacross the results. But no task cuts a video at a start frame, anddissolve_videos' linear ramp shortens the cut by the overlap, so frames are lost. (b) Join only: gives nothing without the split.What "smaller" cuts relative to the idea text:
restore-*,refine-clip,upscale-clip) is LTX. The tasks are grid-agnostic, so H3 needs no task work, only a template. Brings it back: an H3 video-to-video template that needs it.ltx2/restore-longcomes first (Q1, decided). Brings the others back: a field report asking for a long refine or upscale.Problem and non-goals
Problem. LTX video-to-video templates take at most one model bucket. Nothing cuts a long source into overlapping model-sized windows and stitches the processed windows back to the source's exact length.
Non-goals:
for_eachexpands before validation (dw/for_each.py:61-124), so the window list is a workflow variable (Q3, decided).dw/for_each.py:43). At 121-frame windows with a 16-frame overlap that is 3,360 frames, about 140 s at 24 fps.Design, with corrections to the idea
Correction 1: "per_entry" is cost pricing, not iteration. In dw,
per_entryonly prices a list (dw/plan.py:248). Iteration isfor_eachover avariable:list, withitem:andgather:. The design composes those.Correction 2: the split can't return a list of windows. A list result fans every later
previous_result:out per item (dw/previous_results.py:56-123), and nothing can iterate a list made at run time. Sowindow_videoreturns one window per call, picked byindex. The workflow drives it withfor_eachovervariable:windows, a list whose entries are{name, index}.Correction 3: the grid comes from the existing
num_framesconstraint, not a new frame-snap argument. The window length isnum_frames, andstride = num_frames - overlap. The template bindsnum_framestovariable:num_frames, which the LTX constraint already holds to 8n+1 (dw/variable_constraints.py). This needs no new constraint plumbing and matches Don's "grid from variable_constraints".Correction 4: the join can't read the window plan from its inputs. Pipeline outputs don't carry their input's shot records (
dw/result.py:650-711). Sojoin_windowstakessource(the samevariable:source_video) and re-derives the plan from the source's frame count. That also gives it the source audio to re-attach.window_video(video, index, num_frames, overlap, fps=None)returns oneAudioVideoicovers source frames[i·stride − overlap, i·stride + stride).f2s(a)…f2s(b)for its real span[a, b), usingframes_to_samples(dw/task_domains.py:402).f2slength.loop_frames(dw/tasks/video_utils.py:163), because the LTX reference condition takes that form.overlap >= num_frames;overlap < 0;index < 0;indexwhose window starts at or past the source's last frame (index · stride >= source_frames).assessment=False).join_windows(videos, source, num_frames, overlap, curve="cosine", fps=None)returns oneAudioVideovideosisgather:<step>, the processed windows in list order.ceil(source_frames / (num_frames − overlap)). A different count is refused (Q3, decided); the refusal names both numbers and the list entries to add or drop. The rule has one home, a function indw/task_domains.pybesidedissolve_shortfalls, called by both the task at run time (stage 2) and its static check at validate time (stage 3).source_framesframes, each source frame exactly once.overlapframes are blended over the previous window's lastoverlapreal frames with weightw(t), wheret = (k+1)/(overlap+1). That is the same open ramp as_ramp(dw/tasks/dissolve_videos.py:259), so neither end is a hard copy.w(t)is cosine(1−cos πt)/2, smoothstep3t²−2t³, or lineart.num_frames, naming which window and its count;refine-clipdoubles it.source_frames. When the source has none, so does the output. The windows' own audio is discarded.videos, its span covers the frames it owns,overlap_framesis set on every seam, andstart_sample/num_samplesare cumulativef2s. That wayassess_output's seam probe treats the blends as dissolves (dw/tasks/assess.py:677), and the sync probe passes._CUT_TASKS(dw/server/catalog_shape.py:61).join_windows' static window-count check (Don, 2026-10-05)A validate-time check owned by
join_windows, in the shape ofdw/dissolve_frame_errors.py(a module of its own, wired intovalidation_errors):join_windowsstep, never to a named template, so a later windowed template gets it with no extra code.sourcenames a knowable file (asset:/output:, or a literal path the run may read;resolve_probe_path+probe_metadata, header only),num_framesandoverlapare literal after substitution, andvideosisgather:<step>whose member count is known afterfor_eachexpansion.len(gather list) ≠ ceil(source_frames / (num_frames − overlap)), naming the count needed, at thejoin_windowsstep's path, by calling the stage 2 rule.previous_result:source, a non-literalnum_frames) is left to the run-time refusal. Silence there is correct, asdissolve_frame_errorsdocuments.Surfaces touched
These are what
needs-approvalexists for, decided here:join_windows.list_tasks/get_task.for_each,item:andgather:as they are.Stages
Stages are built in order, and each one lands on
developon its own. Stages 1–2 are unreferenced surface until stage 3 wires them in.Stage 1:
window_video(server)TASK_ARGUMENT_DOMAINSentries (indexandoverlapnon-negative,num_framespositive), checked at run time likeslice_audio;AudioVideosite intests/test_shots.pyEXPECTED_SITESand indw/shots.py's list.docs/TASKS.md(Video Processing) and andocs/ARCHITECTURE.mdrow.asset:clip, e.g. 50 frames,num_frames=17,overlap=4, stride 13):get_task("window_video")describes it.index=0succeeds with exactly 17 frames.get_output_frames).index=3(frames 35–51) has 17 frames, and its last 2 repeat source frame 49.index=4is refused, naming the source's frame count and the last valid index (3).overlap=17is refused, and so is a negativeoverlap.validate_workflowwhere it can see the asset's length, or otherwise at run time with a clear error. The tester should accept either and note which.Stage 2:
join_windows(server)overlap_frames, the_CUT_TASKSentry, and the window-count rule as a shared function indw/task_domains.py.TASKS.mdandARCHITECTURE.md.for_each(no model step) and joins withgather:returns a video with the source's exact frame count.assess_outputraises no sync-drift finding, and its seams read as dissolves.curve="bogus"is refused.Stage 3:
join_windowsstatic count check,ltx2/restore-longtemplate and skill note (server + plugin)join_windows, as specified above: a module in the shape ofdw/dissolve_frame_errors.py, wired intovalidation_errors, calling the stage 2 rule. No template-specific logic in validate.workflows/templates/ltx2/restore-long.json. It holds:windowfor_each overvariable:windows;restorefor_each, therestore-deblurpipeline step withframes: previous_result:window;joinstep withjoin_windows(gather:restore, source=variable:source_video);costblock yet. A cost is measured, never derived, andrestore-deblurhas none to start from. Stage 4 adds it;windows(soindex) kept out ofcost_driversnow, so stage 4'sper_entrywon't be made unknown (validate_workflow: plan.estimate ignores a per-entry num_frames in a list-driven template (follow-up to #589) #593's_list_entry_field_shifted).dw:ltx-2.5skill on choosingwindowsfor a long source.join_windowsworkflow (not the template): short, exact and long lists; an unknowable source or a non-literalnum_framesstays silent.WORKFLOW_GUIDE.md's for_each example list, and the check inTASKS.md'sjoin_windowssection.tests/test_plugin_skills.py). The skill note reaches the tester throughorigin/develop.list_workflows(shape=…)lists the template, andvalidate_workflowon it is clean. Itsplan.estimateis unknown (nocostyet), and validate doesn't refuse on that.restore@wNstep and the whole run's minutes, with the window count and device, for stage 4.windowslist, the run succeeds with the source's frame count and audio length.get_output_framesaround each seam).assess_outputraises no sync-drift finding.validate_workflow, naming the needed count, before any GPU time.window_video/join_windowsworkflow (no model step, not the template) with a short list is also refused atvalidate_workflow: the check belongs to the task.Stage 4:
per_entrycost forltx2/restore-long(catalog)costentry from stage 3's acceptance run.minutesis the run's total, andper_entryis{variable: "windows", minutes: <one restore window plus its slice>, entries: 3}, measured as shots-batch's was (Add templates/minimax/shots-batch: list-driven H3 shot generation with no in-job assembly #352). If that run used a list other than the default 3, the total is the measured run's, andentriesand the default list match it.per_entry_problemsand the catalog-structure tests pass.validate_workflowonrestore-longwith 2, 3 and 4 windows returns aplan.estimatewithbasis: per_entrythat scales by window count.Risks
overlap(16) and the stage 3 visual check. If it shows, a follow-up could condition each window on the previous window's tail, which is a bigger feature.num_frames. H3 snaps up, and LTX should match.join_windowsrefuses rather than guesses.windowslist from the source length. The static check and the join's count refusal both name the right number._sample_span(dw/tasks/assess.py:255) and thefade_samplesrounding at:727don't follow shot records: dissolve_videos floors frame→sample, pair_audio/gain_audio round — re-pairing an episode shifts its shots by one sample #401's cumulative rule. If they trip stage 2's acceptance, the fix belongs to the implementer as its own bug, not to this plan.Decisions (Don, 2026-10-05)
ltx2/restore-long, built onrestore-deblur.join_windowsthat states its own rule. There is no template-specific logic in validate.dkackman commentedon Oct 5, 2026
Plan v1 approved by Don (2026-10-05). Q1-Q3 defaults accepted:
ltx2/restore-longcomes first;One change to stage 3: the validate-time window-count preflight should be a static check owned by
join_windows, which states its own rule:len(gather list) == ceil(source_frames / (num_frames - overlap))whensourceis an asset with a known length. Don't add template-specific logic to validate. Any later windowed template then gets the check with no extra code.42 remaining items
dkackman commentedon Oct 7, 2026
Back from the failed final check: fix-forward filed (lead,
claude-opus-5-5viaanthropic).C-F233 step 2 failed because stage 3's static window-count check doesn't fire on an
output:source. Plan v3 namesoutput:as knowable, so this is a build miss, not a plan gap, and no re-plan is needed. #666 is the fix-forward stage, and its acceptance is the existing C-F233 step 2. The parent returns to close-out once #666 closes. That turn updates the design record and hands the parent back for a full re-verify, since the bounce left C-F226, C-F228, C-F230's run part, C-F231 arms 1–3, C-F232 and C-F236 unrun.Merge branch 'docs/601-record-fix-forward' into develop (#601)
dkackman commentedon Oct 7, 2026
Close-out after the fix-forward: all five stages closed and verified (lead,
claude-opus-5-5viaanthropic).admit()now activates the request workspace's output root, so the static count check fires on anoutput:source. Shipped as merge53225a9e.docs/proposals/complete/overlap-windowing-complete.mdnow has a stage 5 section (why, cause, fix) and the bounce row. Docs-only commitaaef0dfb, merged todevelopas86f8b9a8. Nothing was deployed.Tester: verify: run every case whose
source:names one of this feature's stages (#628, #629, #630, #658, #666), and close the parent if they all pass. This is a full re-verify: the last bounce left C-F226, C-F228, C-F230's run part, C-F231 arms 1–3, C-F232 and C-F236 unrun. C-F234 and C-F235 are plan v2 cases under the retire/amend requests dkackman/harnest#59 and #64, and don't count.dkackman commentedon Oct 7, 2026
Verified: feature parent #601 (window_video / join_windows / restore-long). Tester: claude-opus-5-5 via anthropic. lem was on develop @ 86f8b9a. Every check below is an MCP call I made this session.
This run covers what the last bounce left unrun, plus re-checks of the core cases.
get_task("window_video")listsvideo,index,num_frames,overlapandfps;indexandoverlaparenon_negative,num_framesispositive. I cut 17-frame windows (num_frames 17, overlap 4) at indexes 0, 5 and 9 of ep6-cold-open (124 frames):analyze_audiopeak null). The audio over window frames 4–16 matches source frames 0–12: peak −5.2307, rms −19.728.previous_result:framesdirectly, with no fallback needed. The result has 17 frames at 24 fps, 960×544, with no audio stream. Frames 0 and 4 equal source frame 0, and frame 5 equals source frame 1.steps[1], in all three arms:run_workflowon the 4-window RT is refused with the same message, and no job id comes back.window_videoitself (job 918bb585dbf6): "window 4 would start at source frame 132 … the last window is index 3 (4 windows)".resize_rescaleaccepts a window's frame array, andjoin_windowsaccepts an explicitvideoslist. With w1 resized to 480×272 (job 302ac4c9733a): "needs every window at one size, the first's 960x544: window 1 is 480x272".curve: "bogus") is refused at validate, atsteps[1].task.arguments.curve, naming['cosine', 'smoothstep', 'linear'].previous_result:framesform. The join (job 6270093cd34b) has 124 frames at 24 fps, 960×544, 5.1667 s, with no audio stream. It matches the source by eye at frames 0, 24, 25 and 123.media.shotshas 5 entries tiling [0,124), withoverlap_frames: 8on w1–w4 and null sample fields.output:source. A 124-frameconcat_videosoutput with 4 windows is refused at validate atsteps[1], naming 5. This was the earlier bounce, fixed by #601 stage 5 (fix-forward): join_windows static count check misses output: sources #666.list_entries.windows: 5).previous_result:framessource, and anum_frames: "previous_result:nf"taken fromget_dict_value, both validate with no count error.dw:ltx-2-5skill (plugin tree at develop) namestemplates/ltx2/restore-longfor footage longer thannum_frames. It givesceil(source_frames / (num_frames - overlap))withindexfrom 0, and says validate names any entry to add or drop.get_task("join_windows")states the same rule forvideos.join_windowssection states it too, and says the validate-time check belongs to the task, "so anyjoin_windowsstep gets it".valid:falseat the argument's own path:join.sourceset to/etc/passwdor/nonexistent-dw-probe/x.mp4gets identical "resolves outside every directory" wording, with no count error and nothing beyond the string sent.file:///etc/passwdis refused as a non-http(s) URL.source_video: "../../../../etc/passwd"is refused at bothwindowandjoinfor its..segment.run_workflowon the/etc/passwdsource is refused with the same gate message, and no job is queued.restore-longon ep13 validates with 3 windows (basis: observed, 11.9 min).run_workflowwith 2 windows is refused before queuing: "needs 3 windows for a 282-frame source … got 2 - add 1 entry (index 2)".Not re-run this session: C-F227, C-F229 (beyond its 5-window validate inside C-F233), C-F315, C-F316 and C-F317. They passed in earlier verify sessions of this feature, C-F315's GPU run at b8aef18, and I'm relying on those results. C-F234 and C-F235 aren't counted (harnest#59, #64).
Minor, not filed:
indexfield is 4, so "index" is ambiguous there...path inwindow_video.videoreports the same error once per expanded member, all at the same path.All jobs from this session were deleted with
delete_output(job_id=…).docs(proposals): dkackman#601 - overlap windowing design record
docs(proposals): dkackman#601 - record the dkackman#666 fix-forward i…