feat(sampler): second pass gets an Enable switch; shorter hints

The pass was derived from whether the schedule field had content, so turning it
off meant deleting a schedule you wanted to keep. It has its own checkbox now
and the schedule is just configuration; enabling without one is reported rather
than silently ignored.

Trims the second-pass and ALG hints from four paragraphs to a sentence each.
The long-form detail stays behind the Details link, which is what it is for.
This commit is contained in:
digital-garbage
2026-08-13 15:15:58 +03:00
parent 8e3e23252f
commit 1b8ae08b08
2 changed files with 23 additions and 49 deletions
+5 -26
View File
@@ -242,7 +242,7 @@
{ name: "v2a_grad_scale", label: "Video→audio coupling", kind: "float", default: 1.0, min: 0.0, max: 4.0, step: 0.25, dependsOn: "joyai_audio_memory",
hint: "How much the carried audio follows the new shot's picture. 1.0 is the model's own behaviour and costs nothing; JoyAI uses 2.0; 0 makes audio ignore the video." },
{ name: "alg_anchor", label: "Blur the i2v anchor (ALG)", kind: "bool", default: false,
hint: "Hides the anchor image's fine detail during the first, noisiest steps so the shot can't shortcut to a near-still that just matches it — the usual fix for an anchored scene that barely moves. Works with whatever sampler you have wired, including a plain KSampler, and does nothing on a scene with no anchor image." },
hint: "Blurs the anchor's fine detail during the first, noisiest steps — the usual fix for an anchored scene that barely moves. No effect without an anchor image." },
{ name: "alg_anchor_strength", label: "Anchor blur strength", kind: "float", default: 2.0, min: 1.0, max: 4.0, step: 0.1, dependsOn: "alg_anchor",
hint: "How blurry the anchor gets while it's blurred. The paper says 2.5; 2.0 held character likeness noticeably better here." },
{ name: "alg_anchor_sigma_threshold", label: "Anchor blur sigma threshold", kind: "float", default: 0.975, min: 0.5, max: 0.999, step: 0.005, dependsOn: "alg_anchor",
@@ -634,22 +634,6 @@
const next = JSON.stringify({ ...cur, samplers: updatedSamplers });
if (quiet) S.setStudioInput("studio_settings", next);
else S.setStudioInputNow("studio_settings", next);
syncSecondPassFromSchedule(updatedSamplers, quiet);
}
// The second pass is driven by ONE thing: the schedule. Giving it one turns it on;
// taking it away turns it off. There is no cut and no re-entry point to configure —
// pass 1 always runs the main schedule in full and pass 2 always runs this one in full.
// A schedule is either typed into the low pass's Sigmas field or COMPUTED by picking a
// scheduler for it; both must count here, or picking one would be an inert control.
function syncSecondPassFromSchedule(samplers, quiet) {
const raw = String(samplers?.low?.sigmas || "").replace(/;/g, ",");
const vals = raw.split(",").map((v) => parseFloat(v.trim())).filter((v) => !isNaN(v));
const sched = String(samplers?.low?.scheduler || "use_user_sigmas");
const computed = sched !== "use_user_sigmas" && Number(samplers?.low?.steps || 0) > 0;
const set = quiet ? S.setSamplerInput : S.setSamplerInputNow;
// one number is not a schedule
if (computed || vals.length >= 2) set("second_pass", true);
else S.unsetSamplerInput("second_pass");
}
// ALG used to have two switches: the Distilled Flow panel's own alg_enabled, and the
@@ -705,15 +689,10 @@
moved.style.color = "var(--accent)";
algG.append(moved);
}
algG.append(hintEl("This is the only ALG anchor control — on Distilled Flow it runs inside "
+ "the sampler loop, on every other sampler through a denoiser proxy. The same blur for "
+ "guide and JoyAI-memory frames is under Experimental (“Blur i2v guides and JoyAI "
+ "memory”) — it has its own strength and window."));
// Second pass lives here rather than under Experimental: at its defaults the split is
// behaviour-neutral (pass 2 resumes from exactly the state pass 1 handed over), so it
// is a sampler setting, not a gamble. The only control is the schedule field in the
// panel above — enable and cut are derived from it (see syncSecondPassFromSchedule);
// all that is left here is the optional between-pass operation.
algG.append(hintEl("The same blur for guide and JoyAI-memory frames is under Experimental, "
+ "with its own strength and window."));
// Enable and schedule are in the panel above, with the sampler they belong to; this is
// the optional operation applied to the latent between the two passes.
const g = group(pane, "Second pass");
renderKnobList(g, st, ["second_pass_op"]);
const si = st.project.sampler_inputs || {};
+18 -23
View File
@@ -406,23 +406,19 @@
// reuses the sampler configured above, and rendering its algorithm settings here would
// offer knobs that quietly do nothing.
sectionTag(container, "Second pass");
const spOn = !!(window.Store?.get()?.project?.sampler_inputs || {}).second_pass;
row(container, "Enable", checkCtrl(spOn, "sp-second-on", (v) => {
window.Store?.setSamplerInputNow?.("second_pass", v);
}));
hint(container, "Samples each scene twice. Pass 1 runs the main schedule in full, then "
+ "pass 2 runs the one below in full — total steps are the two added up.");
const spSig = textCtrl(s.low.sigmas, "e.g. 0.812, 0.6, 0.35, 0.15, 0.0", "sp-second-sig",
(v) => { s.low.sigmas = v; mkSave(true)(); });
row(container, "Second pass schedule", spSig);
hint(container, "Fill this in and each scene is sampled in two passes; leave it empty and "
+ "it isn't. Comma-separated floats, high to low, ending at 0. Pass 1 "
+ "runs the main Sigmas schedule above in full, then pass 2 runs THIS "
+ "one in full, so the total is simply the two added up (a 9-step main "
+ "plus a 4-step second pass is 13). Pass 2 starts from the finished "
+ "clip: it is handed in as the latent and the sampler noises it to your "
+ "first sigma itself, exactly as any img2img does — no extra step in "
+ "between. That first sigma is therefore the strength dial, and it is "
+ "literal: at 0.8 pass 2 starts from 80% fresh noise over 20% of the "
+ "pass-1 picture and will rework the shot (and look soft if it has few "
+ "steps to resolve it); at 0.4 it is 40/60 and polishes; at 0.2 it is "
+ "nearly pure detail work. The rest of the list sets how many steps it "
+ "gets. To make pass 1 "
+ "shorter, shorten the schedule above; nothing here cuts it short.");
row(container, "Schedule", spSig);
hint(container, "Comma-separated, high to low, ending at 0. The FIRST sigma is the strength "
+ "dial: 0.8 reworks the shot, 0.4 polishes, 0.2 is detail work. The rest sets "
+ "how many steps pass 2 gets.");
// Pass 2 can be computed instead of typed, same switch as pass 1 — its SIGMAS is the
// only thing of the low pass that is wired, so a schedule chosen here IS pass 2.
@@ -430,19 +426,18 @@ hint(container, "Fill this in and each scene is sampled in two passes; leave it
if (s.low.steps === undefined) s.low.steps = s.low.ksampler_steps === undefined ? 4 : s.low.ksampler_steps;
const spScheds = ksamplerSchedulers(mkSave(false));
const spSchedCur = s.low.scheduler || KSAMPLER_USER_SIGMAS;
row(container, "Second pass schedule (computed)",
row(container, "Schedule (computed)",
selCtrl(spScheds.includes(spSchedCur) ? spScheds : spScheds.concat([spSchedCur]), spSchedCur,
"sp-second-sch", (v) => { s.low.scheduler = v; mkSave(false)(); }));
hint(container, "Leave on '" + KSAMPLER_USER_SIGMAS + "' to use the typed field above. Pick a "
+ "scheduler and pass 2 runs a computed schedule of the step count below instead "
+ "— which also TURNS THE SECOND PASS ON, whatever the field above says. A full "
+ "computed schedule starts at the model's top sigma, so pass 2 reworks the shot "
+ "rather than polishing it; for a polish, type the short low-sigma list above "
+ "and leave this alone.");
hint(container, "Leave on '" + KSAMPLER_USER_SIGMAS + "' to use the field above. A computed "
+ "schedule starts at the model's top sigma, so pass 2 reworks rather than polishes.");
if (spSchedCur !== KSAMPLER_USER_SIGMAS) {
row(container, "Second pass steps",
row(container, "Steps",
intCtrl(s.low.steps, 1, 200, "sp-second-steps", (v) => { s.low.steps = v; mkSave(true)(); }));
}
if (spOn && spSchedCur === KSAMPLER_USER_SIGMAS && !String(s.low.sigmas || "").trim()) {
hint(container, "Pass 2 is on but has no schedule — give it one above, or pick a computed one.");
}
}
window.SamplerPanel = { render, defaultSamplers, defaultPass };