Update H3 tools and fix Scout lazy bypass

This commit is contained in:
IAMCCS
2026-10-29 22:26:45 +01:00
parent 156485f365
commit 1aaa47f7e9
22 changed files with 1063 additions and 63 deletions
+7
View File
@@ -3161,3 +3161,10 @@ from .iamccs_moss_frame_output import (
)
NODE_CLASS_MAPPINGS.update(_MOSS_RESULT_CLASSES)
NODE_DISPLAY_NAME_MAPPINGS.update(_MOSS_RESULT_NAMES)
from .iamccs_minimax_h3_open_refine import (
NODE_CLASS_MAPPINGS as _OPEN_H3_REFINE_CLASSES,
NODE_DISPLAY_NAME_MAPPINGS as _OPEN_H3_REFINE_NAMES,
)
NODE_CLASS_MAPPINGS.update(_OPEN_H3_REFINE_CLASSES)
NODE_DISPLAY_NAME_MAPPINGS.update(_OPEN_H3_REFINE_NAMES)
+174
View File
@@ -0,0 +1,174 @@
# SPDX-FileCopyrightText: 2026 Carmine Cristallo Scalzi (IAMCCS)
# SPDX-License-Identifier: GPL-3.0-or-later
"""Read-only continuation diagnostics. Reports are evidence, not identity scores."""
from __future__ import annotations
import hashlib
import json
import logging
import math
import re
import shutil
import uuid
from pathlib import Path
import numpy as np
import torch
import torch.nn.functional as F
def settings_schema():
# Retired fields keep their positions so existing workflows still load.
retired = "Retired compatibility field. Ignored; no repair or reference injection runs."
return {
"h3_guard_mode": (["off", "monitor", "repair"], {"default": "off", "display_name": "GUARD - MODE", "tooltip": "Read-only Extended AV diagnostics. Legacy repair values resolve to Off with a warning."}),
"h3_guard_reference_memory": ("BOOLEAN", {"default": True, "display_name": "RETIRED - REFERENCE MEMORY", "tooltip": retired}),
"h3_guard_repair_denoise": ("FLOAT", {"default": 0.15, "min": 0.01, "max": 0.35, "step": 0.01, "display_name": "RETIRED - REPAIR DENOISE", "tooltip": retired}),
"h3_guard_repair_steps": ("INT", {"default": 6, "min": 1, "max": 30, "display_name": "RETIRED - REPAIR STEPS", "tooltip": retired}),
"h3_guard_detail_ratio": ("FLOAT", {"default": 0.65, "min": 0.1, "max": 1.0, "step": 0.01, "display_name": "GUARD - DETAIL LOSS THRESHOLD", "tooltip": "Diagnostic heuristic only. Camera, focus and motion changes can cause false alarms."}),
"h3_guard_motion_tolerance": ("FLOAT", {"default": 0.20, "min": 0.01, "max": 1.0, "step": 0.01, "display_name": "RETIRED - MOTION CHANGE LIMIT", "tooltip": retired}),
}
def effective_mode(config):
mode = config.get("mode", "off")
if mode == "repair":
logging.getLogger(__name__).warning(
"Continuity Guard Repair has been retired after unsuccessful validation; using Off. "
"Select Monitor for read-only diagnostics.")
return "off"
if mode not in {"off", "monitor"}:
raise ValueError("Unknown Continuity Guard mode")
return mode
def configuration(settings):
mode = effective_mode({"mode": settings.get("h3_guard_mode", "off")})
threshold = float(settings.get("h3_guard_detail_ratio", 0.65))
if not math.isfinite(threshold) or not 0 < threshold <= 1:
raise ValueError("Invalid Continuity Guard detail ratio")
return {"mode": mode, "detail_ratio": threshold}
def storage(output_directory, render_id):
if not render_id:
raise ValueError("Continuity Guard requires the R49 render identity")
key = hashlib.sha256(str(render_id).encode()).hexdigest()[:24]
path = Path(output_directory) / "IAMCCS" / "continuity_guard" / key
path.mkdir(parents=True, exist_ok=True)
return path
def conditioning_identity(render_id, index):
# R49 assigns the definitive, numbered name at the first native checkpoint.
if int(index) == 0:
return uuid.uuid4().hex
if not str(render_id or "").strip():
raise ValueError("Guard continuation requires its predecessor's checkpoint render identity")
return str(render_id)
def bind_checkpoint(output_directory, report, render_id):
"""Bind the first segment's provisional measurements to R49's final name."""
key = report["storage_key"]
if not isinstance(key, str) or not re.fullmatch(r"[0-9a-f]{24}", key):
raise ValueError("Invalid Continuity Guard storage key")
source = Path(output_directory) / "IAMCCS" / "continuity_guard" / key
destination = storage(output_directory, render_id)
if source == destination:
return
if int(report["segment_index"]) != 0:
raise ValueError("Guard continuation measurements do not match the checkpoint render identity")
for name in ("baseline.json", "segment_0000.json"):
path = source / name
if path.is_file():
shutil.copy2(path, destination / name)
if not (destination / "baseline.json").is_file():
raise ValueError("Guard first-segment baseline is missing before checkpoint binding")
write_report(destination / "checkpoint_identity.json", {"render_id": str(render_id), "provisional_storage_key": key})
def write_report(path, data):
temporary = path.with_suffix(".tmp")
temporary.write_text(json.dumps(data, indent=2, allow_nan=False), encoding="utf-8")
temporary.replace(path)
def analysis_frames(images):
# One analysis size for all segments; at most 48 frames, with uniform timing.
ids = torch.linspace(0, images.shape[0] - 1, min(48, images.shape[0]), device=images.device).round().long()
x = images[ids, ..., :3].movedim(-1, 1).float()
x = F.interpolate(x, size=(144, 256), mode="area")
return (x[:, 0] * 0.2126 + x[:, 1] * 0.7152 + x[:, 2] * 0.0722).clamp(0, 1).cpu().numpy()
def measure(images):
import cv2
gray = analysis_frames(images)
detail = [float(np.mean(np.abs(cv2.Laplacian(f, cv2.CV_32F)))) for f in gray]
light = gray.mean(axis=(1, 2))
intervals = max(1, images.shape[0] - 1) / max(1, len(gray) - 1)
flow = []
for a, b in zip(gray[:-1], gray[1:]):
field = cv2.calcOpticalFlowFarneback((a * 255).astype('uint8'), (b * 255).astype('uint8'), None, .5, 3, 15, 3, 5, 1.2, 0)
flow.append(float(np.median(np.linalg.norm(field, axis=-1))) / intervals)
# Native-resolution tiles prevent large textured regions from hiding local blur.
ids = torch.linspace(0, images.shape[0]-1, min(24, images.shape[0]), device=images.device).long()
native = images[ids, ..., :3].float().mean(-1).cpu().numpy()
tiles = []
for row in range(4):
for col in range(4):
patch = native[:, row*native.shape[1]//4:(row+1)*native.shape[1]//4,
col*native.shape[2]//4:(col+1)*native.shape[2]//4]
values = [float(np.mean(abs(cv2.Laplacian(f, cv2.CV_32F)))) for f in patch]
tiles.append(float(np.median(values)))
return {"local_detail": tiles, "detail": float(np.median(detail)), "luminance": float(np.median(light)),
"flicker": float(np.median(np.abs(np.diff(light, n=2)))) if len(light) > 2 else 0.0,
"motion": float(np.median(flow)) if flow else 0.0,
"frames": int(images.shape[0]), "analysis": "luma_texture_optical_flow_v1",
"limitations": "No semantic identity, intent or lip-sync certification."}
def assess(current, baseline, config):
ratio = current["detail"] / max(baseline["detail"], 1e-6)
exposure = abs(current["luminance"] - baseline["luminance"])
moving = current["motion"] / max(baseline["motion"], 0.02)
uncertain = exposure > 0.15 or moving < 0.4 or moving > 2.5
local_before = baseline.get("local_detail", [])
local_now = current.get("local_detail", [])
local_ratios = [b/max(a, 1e-6) for a,b in zip(local_before, local_now)]
# 10% local texture loss is a candidate trigger, never proof of degradation.
affected = [i for i,r in enumerate(local_ratios) if r < .90 and local_before[i] > .005]
loss = ratio < config["detail_ratio"] or bool(affected)
return {"detail_ratio": ratio, "local_detail_ratios": local_ratios, "affected_tiles": affected, "exposure_change": exposure, "motion_ratio": moving,
"texture_loss_detected": bool(loss and not uncertain),
"review_recommended": bool(uncertain or loss)}
def process(config, sampled, raw_frames, *, directory, index, head_frames, visible_frames):
"""Measure decoded frames without modifying video, audio or conditioning."""
if effective_mode(config) != "monitor":
return sampled, raw_frames, {"status": "off", "repair_attempts": 0}
visible = raw_frames[head_frames:head_frames + visible_frames]
current = measure(visible)
baseline_path = directory / "baseline.json"
report = {"schema": "iamccs.guard.report.v1", "segment_index": index,
"storage_key": directory.name,
"mode": config["mode"], "before": current, "repair_attempts": 0,
"audio_policy": "Original sampled audio preserved exactly", "status": "monitored"}
if index == 0:
write_report(baseline_path, current)
report["status"] = "baseline_recorded"
elif not baseline_path.is_file():
raise ValueError("Guard baseline missing. Start this render from its first segment.")
else:
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
verdict = assess(current, baseline, config)
report["assessment"] = verdict
if verdict["review_recommended"]:
report["status"] = "review_recommended"
write_report(directory / f"segment_{index:04d}.json", report)
sampled = {**sampled, "_iamccs_continuity_guard": report}
return sampled, raw_frames, report
+30 -3
View File
@@ -1426,7 +1426,15 @@ def _secondary_lora_settings(shotplan: dict[str, Any]) -> dict[str, Any]:
return settings if isinstance(settings, dict) else {"enabled": False}
def _apply_secondary_lora(model, shotplan: dict[str, Any], include_vfx=False):
def _secondary_lora_applies_to_segment(shotplan: dict[str, Any], chunk_index: int = 0) -> bool:
"""Scope the optional slot to temporal segments, including external resumes."""
settings = _secondary_lora_settings(shotplan)
return (not bool(settings.get("continuation_only", False))
or int(chunk_index) > 0
or _external_continuation_active(shotplan, int(chunk_index)))
def _apply_secondary_lora(model, shotplan: dict[str, Any], include_vfx=False, chunk_index=0):
"""Apply an independent model-only H3 LoRA without changing sampling.
The second slot is deliberately separate from Turbo. It never selects a
@@ -1447,6 +1455,8 @@ def _apply_secondary_lora(model, shotplan: dict[str, Any], include_vfx=False):
return model, f"off (optional H3 LoRA unavailable: {missing})"
return model, "off"
if not _secondary_lora_applies_to_segment(shotplan, chunk_index):
return model, "off (segment 1; second LoRA starts at segment 2)"
lora_name = str(settings.get("lora_name", "") or "").strip()
strength = float(settings.get("strength", 0.0) or 0.0)
if not lora_name or abs(strength) <= 1e-8:
@@ -1474,7 +1484,8 @@ def _apply_secondary_lora(model, shotplan: dict[str, Any], include_vfx=False):
f"Could not apply secondary MiniMax H3 LoRA '{lora_name}'. "
"Verify that it is compatible with the selected FL2VA/REF2VA base model."
) from exc
return patched, f"native model-only H3 LoRA {lora_name}@{strength:.2f}"
scope = "continuation-only" if settings.get("continuation_only", False) else "all-segments"
return patched, f"native model-only H3 LoRA {lora_name}@{strength:.2f} ({scope}, segment {int(chunk_index) + 1})"
def _turbo_sampler(shotplan: dict[str, Any]):
@@ -3070,6 +3081,11 @@ class IAMCCS_MiniMaxH3AtomicConditioningBackend:
prefix_frames=control_prefix_frames,
)
positive, refmod_report = _apply_h3_refmod(positive, shotplan)
guard_config = shotplan.get("continuity_guard", {})
if guard_config.get("mode", "off") == "monitor" and shotboard_task == "fl2va_extended_av":
from .iamccs_h3_continuity_guard import conditioning_identity
guard_id = conditioning_identity(render_id, segment_index)
motion_state["continuity_guard_render_id"] = guard_id
if legacy_actual_output_bridge:
motion_report += "; legacy_actual_output_bridge_ignored"
if motion_state["active"] and motion_state.get("strategy") == "reference_motion_carry":
@@ -3269,7 +3285,7 @@ class IAMCCS_MiniMaxH3GenerationBackendV2:
turbo_model, turbo_report = _apply_turbo_lora(model, shotplan)
fasth3_model, fasth3_report = _apply_fasth3_dense_lora(turbo_model, shotplan)
pdd_model, pdd_report = _apply_pdd_lora(fasth3_model, shotplan)
lora_model, secondary_lora_report = _apply_secondary_lora(pdd_model, shotplan, include_vfx=True)
lora_model, secondary_lora_report = _apply_secondary_lora(pdd_model, shotplan, include_vfx=True, chunk_index=chunk_index)
if turbo_enabled:
# Apply the selected Turbo LoRA without silently replacing any
# authored sampler, scheduler or AV shift. Presets populate those
@@ -3558,6 +3574,16 @@ class IAMCCS_MiniMaxH3GenerationBackendV2:
cleanup_report = _clean_vram_before_decode()
native_frames = comfy_nodes.VAEDecode().decode(vae=video_vae, samples=sampled)[0]
guard_report = {"status": "off"}
guard_config = shotplan.get("continuity_guard", {})
if guard_config.get("mode", "off") == "monitor" and shotplan.get("task_mode") == "fl2va_extended_av":
from .iamccs_h3_continuity_guard import process as process_guard, storage as guard_storage
guard_directory = guard_storage(folder_paths.get_output_directory(), motion_state["continuity_guard_render_id"])
sampled, native_frames, guard_report = process_guard(
guard_config, sampled, native_frames, directory=guard_directory, index=int(chunk_index),
head_frames=int(motion_state.get("trim_frames", 0)),
visible_frames=int(motion_state["export_frames"]))
LOG.info("IAMCCS Continuity Guard | chunk=%d | %s | report=%s", int(chunk_index)+1, guard_report["status"], guard_directory)
native_audio = VAEDecodeAudio.execute(vae=audio_vae, samples=sampled)[0]
if (
isinstance(sampled, dict)
@@ -3977,6 +4003,7 @@ class IAMCCS_MiniMaxH3GenerationBackendV2:
f"lipsync_lock={lipsync_lock_report} | "
f"pre_sample_cleanup={conditioning_cleanup} | "
f"terminal_closure={terminal_closure_report} | "
f"continuity_guard={guard_report['status']} | "
f"pre_decode_cleanup={cleanup_report} | native_last_frame=captured | "
f"resolution={resolution_contract} | "
f"pianosequenza={pianosequenza_report} | "
+1 -1
View File
@@ -214,7 +214,7 @@ class IAMCCS_MiniMaxH3LatentUpresSamplingR38:
shift_video = float(sampling.get("shift_video", 12.0))
shift_audio = float(sampling.get("shift_audio", 3.0))
turbo_model, turbo_report = _apply_turbo_lora(model, plan)
lora_model, secondary_lora_report = _apply_secondary_lora(turbo_model, plan)
lora_model, secondary_lora_report = _apply_secondary_lora(turbo_model, plan, chunk_index=segment_index)
shifted = MiniMaxH3SigmaShift.execute(model=lora_model, shift_video=shift_video, shift_audio=shift_audio)[0]
if str(plan.get("acceleration", "native")).lower() == "comfy_kitchen":
from comfy_extras.nodes_model_advanced import ModelAttentionBackend
+1 -1
View File
@@ -358,7 +358,7 @@ class IAMCCS_MiniMaxH3MaskedLoopGuidedSampler:
turbo_model, turbo_report = _apply_turbo_lora(model, shotplan)
fasth3_model, fasth3_report = _apply_fasth3_dense_lora(turbo_model, shotplan)
pdd_model, pdd_report = _apply_pdd_lora(fasth3_model, shotplan)
lora_model, secondary_report = _apply_secondary_lora(pdd_model, shotplan)
lora_model, secondary_report = _apply_secondary_lora(pdd_model, shotplan, chunk_index=chunk_index)
turbo = _turbo_settings(shotplan)
turbo_name = str(turbo.get("lora_name", "") or "").strip()
turbo_enabled = bool(
+78
View File
@@ -0,0 +1,78 @@
# SPDX-License-Identifier: GPL-3.0-or-later
"""Settings bridge for the stock-node Open-H3 delivery branch."""
import folder_paths
PREFIX = "h3_open_refine_"
SIGMAS = {
"gentle_3step": "0.8, 0.6857142857, 0.48, 0",
"reference_3step": "0.909375, 0.857143, 0.666667, 0",
}
def settings_schema():
loras = ["", *folder_paths.get_filename_list("loras")]
upscalers = ["", *folder_paths.get_filename_list("latent_upscale_models")] if "latent_upscale_models" in folder_paths.folder_names_and_paths else [""]
refiner = next((name for name in loras if name.lower().endswith("minimax_h3_open_regenerate_2k_refiner_rank_384_bf16.safetensors")), "")
distill = next((name for name in loras if name.lower().endswith("minimax_h3_fl2v_lightx2v_turbo_4step_v0.1_comfy.safetensors")), "")
upscaler = next((name for name in upscalers if name.lower().endswith("minimax_h3_latent_upscaler_3d_fp16.safetensors")), "")
fields = {
"enabled": ("BOOLEAN", {"default": False, "display_name": "OPEN-H3 · ENABLE REFINEMENT", "tooltip": "One native generation followed by Open-H3. Replaces the other upscale routes; requires the Open-H3 workflow branch."}),
"scale": ("FLOAT", {"default": 2.0, "min": 1.0, "max": 4.0, "step": 0.05, "display_name": "OPEN-H3 · SPATIAL SCALE", "tooltip": "Learned latent upscale. Output follows the native aspect ratio, rounded to the H3 grid. Full-resolution refinement can exceed low-VRAM capacity."}),
"model_name": (upscalers, {"default": upscaler, "display_name": "OPEN-H3 · LATENT UPSCALER"}),
"precision": (["fp16", "bf16", "fp32"], {"default": "fp16", "display_name": "OPEN-H3 · UPSCALER PRECISION"}),
"device": (["cuda", "rocm", "cpu"], {"default": "cuda", "display_name": "OPEN-H3 · UPSCALER DEVICE"}),
"temporal_chunking": ("BOOLEAN", {"default": True, "display_name": "OPEN-H3 · UPSCALER CHUNKING", "tooltip": "Chunks the latent upscaler only. The three-step H3 sampler still processes the complete target latent."}),
"lora_name": (loras, {"default": refiner, "display_name": "OPEN-H3 · REFINER LORA"}),
"strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "display_name": "OPEN-H3 · REFINER STRENGTH"}),
"distill_lora_name": (loras, {"default": distill, "display_name": "OPEN-H3 · DISTILLATION LORA", "tooltip": "Kijai's rank-384 file contains only the refiner. The supplied community recipe pairs it with LightX2V v0.1; it is not NVIDIA's complete rank-576 bundle. Leave blank when the selected refiner already includes distillation."}),
"distill_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "display_name": "OPEN-H3 · DISTILLATION STRENGTH"}),
"sigma_preset": (["gentle_3step", "reference_3step", "custom"], {"default": "gentle_3step", "display_name": "OPEN-H3 · NOISE SCHEDULE", "tooltip": "Gentle starts at 0.8. Reference starts at 0.909375 and can change identity or performance. Neither preset guarantees preservation."}),
"custom_sigmas": ("STRING", {"default": SIGMAS["gentle_3step"], "multiline": False, "display_name": "OPEN-H3 · CUSTOM SIGMAS", "tooltip": "Used only with Custom. Comma-separated Euler sigmas ending in zero."}),
"seed": ("INT", {"default": 777, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": False, "display_name": "OPEN-H3 · REFINE SEED"}),
"prompt": ("STRING", {"default": "", "multiline": True, "display_name": "OPEN-H3 · REFINE PROMPT", "tooltip": "Blank uses the current shot prompt. Describe the existing action and appearance; new instructions can redraw the performance."}),
}
return {PREFIX + name: spec for name, spec in fields.items()}
def configuration(settings):
return {name[len(PREFIX):]: settings.get(name, spec[1]["default"]) for name, spec in settings_schema().items()}
class IAMCCS_MiniMaxH3OpenRefineControl:
@classmethod
def INPUT_TYPES(cls):
return {"required": {"cine_linx": ("IAMCCS_SUPERNODE_LINX",), "segment_index": ("INT", {"default": 0, "min": 0, "forceInput": True})}}
# File names feed both legacy list-combos and V3 COMBO sockets.
RETURN_TYPES = ("BOOLEAN", "STRING", "FLOAT", "*", "COMBO", "COMBO", "BOOLEAN", "*", "FLOAT", "*", "FLOAT", "STRING", "INT", "BOOLEAN")
RETURN_NAMES = ("enabled", "prompt", "scale", "upscaler_name", "precision", "device", "temporal_chunking", "refiner_lora", "refiner_strength", "distill_lora", "distill_strength", "sigmas", "seed", "use_face_source")
FUNCTION = "resolve"
CATEGORY = "IAMCCS/MiniMax H3/Delivery"
def resolve(self, cine_linx, segment_index):
# Atomic imports Shotboard, which imports this module's settings schema.
from .iamccs_minimax_h3_atomic_backend import _resolve_shotplan
plan = _resolve_shotplan(cine_linx)
cfg = plan.get("open_h3_refine") or configuration({})
enabled = bool(cfg["enabled"])
if enabled and bool(plan.get("upscale_enabled")) and plan.get("upscale_mode", "off") != "off":
raise ValueError("Open-H3 replaces the other upscale routes. Select Open-H3 in Settings PRO Mode Assistant.")
if enabled and not cfg["lora_name"]:
raise ValueError("Select an Open-H3 refiner LoRA in Settings PRO Output.")
if enabled and not cfg["model_name"]:
raise ValueError("Select an H3 latent upscaler in Settings PRO Output.")
chunks = plan.get("chunks") or []
chunk = chunks[segment_index] if 0 <= segment_index < len(chunks) else {}
prompt = cfg["prompt"] or chunk.get("prompt") or plan.get("global_prompt", "")
sigmas = cfg["custom_sigmas"] if cfg["sigma_preset"] == "custom" else SIGMAS[cfg["sigma_preset"]]
seed = (int(cfg["seed"]) + int(segment_index)) % (1 << 64)
distill_strength = float(cfg["distill_strength"]) if cfg["distill_lora_name"] else 0.0
use_face_source = bool(plan.get("face_detailer_settings", {}).get("enabled", False)) and plan.get("task_mode") != "longvid_keyframe_continuous"
return (enabled, prompt, float(cfg["scale"]), cfg["model_name"], cfg["precision"], cfg["device"], bool(cfg["temporal_chunking"]), cfg["lora_name"], float(cfg["strength"]), cfg["distill_lora_name"], distill_strength, sigmas, seed, use_face_source)
NODE_CLASS_MAPPINGS = {"IAMCCS_MiniMaxH3OpenRefineControl": IAMCCS_MiniMaxH3OpenRefineControl}
NODE_DISPLAY_NAME_MAPPINGS = {"IAMCCS_MiniMaxH3OpenRefineControl": "MiniMax H3 · Open-H3 Refinement Settings"}
+35 -23
View File
@@ -20,6 +20,7 @@ from typing import Any
import folder_paths
import torch
from comfy_execution.graph import ExecutionBlocker
from .iamccs_minimax_h3_atomic_backend import (
H3_FPS,
@@ -78,14 +79,14 @@ def _apply_sparse(model, enabled: bool, budget: float, denser_edges: bool):
) from exc
def _active_sampling_model(model, plan: dict[str, Any]):
def _active_sampling_model(model, plan: dict[str, Any], chunk_index=0):
from comfy_extras.nodes_minimax_h3 import MiniMaxH3SigmaShift
sampling = plan.get("sampling") if isinstance(plan.get("sampling"), dict) else {}
shift_video = float(sampling.get("shift_video", 12.0))
shift_audio = float(sampling.get("shift_audio", 3.0))
turbo_model, turbo_report = _apply_turbo_lora(model, plan)
lora_model, secondary_report = _apply_secondary_lora(turbo_model, plan)
lora_model, secondary_report = _apply_secondary_lora(turbo_model, plan, chunk_index=chunk_index)
turbo = _turbo_settings(plan)
turbo_name = str(turbo.get("lora_name", "") or "").strip()
compatible, _ = _h3_lora_compatibility(turbo_name)
@@ -123,11 +124,11 @@ class IAMCCS_MiniMaxH3SeedCandidateR40:
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"positive": ("CONDITIONING",),
"latent": ("LATENT",),
"model": ("MODEL", {"lazy": True}),
"positive": ("CONDITIONING", {"lazy": True}),
"latent": ("LATENT", {"lazy": True}),
"cine_linx": (SUPERNODE_LINX_TYPE,),
"chunk_index": ("INT", {"forceInput": True}),
"chunk_index": ("INT", {"forceInput": True, "lazy": True}),
"candidate_index": ("INT", {"default": 0, "min": 0, "max": 3, "step": 1}),
}
}
@@ -137,7 +138,22 @@ class IAMCCS_MiniMaxH3SeedCandidateR40:
FUNCTION = "sample"
CATEGORY = CATEGORY
def sample(self, model, positive, latent, cine_linx, chunk_index, candidate_index):
@staticmethod
def _active(cine_linx, candidate_index):
prefix, _ = IAMCCS_MiniMaxH3ScoutDeliveryLazyRouterR43._selection(cine_linx)
settings = _scout_settings(_resolve_shotplan(cine_linx))
count = max(1, min(4, int(settings.get("candidate_count", 1) or 1)))
return prefix == "scout" and 0 <= int(candidate_index) < count
def check_lazy_status(self, cine_linx, candidate_index, **kwargs):
if not self._active(cine_linx, candidate_index):
return []
return [name for name in ("model", "positive", "latent", "chunk_index") if kwargs.get(name) is None]
def sample(self, model=None, positive=None, latent=None, cine_linx=None, chunk_index=0, candidate_index=0):
if not self._active(cine_linx, candidate_index):
blocker = ExecutionBlocker(None)
return blocker, blocker, 0, False, f"R40 candidate {int(candidate_index) + 1} bypassed"
from comfy_extras.nodes_custom_sampler import (
BasicGuider,
BasicScheduler,
@@ -148,26 +164,17 @@ class IAMCCS_MiniMaxH3SeedCandidateR40:
plan = _resolve_shotplan(cine_linx)
scout = _scout_settings(plan)
enabled = bool(scout.get("enabled", False))
count = max(1, min(4, int(scout.get("candidate_count", 1) or 1))) if enabled else 1
count = max(1, min(4, int(scout.get("candidate_count", 1) or 1)))
index = max(0, min(3, int(candidate_index)))
sampling = plan.get("sampling") if isinstance(plan.get("sampling"), dict) else {}
base_seed = int(sampling.get("seed", 0) or 0)
chunk_stride = int(sampling.get("seed_stride", 1) or 1)
scout_stride = max(1, int(scout.get("seed_stride", 1_000_003) or 1_000_003))
actual_seed = (base_seed + int(chunk_index) * chunk_stride + index * scout_stride) & 0xFFFFFFFFFFFFFFFF
if index >= count:
inactive_sampled = copy.copy(latent)
inactive_x0 = copy.copy(latent)
inactive_sampled["iamccs_r40_candidate_active"] = False
inactive_x0["iamccs_r40_candidate_active"] = False
report = f"R40 candidate {index + 1} inactive | configured candidates={count}"
return inactive_sampled, inactive_x0, actual_seed, False, report
_audit_lipsync_audio_lock(plan, latent, int(chunk_index))
cleanup_report = _release_conditioning_models(plan)
active_model, turbo_enabled, turbo_report, secondary_report, acceleration_report, sparse_report = (
_active_sampling_model(model, plan)
_active_sampling_model(model, plan, chunk_index=chunk_index)
)
noise = RandomNoise.execute(noise_seed=actual_seed)[0]
guider = BasicGuider.execute(model=active_model, conditioning=positive)[0]
@@ -319,8 +326,8 @@ class IAMCCS_MiniMaxH3SeedPreviewR40:
return {
"required": {
"latent": ("LATENT",),
"video_vae": ("VAE",),
"trim_frames": ("INT", {"forceInput": True}),
"video_vae": ("VAE", {"lazy": True}),
"trim_frames": ("INT", {"forceInput": True, "lazy": True}),
"max_preview_frames": ("INT", {"default": 49, "min": 1, "max": 209, "step": 1}),
"max_preview_width": ("INT", {"default": 512, "min": 128, "max": 1024, "step": 64}),
}
@@ -331,9 +338,14 @@ class IAMCCS_MiniMaxH3SeedPreviewR40:
FUNCTION = "decode"
CATEGORY = CATEGORY
def decode(self, latent, video_vae, trim_frames, max_preview_frames, max_preview_width):
def check_lazy_status(self, latent, **kwargs):
if not bool(latent.get("iamccs_r40_candidate_active", False)):
return torch.zeros((1, 144, 256, 3), dtype=torch.float32), "R40 candidate inactive"
return []
return [name for name in ("video_vae", "trim_frames") if kwargs.get(name) is None]
def decode(self, latent, video_vae=None, trim_frames=0, max_preview_frames=49, max_preview_width=512):
if not bool(latent.get("iamccs_r40_candidate_active", False)):
return ExecutionBlocker(None), "R40 candidate inactive"
import nodes as comfy_nodes
frames = comfy_nodes.VAEDecode().decode(vae=video_vae, samples=latent)[0]
@@ -563,7 +575,7 @@ class IAMCCS_MiniMaxH3LatentUpresSamplingR40:
sampling = plan.get("sampling") if isinstance(plan.get("sampling"), dict) else {}
seed = (int(shot_lab.get("stage2_seed", 0)) + int(segment_index) * int(shot_lab.get("stage2_seed_stride", 1))) & 0xFFFFFFFFFFFFFFFF
base, turbo_report = _apply_turbo_lora(model, plan)
base, lora2_report = _apply_secondary_lora(base, plan)
base, lora2_report = _apply_secondary_lora(base, plan, chunk_index=segment_index)
base, stage2_lora_report = _stage2_lora(base, shot_lab)
shifted = MiniMaxH3SigmaShift.execute(
model=base,
+17
View File
@@ -29,6 +29,8 @@ from .iamccs_h3_face_swap import settings_schema as face_swap_schema, face_swap_
from .iamccs_h3_character_swap_lora import settings_schema as character_swap_schema, swap_plan
from .iamccs_h3_vfx_edit import settings_schema as vfx_edit_schema
from .iamccs_h3_omni import configure as configure_omni
from .iamccs_h3_continuity_guard import settings_schema as continuity_guard_schema, configuration as continuity_guard_configuration
from .iamccs_minimax_h3_open_refine import settings_schema as open_h3_refine_schema, configuration as open_h3_refine_configuration
from .iamccs_minimax_h3_shotboard_core import (
H3_FPS,
@@ -3485,6 +3487,7 @@ class IAMCCS_MiniMaxH3ShotPlanner:
extended_av_boundary_polish_strength = EXTEND_BOUNDARY_POLISH_DEFAULT_STRENGTH
# Append-only Settings/PRO controls. They are intentionally NOT planner widgets,
# so historical Shotboard widget positions remain byte-for-byte compatible.
secondary_lora_continuation_only = False
upscale_link_to_native = False
upscale_link_factor = "640x384 -> 1280x768"
import hashlib
@@ -3647,6 +3650,7 @@ class IAMCCS_MiniMaxH3ShotPlanner:
secondary_lora_enabled = saved_settings.get("secondary_lora_enabled", secondary_lora_enabled)
secondary_lora_name = saved_settings.get("secondary_lora_name", secondary_lora_name)
secondary_lora_strength = saved_settings.get("secondary_lora_strength", secondary_lora_strength)
secondary_lora_continuation_only = bool(saved_settings.get("secondary_lora_continuation_only", False))
pdd_lora_name = saved_settings.get("pdd_lora_name", pdd_lora_name)
pdd_strength = saved_settings.get("pdd_strength", pdd_strength)
h3_controlnet_enabled = saved_settings.get("h3_controlnet_enabled", h3_controlnet_enabled)
@@ -4545,6 +4549,7 @@ class IAMCCS_MiniMaxH3ShotPlanner:
"duplicate_of_turbo": bool(secondary_lora_duplicate),
"lora_name": selected_secondary_lora,
"strength": float(secondary_strength),
"continuation_only": bool(secondary_lora_continuation_only),
"loader": "ComfyUI LoraLoaderModelOnly",
"source": "shotboard",
}
@@ -4765,6 +4770,7 @@ class IAMCCS_MiniMaxH3ShotPlanner:
"soft_video_curve": str(saved_settings.get("h3_continuation_soft_video_curve", "smoothstep") or "smoothstep"),
"soft_audio_ms": max(0.0, min(100.0, _finite_float(saved_settings.get("h3_continuation_soft_audio_ms", 15.0), 15.0, 0.0, 100.0))),
}
plan["continuity_guard"] = continuity_guard_configuration(saved_settings)
plan["refmod_settings"] = {
"schema": "iamccs.minimax_h3.refmod.v1",
"enabled": bool(saved_settings.get("h3_refmod_enabled", False)),
@@ -4802,6 +4808,7 @@ class IAMCCS_MiniMaxH3ShotPlanner:
"route": "face_detailer_then_optional_upscale" if bool(face_detailer_enabled) else "native_then_optional_upscale",
"source": "IAMCCS_ShotboardH3Settings" if _h3_settings_from_cine_linx(cine_linx) else "shotboard_defaults",
}
plan["open_h3_refine"] = open_h3_refine_configuration(saved_settings)
configure_omni(plan, saved_settings)
chunk_frames = [int(chunk.get("frame_count", 0) or 0) for chunk in plan.get("chunks", [])]
max_chunk_frames = max(chunk_frames, default=0)
@@ -5571,6 +5578,10 @@ class IAMCCS_MiniMaxH3NativeCheckpointSave:
)
trim_count = max(0, int(trim_head_frames or 0))
guard_report = sampled_latent.get("_iamccs_continuity_guard") if isinstance(sampled_latent, dict) else None
if isinstance(guard_report, dict):
from .iamccs_h3_continuity_guard import bind_checkpoint
bind_checkpoint(folder_paths.get_output_directory(), guard_report, active_render_id)
join_contract = _checkpoint_join_contract(cine_linx, current_segment, trim_count)
motion_context_pretrimmed = bool(join_contract["context_pretrimmed"])
overlap_stitch = join_contract["mode"] == "decoded_overlap"
@@ -7630,6 +7641,12 @@ class IAMCCS_ShotboardH3Settings:
"h3_omni_enabled": ("BOOLEAN", {"default": False,
"display_name": "REF2VA · FASTH3 OMNI 8 STEP",
"tooltip": "Full reference-conditioned checkpoint, not a LoRA. Select the Omni loader. Native PDD32, Euler/simple, 8 steps, shifts 12/3. Other modes keep their own model route."}),
**continuity_guard_schema(),
"secondary_lora_continuation_only": ("BOOLEAN", {
"default": False, "display_name": "Second LoRA: from segment 2",
"tooltip": "Apply the second LoRA only from segment 2 onward. A saved-AV checkpoint resume already counts as continuation. OFF preserves all-segment behavior. This controls temporal segments, not upscale passes.",
}),
**open_h3_refine_schema(),
})}
RETURN_TYPES = (SUPERNODE_LINX_TYPE,)
+79 -13
View File
@@ -893,12 +893,42 @@ def _normalise_ai_images(value: Any) -> list[dict[str, str]]:
return images
def _authored_h3_dialogue_lines(text: str) -> list[str]:
"""Protect exact utterances for referenced characters and standalone voices."""
return list(dict.fromkeys(re.findall(
r"(?:<Subject\s+\d+>\s*)?\(S\d+(?:\s*,\s*S\d+)*\)[^<>\r\n]{0,160}<d>\[[^\]]+\][\s\S]*?</d>(?:\s*<(?:scenetrans|cutoff)>)*",
str(text or ""), flags=re.IGNORECASE,
)))
def _official_h3_tag_rules(task_mode: str) -> str:
"""Shared by Global, selected-field rewrite, and compact local fallback."""
if task_mode in {"v2va_face_swap_lora", "ref2va_vfx_edit"}:
return "Use this mode's native aligned-edit grammar; do not append full-reference labels to its specialized edit prompt."
return (
"Official H3 tag authoring contract applies even when the rough request has no tags. "
"Format every supplied spoken or sung line as (S1) says <d>[Language] exact words</d>, "
"assigning stable (S1), (S2) and later speaker IDs by first actual vocal event, independently of subject numbering. "
"Keep voice, delivery and acting descriptions outside <d>; preserve the exact words and original language inside it. "
"Never invent dialogue. Use <scenetrans> only for speech genuinely continuing across an authored cut, "
"and <cutoff> only for speech intentionally interrupted by the clip ending; they are not generic transition decoration. "
"For full-reference modes, define reusable people, objects or environments with stable <Subject N> labels. "
"Bind their provenance to actual <Picture N> or <Video N> assets and use <Subject N> (Sx) for a referenced speaker. "
"A Subject is content, a Picture is an image/frame, a Video is a source or temporal reference, and an Audio is an active audio reference or reuse source. "
"Do not equate Subject and speaker numbers. Reference labels are category-specific and must resolve to available inputs or explicit existing context. "
"Do not populate unused slots, invent four subjects, or create Audio labels merely because a video contains sound. "
"Base T2VA/I2VA/FL2VA/L2VA keep their three-section grammar and applicable image alignment; "
"full-reference modes keep their six sections. Global describes output scope, not a plain-prose exemption from these rules."
)
def _assistant_instruction(
task_mode: str,
sections: dict[str, str],
user_direction: str = "",
target_keys: Any = None,
images: Any = None,
request_text: str | None = None,
) -> tuple[str, str]:
mode = _normalise_task_mode(task_mode or "t2va")
if mode not in MODE_SECTIONS:
@@ -962,6 +992,27 @@ def _assistant_instruction(
"When images are attached, analyze only the contribution named by each image role. An opening image governs the first frame; a closing image governs the last frame; identity, composition and style images govern only those named attributes. "
"Never mention unavailable media or claim to have seen a detail that is not visible."
)
if request_text is not None:
system = (
"You compile a filmmaker's brief into MiniMax H3 prompt fields. Return one JSON object with plain-string values, "
f"using exactly these selected keys: {selected}. "
"Write all generated descriptions in English, even when the brief is in another language. "
"Preserve supplied dialogue verbatim in its original language and retain exact reference tags and authored times. "
"Develop the brief into concrete, filmable staging: chronological visible movement, physical interactions, performance, "
"camera and location sound belong in their respective fields. Add only the minimal physical detail needed to stage the requested action. "
"Do not merely copy or translate the brief into one field or repeat it across multiple fields. "
"Do not invent characters, dialogue, extra actions, cuts, wardrobe, locations or a different story. "
"A scene field establishes the setting and subjects; shot_list describes the visible action in order; acting describes body mechanics; "
"camera describes the requested framing and movement; lighting describes the stated visual treatment; sound fields describe audible events. "
"For a single continuous action use one concise action passage, without an extra [Shot 1] heading. "
"Never emit timing placeholders such as MM:SS, HH:MM or 'end time', and never invent a timestamp. "
"Leave dialogue empty when none was supplied. Do not add music unless requested. "
"The user brief is the content authority. Existing context supplies only the unselected fields; previous drafts of selected fields are not instructions. "
f"Mode rule: {mode_rules} "
"Images provide visual evidence only for their assigned roles; do not claim unavailable references. "
"Return field contents only, without headings, markdown, commentary or reasoning."
)
system += "\n" + _official_h3_tag_rules(mode)
if len(system) > 24000:
raise RuntimeError("MiniMax assistant system prompt exceeds the 7000-token safety envelope")
user = json.dumps({
@@ -974,6 +1025,15 @@ def _assistant_instruction(
for item in visuals
],
}, ensure_ascii=False, indent=2)
if request_text is not None:
user = json.dumps({
"task_mode": mode,
"selected_sections": selected,
"request": str(request_text).strip(),
"direction": str(user_direction or "").strip(),
"existing_context": {key: value for key, value in filled.items() if key not in selected},
"visual_context": [{"slot": item["slot"], "role": item["role"], "name": item["name"]} for item in visuals],
}, ensure_ascii=False)
return system, user
@@ -1263,7 +1323,7 @@ def _compact_assistant_instruction(task_mode: str, sections: dict[str, str], use
"Return one JSON object only, without markdown. Every value must be one plain string. "
f"Use only these keys: {selected}. Omit a key when the user supplied no relevant content. "
"Preserve requested identity, action, camera direction, timestamps, language and quoted dialogue. "
f"{special}"
f"{special} " + _official_h3_tag_rules(mode)
)
user = json.dumps({
"task_mode": mode,
@@ -1316,6 +1376,7 @@ def rewrite_sections_with_ai(
target_keys: Any = None,
images: Any = None,
vision_model: str = "",
request_text: str | None = None,
) -> tuple[dict[str, str], dict[str, Any]]:
provider = str(provider or "ollama").strip().lower()
if provider == "lm_studio":
@@ -1343,7 +1404,7 @@ def rewrite_sections_with_ai(
visual_inputs[0].update(data=base64.b64encode(buffer.getvalue()).decode("ascii"), mime_type="image/jpeg")
except (OSError, ValueError):
pass
system, user = _assistant_instruction(task_mode, sections, user_direction, target_keys, visual_inputs)
system, user = _assistant_instruction(task_mode, sections, user_direction, target_keys, visual_inputs, request_text)
vision_observations = {}
selected_vision_model = str(vision_model or "").strip()
if _normalise_task_mode(task_mode) == "v2va_face_swap_lora" and visual_inputs and not selected_vision_model:
@@ -1537,6 +1598,10 @@ def rewrite_sections_with_ai(
filtered = {key: value for key, value in rewritten.items() if key in requested and value}
if not filtered:
raise RuntimeError("The AI did not return any valid filled MiniMax section")
if request_text and len(requested) > 1:
brief = re.sub(r"\W+", " ", request_text).strip().casefold()
if all(re.sub(r"\W+", " ", value).strip().casefold() == brief for value in filtered.values()):
raise RuntimeError("The AI echoed REQUEST instead of writing H3 fields. Your existing prompt was kept; retry with another writer model.")
mode_name = _normalise_task_mode(task_mode)
if mode_name == "v2va_face_swap_lora":
text = str(filtered.get("swap_prompt") or "").strip()
@@ -1589,16 +1654,17 @@ def rewrite_sections_with_ai(
filtered["shot_list"] = canonical_timeline
# An AI rewrite may paraphrase a Whisper transcript even when told not to.
# Restore the exact H3 dialogue line supplied by the user after parsing.
authored = "\n".join([str(user_direction or ""), *(str(value or "") for value in sections.values())])
dialogue_lines = list(dict.fromkeys(re.findall(
r"<Subject\s+\d+>\s*\(S\d+\):\s*<d>\[[^\]]+\][\s\S]*?</d>",
authored,
flags=re.IGNORECASE,
)))
authored = "\n".join([str(request_text or ""), str(user_direction or ""), *(str(value or "") for value in sections.values())])
dialogue_lines = _authored_h3_dialogue_lines(authored)
if mode_name == "fl2va" and requested.isdisjoint({"action", "shot_list", "dialogue"}):
# Global keyframe fields remain stable authority; utterances belong to locals.
dialogue_lines = []
if dialogue_lines:
target = "audio_dialogue_map" if "audio_dialogue_map" in requested else next(
(key for key in target_keys if key in requested), next(iter(requested))
) if isinstance(target_keys, list) else next(iter(requested))
if target in {"dialogue", "audio_dialogue_map", "multishot_dialogue_map"}:
filtered[target] = "\n".join(dialogue_lines)
for line in dialogue_lines:
if line not in "\n".join(filtered.values()):
filtered[target] = "\n".join(part for part in (filtered.get(target, ""), line) if part)
@@ -1720,7 +1786,7 @@ def build_visual_story_plan_with_ai(provider: str, base_url: str, model: str, ap
raise ValueError("Describe the relationship between the images")
visual_map = "\n".join(f"Picture {item['slot']}: {item['name']} · role={item['role']}" for item in visuals)
payload = _multimodal_json_chat(provider, base_url, model, api_key,
VISUAL_STORY_SYSTEM_PROMPT + ("" if visuals else
VISUAL_STORY_SYSTEM_PROMPT + "\n" + _official_h3_tag_rules(_normalise_task_mode(task_mode)) + ("" if visuals else
"\nNo images supplied. Do not invent Picture references. Create one local shot per explicitly numbered prompt in the brief. Preserve those slot numbers and their individual instructions; do not collapse them into the global prompt. If no slots are numbered, choose a short consecutive shot sequence. Global defines shared identity, location and atmosphere; local prompts carry the chronological development."),
f"CURRENT IAMCCS MODE: {task_mode}\nVISUAL MAP:\n{visual_map}\n\nRELATIONSHIP / STORY INTENT:\n{relation}",
visuals, temperature, timeout,
@@ -1766,11 +1832,10 @@ def build_visual_story_plan_with_ai(provider: str, base_url: str, model: str, ap
) if part)
# REQUEST → GLOBAL + LOCALS has a separate AI endpoint. Keep verbatim
# Whisper dialogue here too, even when the model paraphrases its output.
for line in dict.fromkeys(re.findall(
r"<Subject\s+\d+>\s*\(S\d+\):\s*<d>\[[^\]]+\][\s\S]*?</d>",
relation, flags=re.IGNORECASE,
)):
for line in _authored_h3_dialogue_lines(relation):
if line not in global_prompt and not any(line in shot["local_prompt"] for shot in shots):
if _normalise_task_mode(task_mode) == "fl2va":
raise RuntimeError("The H3 plan lost an authored dialogue line from its locals. The brief was preserved; retry the local plan.")
global_prompt = "\n\n".join(part for part in (global_prompt, line) if part)
return {"global_prompt": global_prompt,
"global_direction": global_direction,
@@ -2754,6 +2819,7 @@ def _register_prompter_routes() -> None:
payload.get("target_keys"),
payload.get("images"),
str(payload.get("vision_model", "")),
payload.get("request_text"),
)
return web.json_response({"ok": True, "sections": rewritten, "report": report})
except Exception as exc:
+104
View File
@@ -0,0 +1,104 @@
"""Retired Repair compatibility and read-only diagnostics; no inference."""
import importlib.util
from pathlib import Path
import sys
import torch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT.parents[1]))
spec = importlib.util.spec_from_file_location("guard_test_module", ROOT / "iamccs_h3_continuity_guard.py")
guard = importlib.util.module_from_spec(spec)
spec.loader.exec_module(guard)
def test_legacy_repair_disables_all_interventions(tmp_path):
config = guard.configuration({"h3_guard_mode": "repair", "h3_guard_reference_memory": True})
assert config['mode'] == 'off'
samples, frames = object(), object()
result = guard.process({'mode': 'repair'}, samples, frames, directory=tmp_path,
index=1, head_frames=0, visible_frames=1)
assert result[0] is samples and result[1] is frames
assert result[2]['status'] == 'off'
assert list(tmp_path.iterdir()) == []
def test_monitor_preserves_video_audio_mask_and_frames(tmp_path):
from unittest.mock import patch
samples = {'samples': object(), 'noise_mask': object(), 'metadata': {'seed': 42}}
frames = torch.rand(8, 12, 12, 3)
before = frames.clone()
config = guard.configuration({'h3_guard_mode': 'monitor'})
with patch.object(guard, 'measure', return_value={'detail': .1, 'luminance': .5, 'motion': .1}):
for index in (0, 1):
result, returned, report = guard.process(config, samples, frames,
directory=tmp_path, index=index, head_frames=2, visible_frames=6)
for key, value in samples.items():
assert result[key] is value
assert returned is frames and torch.equal(frames, before)
assert report['repair_attempts'] == 0
def test_darkening_or_motion_change_requires_review_not_automatic_repair():
config = guard.configuration({})
baseline = {'detail': .1, 'luminance': .5, 'motion': .1}
assert guard.assess({'detail': .04, 'luminance': .5, 'motion': .1}, baseline, config)['texture_loss_detected']
for changed in ({'detail': .04, 'luminance': .2, 'motion': .1}, {'detail': .04, 'luminance': .5, 'motion': .005}):
verdict = guard.assess(changed, baseline, config)
assert verdict['review_recommended'] and not verdict['texture_loss_detected']
def test_metrics_detect_synthetic_blur():
import torch.nn.functional as F
torch.manual_seed(5)
texture = torch.rand(1, 3, 144, 256)
movie = torch.cat([torch.roll(texture, i, -1) for i in range(8)]).movedim(1, -1)
blurred = F.avg_pool2d(movie.movedim(-1, 1), 7, stride=1, padding=3).movedim(1, -1)
a, b = guard.measure(movie), guard.measure(blurred)
assert b['detail'] < a['detail'] * .65
def test_storage_does_not_use_render_id_as_path(tmp_path):
path = guard.storage(tmp_path, '../../outside')
assert path.is_relative_to(tmp_path)
def test_invalid_configuration_is_rejected():
import unittest
with unittest.TestCase().assertRaises(ValueError):
guard.configuration({'h3_guard_detail_ratio': float('nan')})
def test_blank_root_identity_binds_to_numbered_checkpoint_and_continuation(tmp_path):
import json
provisional = guard.conditioning_identity('', 0)
source = guard.storage(tmp_path, provisional)
baseline = {'detail': .1, 'motion': .1, 'luminance': .5}
guard.write_report(source / 'baseline.json', baseline)
report = {'storage_key': source.name, 'segment_index': 0}
guard.write_report(source / 'segment_0000.json', report)
guard.bind_checkpoint(tmp_path, report, 'soldier_0042')
continuation_id = guard.conditioning_identity('soldier_0042', 1)
destination = guard.storage(tmp_path, continuation_id)
assert json.loads((destination / 'baseline.json').read_text()) == baseline
guard.bind_checkpoint(tmp_path, {'storage_key': destination.name, 'segment_index': 1}, continuation_id)
assert (source / 'baseline.json').is_file()
assert guard.conditioning_identity('', 0) != provisional
def test_missing_continuation_identity_and_invalid_binding_key_fail_closed(tmp_path):
import unittest
with unittest.TestCase().assertRaises(ValueError):
guard.conditioning_identity('', 1)
with unittest.TestCase().assertRaises(ValueError):
guard.bind_checkpoint(tmp_path, {'storage_key': '../../outside', 'segment_index': 0}, 'safe')
if __name__ == '__main__':
import tempfile
import inspect
tests = [(name, value) for name, value in globals().copy().items() if name.startswith('test_')]
for name, test in tests:
with tempfile.TemporaryDirectory() as directory:
test(Path(directory)) if inspect.signature(test).parameters else test()
print('PASS', name)
+57
View File
@@ -0,0 +1,57 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const source = fs.readFileSync(process.argv[2] || path.join(__dirname, '..', 'web', 'iamccs_prompter_ui.js'), 'utf8');
const start = source.indexOf(' const performAIRewrite = async');
const end = source.indexOf(' const renderLocalPrompts = () => {', start);
assert(start > 0 && end > start);
function fixture() {
const state = {request: 'Una donna posa una tazza.', task_mode: 't2va', sections: {scene: 'Old scene', camera: 'Old camera', acting: 'Tired hands'}};
const context = {
project: state, node: {}, MODE_META: {t2va: {sections: [['scene'], ['camera'], ['acting']]}},
activePromptKey: null, activePromptArea: null, rewriteBtn: {}, aiScope: {value: 'active_field'},
aiDirection: {value: ''}, aiStatus: {}, aiProvider: {value: 'qwen_native'}, aiBaseUrl: {value: ''},
aiModel: {value: '__workflow__'}, aiApiKey: {value: ''}, aiVisionModel: {value: ''},
aiTemperature: {value: '0.35'}, aiVisualFiles: [], isEditMode: () => false,
swapcharSignature: () => '', persistAI() {}, commit() {}, beginAiBusy: () => () => {},
buildAIImagePayload: async () => [], nativeClipConfig: () => ({profile: '4b_v3.1'}),
snapshotProject() {}, renderControls() {}, renderSections() {}, renderPreview() {},
workspace: {showReview() {context.reviewCount = (context.reviewCount || 0) + 1;}},
fetchAI: async (_url, options) => {
context.sent = JSON.parse(options.body);
return {ok: true, json: async () => ({ok: true, sections: {scene: 'A woman lowers a cup onto the table.', camera: 'Fixed eye-level framing.'}})};
},
};
vm.createContext(context);
vm.runInContext(source.slice(start, end) + '\nglobalThis.rewrite = performAIRewrite;', context);
return context;
}
(async () => {
const request = fixture();
assert.equal(await request.rewrite({narrativeRequest: request.project.request, directTargetKeys: ['scene', 'camera']}), true);
assert.equal(request.sent.request_text, 'Una donna posa una tazza.');
assert.deepEqual(request.sent.sections, {scene: '', camera: '', acting: 'Tired hands'});
assert.equal(request.project.sections.scene, 'A woman lowers a cup onto the table.');
assert.equal(request.project.sections.acting, 'Tired hands');
assert.equal(request.project.request, 'Una donna posa una tazza.');
assert.equal(request.reviewCount, 1, 'REQUEST must reveal the compiled H3 prompt');
const field = fixture();
assert.equal(await field.rewrite({directTargetKeys: ['scene', 'camera']}), true);
assert.equal(field.sent.request_text, null);
assert.equal(field.sent.sections.scene, 'Old scene');
assert.equal(field.reviewCount, undefined, 'a field rewrite keeps the current view');
const edited = fixture();
edited.fetchAI = async () => {
edited.project.sections.scene = 'User edit while waiting';
return {ok: true, json: async () => ({ok: true, sections: {scene: 'Late AI result'}})};
};
await edited.rewrite({narrativeRequest: edited.project.request, directTargetKeys: ['scene', 'camera']});
assert.equal(edited.project.sections.scene, 'User edit while waiting');
assert.match(edited.aiStatus.textContent, /Your edits were kept/);
console.log('PASS: REQUEST compiles from its brief, field rewrite retains context, late AI cannot overwrite edits');
})().catch(error => {console.error(error); process.exitCode = 1;});
+44
View File
@@ -37,5 +37,49 @@ class ScoutDeliveryRouterTests(unittest.TestCase):
self.assertEqual((prefix, reason), ("original", "Scout disabled"))
class ScoutBypassTests(unittest.TestCase):
def plan(self, enabled=False, count=4, mode="t2va"):
return {"task_mode":mode,"seed_scout_settings":{"enabled":enabled,"candidate_count":count}}
def test_disabled_preview_does_not_request_any_candidate_dependencies(self):
candidate=MODULE.IAMCCS_MiniMaxH3SeedCandidateR40()
with patch.object(MODULE,"_resolve_shotplan",return_value=self.plan()), patch.object(MODULE,"_release_conditioning_models",side_effect=AssertionError("Unexpected model cleanup")), patch.object(MODULE,"_active_sampling_model",side_effect=AssertionError("Unexpected sampling")):
for index in range(4):
self.assertEqual(candidate.check_lazy_status({},index),[])
result=candidate.sample(cine_linx={},candidate_index=index)
self.assertIsInstance(result[0],MODULE.ExecutionBlocker)
self.assertIsInstance(result[1],MODULE.ExecutionBlocker)
self.assertFalse(result[3])
def test_enabled_candidate_requests_its_heavy_inputs(self):
candidate=MODULE.IAMCCS_MiniMaxH3SeedCandidateR40()
with patch.object(MODULE,"_resolve_shotplan",return_value=self.plan(True,2)):
self.assertEqual(candidate.check_lazy_status({},0),["model","positive","latent","chunk_index"])
self.assertEqual(candidate.check_lazy_status({},1,model=object(),chunk_index=0),["positive","latent"])
self.assertEqual(candidate.check_lazy_status({},2),[])
self.assertIsInstance(candidate.sample(cine_linx={},candidate_index=3)[0],MODULE.ExecutionBlocker)
def test_temporal_route_bypasses_even_when_scout_requested(self):
candidate=MODULE.IAMCCS_MiniMaxH3SeedCandidateR40()
with patch.object(MODULE,"_resolve_shotplan",return_value=self.plan(True,4,"latent_go_ahead")):
self.assertEqual(candidate.check_lazy_status({},0),[])
self.assertFalse(candidate.sample(cine_linx={})[3])
def test_inactive_preview_never_requests_vae_or_allocates_an_image(self):
preview=MODULE.IAMCCS_MiniMaxH3SeedPreviewR40()
latent={"iamccs_r40_candidate_active":False}
self.assertEqual(preview.check_lazy_status(latent),[])
self.assertIsInstance(preview.decode(latent)[0],MODULE.ExecutionBlocker)
self.assertEqual(preview.check_lazy_status({"iamccs_r40_candidate_active":True}),["video_vae","trim_frames"])
def test_existing_socket_names_are_preserved_and_heavy_inputs_are_lazy(self):
candidate=MODULE.IAMCCS_MiniMaxH3SeedCandidateR40.INPUT_TYPES()["required"]
for name in ("model","positive","latent","chunk_index"):
self.assertTrue(candidate[name][1]["lazy"])
preview=MODULE.IAMCCS_MiniMaxH3SeedPreviewR40.INPUT_TYPES()["required"]
self.assertTrue(preview["video_vae"][1]["lazy"])
self.assertTrue(preview["trim_frames"][1]["lazy"])
if __name__ == "__main__":
unittest.main()
+89
View File
@@ -0,0 +1,89 @@
"""Exercise the real adapter loader's segment gate without GPU inference."""
import ast
from pathlib import Path
import sys
from types import ModuleType, SimpleNamespace
from typing import Any
from unittest.mock import Mock
import pytest
ROOT = Path(__file__).resolve().parents[1]
def backend(monkeypatch):
package = ModuleType("secondary_scope_test")
package.__path__ = []
swap = ModuleType(package.__name__ + ".iamccs_h3_character_swap_lora")
swap.apply_loras = lambda model, plan: model
monkeypatch.setitem(sys.modules, package.__name__, package)
monkeypatch.setitem(sys.modules, swap.__name__, swap)
loader = Mock(side_effect=lambda **kw: (object(),))
monkeypatch.setitem(sys.modules, "nodes", SimpleNamespace(
LoraLoaderModelOnly=lambda: SimpleNamespace(load_lora_model_only=loader)))
names = {"_external_continuation_active", "_secondary_lora_settings",
"_secondary_lora_applies_to_segment", "_apply_secondary_lora"}
tree = ast.parse((ROOT / "iamccs_minimax_h3_atomic_backend.py").read_text(encoding="utf-8"))
functions = [n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name in names]
ns = {"__package__": package.__name__, "Any": Any,
"folder_paths": SimpleNamespace(get_full_path=lambda *args: "registered-adapter"),
"_h3_lora_compatibility": lambda name: (True, "compatible")}
exec(compile(ast.Module(body=functions, type_ignores=[]), "<secondary-scope>", "exec"), ns)
return ns["_apply_secondary_lora"], loader
@pytest.mark.parametrize("segment", [0, 1, 3])
def test_legacy_all_segments_stays_enabled(monkeypatch, segment):
apply, loader = backend(monkeypatch)
original = object()
plan = {"secondary_lora": {"enabled": True, "lora_name": "test.safetensors", "strength": .5}}
result, report = apply(original, plan, chunk_index=segment)
assert result is not original
assert loader.call_count == 1
assert "all-segments" in report
def test_first_segment_skips_then_continuations_apply_without_mutating_plan(monkeypatch):
apply, loader = backend(monkeypatch)
original = object()
settings = {"enabled": True, "continuation_only": True, "lora_name": "test.safetensors", "strength": .5}
plan = {"secondary_lora": settings.copy()}
result, report = apply(original, plan, chunk_index=0)
assert result is original and loader.call_count == 0
assert "starts at segment 2" in report
for segment in (1, 2):
result, report = apply(original, plan, chunk_index=segment)
assert result is not original
assert "continuation-only" in report
assert loader.call_count == 2
assert plan["secondary_lora"] == settings
# Returning to the first segment must not inherit the patched model.
assert apply(original, plan, chunk_index=0)[0] is original
assert loader.call_count == 2
@pytest.mark.parametrize("checkpoint,enabled,expected", [("native.pt", True, 1), ("", True, 0), ("native.pt", False, 0)])
def test_checkpoint_resume_counts_as_continuation(monkeypatch, checkpoint, enabled, expected):
apply, loader = backend(monkeypatch)
plan = {"secondary_lora": {"enabled": True, "continuation_only": True,
"lora_name": "test.safetensors", "strength": .5},
"continuation_settings": {"enabled": enabled, "checkpoint": checkpoint}}
apply(object(), plan, chunk_index=0)
assert loader.call_count == expected
def test_disabled_slot_never_loads_on_continuation(monkeypatch):
apply, loader = backend(monkeypatch)
apply(object(), {"secondary_lora": {"enabled": False, "continuation_only": True}}, chunk_index=2)
assert loader.call_count == 0
def test_settings_schema_is_append_only():
tree = ast.parse((ROOT / "iamccs_minimax_h3_shotboard.py").read_text(encoding="utf-8"))
settings = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == "IAMCCS_ShotboardH3Settings")
method = next(n for n in settings.body if isinstance(n, ast.FunctionDef) and n.name == "INPUT_TYPES")
keys = [n.value for n in ast.walk(method) if isinstance(n, ast.Constant) and n.value == "secondary_lora_continuation_only"]
assert len(keys) == 1
# New widget remains after the final historical extension.
source = ast.get_source_segment((ROOT / "iamccs_minimax_h3_shotboard.py").read_text(encoding="utf-8"), method)
assert source.index('"secondary_lora_continuation_only"') > source.index("**continuity_guard_schema()")
+60
View File
@@ -0,0 +1,60 @@
import importlib.util
import sys
import types
import unittest
from pathlib import Path
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT.parents[1]))
PACKAGE = "_open_h3_test"
package = types.ModuleType(PACKAGE)
package.__path__ = [str(ROOT)]
sys.modules[PACKAGE] = package
spec = importlib.util.spec_from_file_location(PACKAGE + ".iamccs_minimax_h3_open_refine", ROOT / "iamccs_minimax_h3_open_refine.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
atomic = types.ModuleType(PACKAGE + ".iamccs_minimax_h3_atomic_backend")
atomic._resolve_shotplan = lambda value: value
class OpenH3SettingsTests(unittest.TestCase):
def resolve(self, settings=None, **plan):
payload = {"open_h3_refine": module.configuration(settings or {}), **plan}
with patch.dict(sys.modules, {atomic.__name__: atomic}):
return module.IAMCCS_MiniMaxH3OpenRefineControl().resolve(payload, 1)
def test_defaults_keep_existing_workflows_disabled(self):
self.assertFalse(self.resolve()[0])
def test_open_refine_does_not_stack_another_upscale(self):
with self.assertRaisesRegex(ValueError, "replaces the other upscale"):
self.resolve({"h3_open_refine_enabled": True}, upscale_enabled=True, upscale_mode="h3_ultimate_tiled")
def test_missing_assets_fail_explicitly(self):
with self.assertRaisesRegex(ValueError, "refiner LoRA"):
self.resolve({"h3_open_refine_enabled": True, "h3_open_refine_lora_name": ""})
def test_custom_schedule_prompt_and_segment_seed(self):
result = self.resolve({"h3_open_refine_sigma_preset": "custom", "h3_open_refine_custom_sigmas": "0.5,0.2,0", "h3_open_refine_seed": 100, "h3_open_refine_prompt": "Preserve the yawn."}, global_prompt="Other prompt")
self.assertEqual((result[1], result[11], result[12]), ("Preserve the yawn.", "0.5,0.2,0", 101))
def test_blank_distill_is_disabled_and_shot_prompt_wins(self):
result = self.resolve({"h3_open_refine_distill_lora_name": ""}, global_prompt="Global", chunks=[{}, {"prompt": "Local shot"}])
self.assertEqual(result[1], "Local shot")
self.assertEqual(result[10], 0.0)
def test_reference_schedule_and_output_contract(self):
result = self.resolve({"h3_open_refine_sigma_preset": "reference_3step"})
self.assertEqual(result[11], module.SIGMAS["reference_3step"])
self.assertEqual(len(result), len(module.IAMCCS_MiniMaxH3OpenRefineControl.RETURN_TYPES))
self.assertEqual(len(result), len(module.IAMCCS_MiniMaxH3OpenRefineControl.RETURN_NAMES))
def test_optional_face_source_is_retained_for_native_shots(self):
self.assertTrue(self.resolve(face_detailer_settings={"enabled": True}, task_mode="ref2va")[13])
self.assertFalse(self.resolve(face_detailer_settings={"enabled": True}, task_mode="longvid_keyframe_continuous")[13])
if __name__ == "__main__":
unittest.main()
+32
View File
@@ -0,0 +1,32 @@
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const assert = require('node:assert/strict');
const source = fs.readFileSync(path.join(__dirname, '../web/iamccs_h3_settings_pro_ui.js'), 'utf8');
const values = {upscale_enabled:true,upscale_mode:'h3_ultimate_tiled',h3_open_refine_enabled:false,h3_open_refine_sigma_preset:'reference_3step',rife_mode:'off',h3_omni_enabled:true,steps:8,seed:42};
const node = {id:811, properties:{}, widgets:Object.entries(values).map(([name,value])=>({name,value}))};
const widget = (n,key)=>n.widgets.find(w=>w.name===key);
let connected = ['IAMCCS_MiniMaxH3OpenRefineControl'];
const context = vm.createContext({node,widget,setValue:(n,key,value)=>{const w=widget(n,key);if(w)w.value=value;},connectedNodes:()=>connected.map(type=>({type})),nodeClass:n=>n.type,shotboardMode:()=> 'ref2va',modeFamily:()=> 'ref2va',document:{dispatchEvent(){}},CustomEvent:class{}});
const constants = source.slice(source.indexOf('const NODE_TYPE ='), source.indexOf('const widget ='));
const gate = source.slice(source.indexOf('function branchGate('), source.indexOf('function modeAvailability('));
const recipes = source.slice(source.indexOf('function applyRecipe('), source.indexOf('const FACE_REFINE_PRESET_HELP'));
vm.runInContext(constants + gate + recipes + 'globalThis.apply=applyRecipe;globalThis.layout=FUNCTIONAL_LAYOUT;globalThis.groups=GROUPS;',context);
assert.equal(context.apply(node,'open-h3-refine'),true);
assert.equal(widget(node,'h3_open_refine_enabled').value,true);
assert.equal(widget(node,'upscale_enabled').value,false);
assert.equal(widget(node,'upscale_mode').value,'off');
assert.equal(widget(node,'h3_open_refine_sigma_preset').value,'gentle_3step');
assert.equal(widget(node,'h3_omni_enabled').value,true);
assert.equal(widget(node,'steps').value,8);
assert.equal(widget(node,'seed').value,42);
context.apply(node,'native-delivery');
assert.equal(widget(node,'h3_open_refine_enabled').value,false);
connected=[];
assert.equal(context.apply(node,'open-h3-refine'),false);
assert.equal(widget(node,'h3_open_refine_enabled').value,false);
const section=context.layout.finish.find(([title])=>title.startsWith('OPEN-H3 REFINEMENT'));
assert.equal(section[1].length,14);
assert.ok(context.groups.find(g=>g.id==='finish').fields.includes('h3_open_refine_seed'));
assert.match(source,/addRecipe\(delivery, "OPEN-H3 REFINEMENT/);
console.log('PASS: Mode Assistant selection, separate Output box, lazy-branch gate and one-native-generation settings');
+25
View File
@@ -0,0 +1,25 @@
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const assert = require('node:assert/strict');
const source = fs.readFileSync(path.join(__dirname, '../web/iamccs_h3_settings_pro_ui.js'), 'utf8');
const start = source.indexOf('function openH3ResolutionEstimate(');
const end = source.indexOf('function openH3ResolutionSummary(', start);
const context = vm.createContext({widget: (node, name) => node.widgets.find(item => item.name === name)});
vm.runInContext(source.slice(start, end) + '\nglobalThis.estimate = openH3ResolutionEstimate;', context);
const node = {widgets: Object.entries({width: 640, height: 384, h3_open_refine_scale: 2, h3_open_refine_enabled: true}).map(([name, value]) => ({name, value}))};
const set = (name, value) => {node.widgets.find(item => item.name === name).value = value;};
let result = context.estimate(node);
assert.deepEqual([result.targetWidth, result.targetHeight, result.pixelGain], [1280, 768, 4]);
set('width', 1024); set('height', 576); set('h3_open_refine_scale', 2.5);
result = context.estimate(node);
assert.deepEqual([result.targetWidth, result.targetHeight, result.pixelGain], [2560, 1440, 6.25]);
set('width', 736); set('height', 416); set('h3_open_refine_scale', 1.5);
result = context.estimate(node);
assert.deepEqual([result.targetWidth, result.targetHeight], [1088, 640], 'match Python half-to-even, not Math.round');
set('h3_open_refine_enabled', false);
assert.equal(context.estimate(node).enabled, false);
set('width', 0);
assert.equal(context.estimate(node), null);
assert.match(source, /section\.append\(openH3ResolutionSummary\(node\)\)/);
console.log('PASS: native/target sizes, pixel gain, QHD, Python grid rounding and bypass state');
+52
View File
@@ -0,0 +1,52 @@
import json
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parent))
from test_prompter_native_qwen import prompter
class RequestCompilationTests(unittest.TestCase):
def test_request_is_authority_and_selected_drafts_are_not_context(self):
sections = {"scene": "Previous railway scene", "camera": "Old camera", "acting": "Keep her tired performance"}
system, user = prompter._assistant_instruction(
"t2va", sections, "Develop the requested shot", ["scene", "camera"],
request_text="Una donna posa una tazza sul tavolo.",
)
message = json.loads(user)
self.assertEqual(message["request"], "Una donna posa una tazza sul tavolo.")
self.assertEqual(message["existing_context"], {"acting": "Keep her tired performance"})
self.assertNotIn("Previous railway scene", user)
self.assertIn("in English", system)
self.assertIn("Never emit timing placeholders", system)
self.assertEqual(sections["scene"], "Previous railway scene")
def test_request_returns_generated_fields(self):
generated = {"scene": "A woman approaches a table holding a cup.", "camera": "A fixed eye-level composition holds the table in view."}
with patch.object(prompter, "native_json_chat", return_value=generated) as writer:
fields, report = prompter.rewrite_sections_with_ai(
"qwen_native", "", "__workflow__", "", "t2va", {"scene": "", "camera": ""},
target_keys=["scene", "camera"], request_text="Una donna posa una tazza sul tavolo.",
)
self.assertEqual(fields, generated)
self.assertEqual(report["rewritten_sections"], ["camera", "scene"])
self.assertIn('"request":', writer.call_args.args[2])
def test_request_echo_is_not_reported_as_success(self):
brief = "Una donna posa una tazza sul tavolo."
with patch.object(prompter, "native_json_chat", return_value={"scene": brief, "camera": brief}):
with self.assertRaisesRegex(RuntimeError, "echoed REQUEST"):
prompter.rewrite_sections_with_ai(
"qwen_native", "", "__workflow__", "", "t2va", {"scene": "", "camera": ""},
target_keys=["scene", "camera"], request_text=brief,
)
def test_single_field_rewrite_keeps_its_original_contract(self):
_, user = prompter._assistant_instruction("t2va", {"scene": "A quiet room"}, target_keys=["scene"])
self.assertEqual(json.loads(user)["rough_sections"], {"scene": "A quiet room"})
if __name__ == "__main__":
unittest.main()
+77
View File
@@ -0,0 +1,77 @@
import json
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parent))
from test_prompter_native_qwen import prompter
class OfficialTagAuthoringTests(unittest.TestCase):
def test_global_request_creates_speech_tags_even_when_input_has_none(self):
system, user = prompter._assistant_instruction(
't2va', {}, 'Compile Global', ['scene', 'dialogue'],
request_text='Una donna dice: Aspettami qui.',
)
self.assertIn('even when the rough request has no tags', system)
self.assertIn('<d>[Language] exact words</d>', system)
self.assertIn('independently of subject numbering', system)
self.assertIn('<scenetrans>', system)
self.assertIn('<cutoff>', system)
self.assertIn('Global describes output scope', system)
self.assertEqual(json.loads(user)['request'], 'Una donna dice: Aspettami qui.')
def test_reference_grammar_distinguishes_assets_content_and_speakers(self):
keys=[key for key,_ in prompter.MODE_SECTIONS['ref2va']]
system,_=prompter._assistant_instruction('ref2va',{},'',keys,request_text='The second character speaks.')
for tag in ['<Subject N>', '<Picture N>', '<Video N>', '<Audio N>']:
self.assertIn(tag,system)
self.assertIn('Do not populate unused slots',system)
self.assertIn('full-reference modes keep their six sections',system)
def test_compact_retry_retains_tag_rules(self):
system,_=prompter._compact_assistant_instruction('i2va',{},'She says hello.',['dialogue'])
self.assertIn('<d>[Language] exact words</d>',system)
def test_specialized_edit_grammars_are_not_overwritten(self):
for mode in ['v2va_face_swap_lora','ref2va_vfx_edit']:
self.assertIn('native aligned-edit grammar',prompter._official_h3_tag_rules(mode))
self.assertNotIn('Format every supplied',prompter._official_h3_tag_rules(mode))
def test_reference_output_preserves_independent_subject_and_speaker_ids(self):
line='<Subject 2> (S1) says <d>[Italian] Aspettami qui.</d>'
payload={'subject_definitions':'<Subject 2> is the woman in <Picture 1>.','detailed_description':'[Shot 1] '+line}
reply={'choices':[{'message':{'content':json.dumps(payload)}}]}
with patch.object(prompter,'_http_json',return_value=reply):
fields,_=prompter.rewrite_sections_with_ai('lm_studio','','local','','ref2va',{},target_keys=list(payload),request_text='Una donna dice: Aspettami qui.')
self.assertIn(line,fields['detailed_description'])
self.assertNotIn('<Subject 1>',fields['detailed_description'])
def test_global_and_locals_endpoint_uses_the_same_tag_contract(self):
payload={'global_prompt':'A quiet room.','shots':[{'slot':1,'local_prompt':'(S1) says <d>[Italian] Resta.</d>'}]}
with patch.object(prompter,'_multimodal_json_chat',return_value=payload) as chat:
plan,_=prompter.build_visual_story_plan_with_ai('lm_studio','','local','','Prompt 1: una donna dice Resta.','t2va',[])
self.assertIn('even when the rough request has no tags',chat.call_args.args[4])
self.assertIn('<d>[Italian] Resta.</d>',plan['shots'][0]['local_prompt'])
def test_request_utterance_with_says_and_cutoff_survives_a_bad_rewrite(self):
line='<Subject 2> (S1) says <d>[Italian] Non andare!</d><cutoff>'
reply={'choices':[{'message':{'content':json.dumps({'dialogue':'The woman says go.'})}}]}
with patch.object(prompter,'_http_json',return_value=reply):
fields,_=prompter.rewrite_sections_with_ai('lm_studio','','local','','t2va',{},target_keys=['dialogue'],request_text=line)
self.assertIn(line,fields['dialogue'])
self.assertNotIn('The woman says go.',fields['dialogue'])
def test_standalone_voice_and_cross_cut_markers_are_preserved(self):
line='(S2) speaks off-screen <d>[English] Stay here.</d><scenetrans>'
self.assertEqual(prompter._authored_h3_dialogue_lines(line),[line])
def test_keyframe_global_does_not_absorb_timed_dialogue_from_request(self):
line='(S1) says <d>[Italian] Resta.</d>'
reply={'choices':[{'message':{'content':json.dumps({'reference_use':'Keep the same opening frame.'})}}]}
with patch.object(prompter,'_http_json',return_value=reply):
fields,_=prompter.rewrite_sections_with_ai('lm_studio','','local','','fl2va',{},target_keys=['reference_use'],request_text=line)
self.assertNotIn('<d>',fields['reference_use'])
if __name__=='__main__':unittest.main()
+1 -3
View File
@@ -103,8 +103,6 @@ export function createPrompterWorkspace({ root, layout, left, center, right, mod
buttons.set(key, button);
}
function refresh() {
const request = center.querySelector('[data-workspace="direction"]');
if (request && center.firstElementChild !== request) center.prepend(request);
const hasShots = Boolean(center.querySelector('[data-workspace="shots"]'));
buttons.get("shots").disabled = !hasShots;
if (selected === "shots" && !hasShots) selected = "direction";
@@ -122,5 +120,5 @@ export function createPrompterWorkspace({ root, layout, left, center, right, mod
}
}
refresh();
return { refresh };
return { refresh, showReview() { selected = "review"; refresh(); } };
}
+88 -10
View File
@@ -9,6 +9,7 @@ const NODE_TYPE = "IAMCCS_ShotboardH3SettingsPro";
const SHOTBOARD_TYPE = "IAMCCS_MiniMaxH3ShotPlanner";
const BRIDGE_TYPES = new Set(["IAMCCS_CineH3Input", "IAMCCS_CineH3FunControlInput", "IAMCCS_MiniMaxH3FunControlInput"]);
const SHOTBOARD_OWNED = new Set(["task_mode"]);
const OPEN_H3_REFINE_FIELDS = ["h3_open_refine_enabled", "h3_open_refine_scale", "h3_open_refine_model_name", "h3_open_refine_precision", "h3_open_refine_device", "h3_open_refine_temporal_chunking", "h3_open_refine_lora_name", "h3_open_refine_strength", "h3_open_refine_distill_lora_name", "h3_open_refine_distill_strength", "h3_open_refine_sigma_preset", "h3_open_refine_custom_sigmas", "h3_open_refine_seed", "h3_open_refine_prompt"];
const AUTO_IMPORT_BLOCKED = new Set([
"global_prompt", "timeline_data", "image_paths", "frame_rate", "audio_mode",
"guide_policy", "min_guide_gap_seconds", "max_guides", "default_force", "promptrelay_epsilon",
@@ -38,11 +39,13 @@ const INTERNAL = new Set([
// compiler intentionally does not publish to CineLinX, so the PRO surface does
// not present them as a second source of truth.
const COMPATIBILITY_ONLY = new Set([
"h3_guard_reference_memory", "h3_guard_repair_denoise", "h3_guard_repair_steps", "h3_guard_motion_tolerance",
"frame_rate", "guide_policy", "min_guide_gap_seconds", "max_guides", "default_force",
"promptrelay_epsilon", "ltx_round_mode", "image_width", "image_height",
"image_resize_method", "image_multiple_of", "img_compression",
]);
const GROUPS = [
{ id: "continuity_guard", label: "CONTINUITY MONITOR", title: "Read-only Extended AV diagnostics. Repair has been retired; this monitor does not prevent degradation.", modeEngine: true, fields: ["h3_guard_mode", "h3_guard_detail_ratio"] },
{ id: "ahead", label: "AHEAD CONTROL ROOM", title: "LatentGoAhead seam controls", fields: [] },
{ id: "extension", label: "LONG TAKE · EXTEND", title: "Automatic native AV extension for one authored shot beyond 15 seconds", modeEngine: true, fields: ["extended_av_profile", "extended_av_custom_window_frames", "extended_av_custom_overlap_frames"] },
{ id: "continuation", label: "SAVED TAKE · CONTINUATION", title: "Start a new generation from a previously saved AV checkpoint", fields: ["h3_continuation_enabled", "h3_continuation_save_enabled", "h3_continuation_checkpoint", "h3_continuation_context_frames", "h3_continuation_handover_mode", "h3_continuation_manual_tail_frames", "h3_continuation_visual_handover", "h3_continuation_run_and_gun_enabled", "h3_continuation_run_and_gun_join", "h3_continuation_soft_video_frames", "h3_continuation_soft_video_curve", "h3_continuation_soft_audio_ms"] },
@@ -64,7 +67,7 @@ const GROUPS = [
{ id: "audio", label: "2 · AUDIO", title: "Audio authority and EXTEND seam polish", fields: ["audio_mode", "reference_audio_role", "voice_reference_picture_index", "extended_av_soft_audio_handover_ms", "extended_av_boundary_polish_ms", "extended_av_boundary_polish_strength"] },
{ id: "memory", label: "3 · MEMORY", title: "VRAM preset", fields: ["performance_profile", "motion_context_window_frames", "text_encoder_device", "h3_exact_profile", "h3_exact_chunk_rows", "h3_exact_precision_mode", "h3_exact_qkv_streaming", "h3_exact_attention_memory", "h3_clipproj_profile", "h3_clipproj_load_mode", "vram_clean_before_decode"] },
{ id: "sampling", label: "4 · SAMPLE", title: "Native H3 sampling", fields: ["seed", "seed_policy", "seed_stride", "steps", "sampler_name", "scheduler", "denoise", "shift_video", "shift_audio"] },
{ id: "speed", label: "5 · SPEED", title: "Acceleration recipe", fields: ["h3_attention_backend", "h3_veda_enabled", "h3_veda_predictor", "h3_veda_verbose", "acceleration", "turbo_mode", "turbo_lora_name", "turbo_strength", "turbo_sampler_mode", "fused_turbo_model_name", "fused_turbo_sigma_preset", "pdd_lora_name", "pdd_strength", "secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength", "ref_image_size", "sol_conditioning", "spectrum_profile", "h3_sla_sparsity", "h3_sla_dense_last_steps"] },
{ id: "speed", label: "5 · SPEED", title: "Acceleration recipe", fields: ["h3_attention_backend", "h3_veda_enabled", "h3_veda_predictor", "h3_veda_verbose", "acceleration", "turbo_mode", "turbo_lora_name", "turbo_strength", "turbo_sampler_mode", "fused_turbo_model_name", "fused_turbo_sigma_preset", "pdd_lora_name", "pdd_strength", "secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength", "secondary_lora_continuation_only", "ref_image_size", "sol_conditioning", "spectrum_profile", "h3_sla_sparsity", "h3_sla_dense_last_steps"] },
{ id: "direction", label: "6 · DIRECT · LEGACY", title: "Legacy Direct compatibility", fields: ["reference_role_1", "reference_role_2", "reference_role_3", "reference_role_4", "reference_video_role", "v2v_guide_mode", "v2v_source_range_policy", "v2v_source_offset_seconds", "v2v_source_fit", "v2v_source_end_policy", "v2v_audio_pairing", "flf_join_mode", "flf_overlap_frames", "flf_continuity_mode", "flf_continuity_tail_frames", "flf_continuity_audio", "longvid_guide_window_policy", "keyframe_joint_latent_new", "longvid_terminal_endpoint_mode", "longvid_pianosequenza_2stage_enabled"] }, // IAMCCS_LONGVID_ENDPOINT_STRATEGIES_V1
{ id: "control", label: "CONTROLNET", title: "H3 Fun ControlNet", contextual: "control", modeEngine: true, fields: ["h3_controlnet_enabled", "h3_controlnet_name", "h3_controlnet_kind", "h3_controlnet_preprocess_resolution", "h3_controlnet_strength", "h3_controlnet_start_percent", "h3_controlnet_end_percent", "h3_controlnet_frame_scope", "h3_controlnet_end_policy", "h3_controlnet_use_video_audio", "h3_controlnet_dwpose_torch_gpu"] },
{ id: "vfx_edit", label: "VFX EDIT", title: "REF2VA · VFX EDIT · aligned video guide", modeEngine: true, fields: ["h3_vfx_lora_name", "h3_vfx_lora_strength", "h3_edit_video_source", "upscale_enabled", "upscale_mode", "upscale_width", "upscale_height"] },
@@ -75,6 +78,7 @@ const GROUPS = [
{ id: "finish", label: "7 · OUTPUT", title: "Delivery", fields: ["h3_editor_enabled", "upscale_mode", "upscale_enabled", "upscale_width", "upscale_height", "h3_pixel_tiled_method", "h3_pixel_tiled_model_name", "h3_pixel_tiled_tile_size", "h3_pixel_tiled_overlap", "upscale_prompt", "upscale_sage", "upscale_seed_offset", "wan_upscale_denoise", "ltx_seam_safe", "ltx_detailer_enabled", "ltx_detailer_lora_name", "ltx_detailer_strength", "ltx_4k_enabled", "ltx_4k_quality", "ltx_looper_temporal_tile_size", "ltx_looper_temporal_overlap", "ltx_looper_guiding_strength", "ltx_looper_overlap_strength", "ltx_looper_cond_image_strength", "ltx_looper_horizontal_tiles", "ltx_looper_vertical_tiles", "ltx_looper_spatial_overlap", "h3_upres_model_name", "h3_upres_precision", "h3_upres_device", "h3_upres_keep_models_resident", "h3_fast_latent_temporal_chunk", "h3_fast_latent_stage2_window", "h3_fast_latent_stage2_overlap", "h3_fast_latent_stage2_guides", "h3_upres_seed", "h3_upres_steps", "h3_upres_denoise", "h3_upres_sampler", "h3_upres_scheduler", "h3_upres_temporal_chunk", "h3_upres_temporal_overlap", "h3_upres_anchor_strength", "h3_upres_tile_width", "h3_upres_tile_height", "h3_upres_overlap_width", "h3_upres_overlap_height", "h3_upres_fade_width", "h3_upres_fade_height", "h3_upres_min_tile_size", "h3_upres_overlap_mode", "h3_upres_overlap_blend", "h3_upres_rtx_enabled", "h3_upres_rtx_quality", "h3_upres_pixel_groups", "h3_upres_window_frames", "h3_upres_window_overlap", "h3_upres_pixel_method"] },
{ id: "advanced", label: "TECHNICAL", title: "Technical controls", dynamic: true, fields: [] },
];
GROUPS.find(group => group.id === "finish").fields.push(...OPEN_H3_REFINE_FIELDS);
const ENGINE_IDS = Object.freeze(GROUPS.filter((group) => group.modeEngine || ["ahead", "continuation", "refmod", "face_refine", "scout"].includes(group.id)).map((group) => group.id));
const ENGINE_ID_SET = new Set(ENGINE_IDS);
const ASSIGNED = new Set(GROUPS.flatMap((group) => group.fields));
@@ -116,6 +120,7 @@ const FUNCTIONAL_LAYOUT = {
],
finish: [
["OUTPUT", ["rife_mode", "upscale_enabled", "upscale_mode", "upscale_width", "upscale_height", "upscale_prompt", "upscale_sage", "upscale_seed_offset", "wan_upscale_denoise"]],
["OPEN-H3 REFINEMENT · AFTER ONE NATIVE GENERATION", OPEN_H3_REFINE_FIELDS],
["PIXEL SAFE · LOW VRAM", ["h3_pixel_tiled_method", "h3_pixel_tiled_model_name", "h3_pixel_tiled_tile_size", "h3_pixel_tiled_overlap"]],
["LTX DELIVERY", ["ltx_seam_safe", "ltx_detailer_enabled", "ltx_detailer_lora_name", "ltx_detailer_strength", "ltx_4k_enabled", "ltx_4k_quality", "ltx_looper_temporal_tile_size", "ltx_looper_temporal_overlap", "ltx_looper_guiding_strength", "ltx_looper_overlap_strength", "ltx_looper_cond_image_strength", "ltx_looper_horizontal_tiles", "ltx_looper_vertical_tiles", "ltx_looper_spatial_overlap"]],
["H3 2-PASS MODEL", ["h3_upres_model_name", "h3_upres_precision", "h3_upres_device", "h3_upres_keep_models_resident"]],
@@ -314,6 +319,7 @@ function selectDeliveryMode(node, mode) {
if (!deliveryModeAvailable(node,mode)) return false;
const option = H3_DELIVERY_MODES.find(([key]) => key === mode);
if (!option) return false;
setValue(node,"h3_open_refine_enabled",false,false);
if (["h3_fast_latent_2pass","h3_ultimate_tiled"].includes(mode)) {
const selected = String(widget(node,"h3_upres_model_name")?.value || "");
const installed = selected && !/^none$/i.test(selected) ? selected : firstChoice(node,"h3_upres_model_name",name => /minimax_h3.*upscaler/i.test(name));
@@ -330,6 +336,44 @@ function selectDeliveryMode(node, mode) {
}
const widget = (node, name) => (node?.widgets || []).find((item) => item?.name === name);
function openH3ResolutionEstimate(node) {
const width = Number(widget(node, "width")?.value), height = Number(widget(node, "height")?.value);
const scale = Number(widget(node, "h3_open_refine_scale")?.value);
if (![width, height, scale].every(value => Number.isFinite(value) && value > 0)) return null;
// Match the latent upscaler's Python round-to-even on its 32-pixel grid.
const align = value => {
const cells = value / 32, lower = Math.floor(cells);
return Math.max(16, (cells - lower === 0.5 ? lower + lower % 2 : Math.round(cells)) * 32);
};
const targetWidth = align(width * scale), targetHeight = align(height * scale);
return {width, height, scale, targetWidth, targetHeight,
widthGain: targetWidth / width, heightGain: targetHeight / height,
pixelGain: targetWidth * targetHeight / (width * height),
enabled: Boolean(widget(node, "h3_open_refine_enabled")?.value)};
}
function openH3ResolutionSummary(node) {
const box = document.createElement("div");
box.className = "h3p-context";
box.dataset.openH3Resolution = "true";
box.style.cssText = "margin:0 0 10px;line-height:1.6";
box.setAttribute("role", "status");
box.setAttribute("aria-label", "Open-H3 resolution and pixel gain");
const estimate = openH3ResolutionEstimate(node);
if (!estimate) { box.textContent = "Set the Native width, height and Open-H3 scale to preview the output size."; return box; }
const {width, height, targetWidth, targetHeight, widthGain, heightGain, pixelGain, enabled} = estimate;
const format = value => value.toLocaleString("en-US", {maximumFractionDigits: 2});
const size = document.createElement("b");
size.textContent = `${width} × ${height} → ${targetWidth} × ${targetHeight}`;
const gain = document.createElement("div");
gain.textContent = `${format(widthGain)}× width · ${format(heightGain)}× height · ${format(pixelGain)}× pixels`;
const note = document.createElement("div");
note.style.cssText = "color:#a9b7c5;font-size:9px";
note.textContent = `${enabled ? "Estimated Open-H3 output" : "Open-H3 OFF: configured target only; refinement disabled"}. Based on the Native canvas, rounded to 32 px. Pixel gain does not measure quality gain.`;
box.append(size, gain, note);
return box;
}
const nodeClass = (node) => String(node?.comfyClass || node?.type || "");
function hideWidget(item) {
if (!item || item._iamccsProHidden) return;
@@ -545,6 +589,10 @@ function connectedNodes(node) {
function branchGate(node, name) {
const classes = node._iamccsSettingsProConnectedClasses || connectedNodes(node).map(nodeClass);
const has = (pattern) => classes.some((value) => pattern.test(value));
if (name.startsWith("h3_open_refine_")) return {
available: has(/^IAMCCS_MiniMaxH3OpenRefineControl$/),
reason: "Load the R49 Open-H3 workflow with its connected refinement settings branch.",
};
if (name === "keyframe_joint_latent_new") return {
available: Boolean(widget(node, "extended_av_profile")),
reason: "LATENT NEW uses the B1 EXTEND backend.",
@@ -1222,6 +1270,7 @@ function selectedMemory(node) {
}
// IAMCCS FACE/UPSCALE MODE ASSISTANT V2
function selectedDelivery(node) {
if (widget(node, "h3_open_refine_enabled")?.value) return "open-h3-refine";
if (!widget(node, "upscale_enabled")?.value) return "native-delivery";
const mode = String(widget(node, "upscale_mode")?.value || "off");
if (!deliveryBranchGate(node, mode).available) return "native-delivery";
@@ -1274,8 +1323,14 @@ function deliveryModeAvailable(node, mode) {
function applyRecipe(node, recipe) {
const mode = shotboardMode(node), family = modeFamily(mode), set = (name, value) => setValue(node, name, value, false);
if (["native-delivery", "ultimate-tiled", "upres", "h3-pixel", "pixel-safe", "ltx-master", "ltx-per-shot", "rtx-final"].includes(recipe)) set("h3_open_refine_enabled", false);
if (["native", "pdd", "fasth3", "sla", "fused"].includes(recipe)) set("h3_omni_enabled", false);
if (recipe === "omni") {
if (recipe === "open-h3-refine") {
if (!branchGate(node, "h3_open_refine_enabled").available) return false;
set("upscale_enabled", false); set("upscale_mode", "off");
set("h3_open_refine_enabled", true); set("rife_mode", "off");
set("h3_open_refine_sigma_preset", "gentle_3step");
} else if (recipe === "omni") {
resetAcceleration(set); set("h3_omni_enabled", true); set("acceleration", "h3_sage"); set("h3_attention_backend", "h3_sage");
set("steps", 8); set("sampler_name", "euler"); set("scheduler", "simple");
set("denoise", 1); set("shift_video", 12); set("shift_audio", 3);
@@ -1834,6 +1889,7 @@ function mount(node) {
const targetEngine = modeEngineGroup(node, assistantModeKey(node, mode));
return GROUPS.filter((group) => {
if (group.id === "engine_reference_image") return false;
if (group.id === "continuity_guard") return true;
if (group.id === "ahead" && mode !== "latent_go_ahead") return false;
if (group.modeEngine && group.id !== targetEngine) return false;
return ["continuation", "refmod"].includes(group.id) || group.assistant || group.dynamic || group.fields.some((name) => widget(node, name));
@@ -1844,10 +1900,11 @@ function mount(node) {
return (node.widgets || []).map((item) => item?.name).filter((name) => name && !ASSIGNED.has(name) && !SHOTBOARD_OWNED.has(name) && !INTERNAL.has(name) && !COMPATIBILITY_ONLY.has(name) && fieldRelevant(name, mode));
}
function makeControl(name) {
const item = widget(node, name); if (!item) return null;
const item = widget(node, name); if (!item || (name.startsWith("h3_guard_") && COMPATIBILITY_ONLY.has(name))) return null;
const box = document.createElement("div"); box.className = "h3p-field";
const label = document.createElement("label"); label.textContent = human(name); box.append(label);
let control; const options = choices(item);
if (name === "secondary_lora_continuation_only") box.style.gridColumn = "1 / -1";
const label = document.createElement("label"); label.textContent = name === "secondary_lora_continuation_only" ? "Second LoRA: from segment 2" : human(name); box.append(label);
let control; const options = name === "h3_guard_mode" ? ["off", "monitor"] : choices(item);
if (options.length) {
control = document.createElement("select"); options.forEach((value) => control.add(new Option(
name === "h3_fast_latent_stage2_guides"
@@ -1859,6 +1916,19 @@ function mount(node) {
control = document.createElement("input"); control.type = typeof item.value === "number" ? "number" : "text"; control.value = item.value ?? "";
if (control.type === "number") { if (Number.isFinite(item.options?.min)) control.min = item.options.min; if (Number.isFinite(item.options?.max)) control.max = item.options.max; control.step = item.options?.step ?? "any"; }
}
if (name.startsWith("h3_guard_")) {
const supported = assistantModeKey(node) === "fl2va_extended_av";
const mode = String(widget(node, "h3_guard_mode")?.value || "off");
if (name === "h3_guard_mode" && mode === "repair") {
control.value = "off";
control.title = "Saved Repair mode is retired and runs as Off. Select Monitor for diagnostics.";
const notice = document.createElement("small");
notice.textContent = "Saved Repair mode retired - effective mode: Off.";
box.append(notice);
}
control.disabled = !supported || (name !== "h3_guard_mode" && mode !== "monitor");
if (!supported) control.title = "Continuity Guard currently supports Extended AV only.";
}
const gate = branchGate(node, name);
if (!gate.available) {
control.disabled = true;
@@ -1880,6 +1950,10 @@ function mount(node) {
const value = control.type === "checkbox" ? control.checked : control.type === "number" ? Number(control.value) : control.value;
if (name === "duration_seconds") setShotboardDuration(node, value);
else if (name === "audio_mode") setShotboardAudio(node, value);
else if (name === "h3_open_refine_enabled") {
if (value) applyRecipe(node, "open-h3-refine");
else setValue(node, name, false, false);
}
else if (name === "upscale_enabled") selectDeliveryMode(node,value ? (String(widget(node,"upscale_mode")?.value || "off") !== "off" ? widget(node,"upscale_mode").value : node.properties?.iamccs_delivery_route || "h3_ultimate_tiled") : "off");
else if (name === "upscale_mode") selectDeliveryMode(node,String(value));
else if (["upscale_width","upscale_height"].includes(name)) {
@@ -2181,6 +2255,7 @@ function mount(node) {
const delivery = document.createElement("div"); delivery.className = "h3p-recipes h3p-delivery-recipes";
addRecipe(delivery, "FACE REFINE · TOGGLE", "face-refine", true, selectedFace);
addRecipe(delivery, "NATIVE DELIVERY", "native-delivery", true, selectedFinish === "native-delivery");
addRecipe(delivery, "OPEN-H3 REFINEMENT · 3 STEP", "open-h3-refine", branchGate(node, "h3_open_refine_enabled").available, selectedFinish === "open-h3-refine");
addRecipe(delivery, "H3 DETAIL · 1-STEP REFINE", "ultimate-tiled", deliveryModeAvailable(node, "h3_ultimate_tiled"), selectedFinish === "ultimate-tiled");
addRecipe(delivery, "H3 2-PASS UPRES", "upres", branchGate(node, "h3_upres_model_name").available && deliveryModeAvailable(node, "h3_fast_latent_2pass"), selectedFinish === "upres");
addRecipe(delivery, "PIXEL SAFE · LOW VRAM", "pixel-safe", deliveryModeAvailable(node, "pixel_tiled_low_vram"), selectedFinish === "pixel-safe");
@@ -2191,6 +2266,7 @@ function mount(node) {
grid.append(delivery);
const detailNote = document.createElement("p");
detailNote.textContent = mode === "v2va_face_swap" ? "SAM3 crop swap supports Native / RTX delivery. H3 Detail needs a full-frame latent and is unavailable for this legacy crop mode." : "H3 DETAIL uses the proven SwapChar second-pass recipe for full-frame video modes: learned latent upscale, 1 step, SA Solver / simple, denoise 0.2, seed 777. Output exposes the shared seed, dimensions, model, sampling and tile controls. The native pass keeps its own settings.";
if (selectedFinish === "open-h3-refine") detailNote.textContent = "One native generation followed by Open-H3 refinement. Dedicated controls are in Output / Open-H3 Refinement. The source soundtrack is retained. Gentle sigma 0.8 still requires visual review of identity and acting; the high-resolution transformer pass is not tiled.";
grid.append(detailNote);
}
async function renderAssetLibrary(kind, grid) {
@@ -2667,10 +2743,10 @@ function mount(node) {
stack.append(stackText, reset);
recipes.append(presetGrid, stack);
}
const recipeSet = active === "memory" ? [["≤ 8–12 GB · 124F / 2048 ROWS", "vram8"], ["12–16 GB · 209F / 4096 ROWS", "vram12"], ["16–24 GB · 294F / 8192 ROWS", "vram16"], ["24 GB+ · 362F / 16384 ROWS", "vram24"]] : active === "speed" ? [["NATIVE QUALITY · 20 STEP", "native"], ["PDD · 8 STEP", "pdd"], ["FASTH3 · 6 STEP", "fasth3"], ["SLA · 4 STEP", "sla"], ["FUSED FAST · T2VA ONLY", "fused"]] : active === "control" ? [["ENABLE + SELECT INSTALLED MODEL", "control"]] : active === "face" ? [["SAM3 SUBJECT SWAP · SAFE DEFAULTS", "face"]] : active === "face_refine" ? [["FACE REFINE · TOGGLE", "face-refine"]] : active === "finish" ? [["NATIVE DELIVERY", "native-delivery"], ["H3 DETAIL · 1-STEP REFINE", "ultimate-tiled"], ["PIXEL SAFE · LOW VRAM", "pixel-safe"], ["H3 2-PASS UPRES", "upres"], ["H3 PIXEL REFINE", "h3-pixel"], ["LTX 2.3 MASTER", "ltx-master"], ["LTX 2.3 PER-SHOT", "ltx-per-shot"], ["RTX FINAL", "rtx-final"]] : [];
const recipeSet = active === "memory" ? [["≤ 8–12 GB · 124F / 2048 ROWS", "vram8"], ["12–16 GB · 209F / 4096 ROWS", "vram12"], ["16–24 GB · 294F / 8192 ROWS", "vram16"], ["24 GB+ · 362F / 16384 ROWS", "vram24"]] : active === "speed" ? [["NATIVE QUALITY · 20 STEP", "native"], ["PDD · 8 STEP", "pdd"], ["FASTH3 · 6 STEP", "fasth3"], ["SLA · 4 STEP", "sla"], ["FUSED FAST · T2VA ONLY", "fused"]] : active === "control" ? [["ENABLE + SELECT INSTALLED MODEL", "control"]] : active === "face" ? [["SAM3 SUBJECT SWAP · SAFE DEFAULTS", "face"]] : active === "face_refine" ? [["FACE REFINE · TOGGLE", "face-refine"]] : active === "finish" ? [["OPEN-H3 REFINEMENT · 3 STEP", "open-h3-refine"], ["NATIVE DELIVERY", "native-delivery"], ["H3 DETAIL · 1-STEP REFINE", "ultimate-tiled"], ["PIXEL SAFE · LOW VRAM", "pixel-safe"], ["H3 2-PASS UPRES", "upres"], ["H3 PIXEL REFINE", "h3-pixel"], ["LTX 2.3 MASTER", "ltx-master"], ["LTX 2.3 PER-SHOT", "ltx-per-shot"], ["RTX FINAL", "rtx-final"]] : [];
recipeSet.forEach(([label, id]) => {
const deliveryRecipe = ["native-delivery", "ultimate-tiled", "pixel-safe", "upres", "h3-pixel", "ltx-master", "ltx-per-shot", "rtx-final", "face-refine"].includes(id);
const deliveryAvailable = id === "upres"
const deliveryRecipe = ["open-h3-refine", "native-delivery", "ultimate-tiled", "pixel-safe", "upres", "h3-pixel", "ltx-master", "ltx-per-shot", "rtx-final", "face-refine"].includes(id);
const deliveryAvailable = id === "open-h3-refine" ? branchGate(node, "h3_open_refine_enabled").available : id === "upres"
? branchGate(node, "h3_upres_model_name").available && deliveryModeAvailable(node, "h3_fast_latent_2pass")
: id === "ultimate-tiled" ? deliveryModeAvailable(node, "h3_ultimate_tiled")
: id === "h3-pixel" ? deliveryModeAvailable(node, "h3_pixel_refine")
@@ -2757,6 +2833,7 @@ function mount(node) {
const grid = q("[data-grid]"); grid.className = "h3p-grid"; grid.replaceChildren();
if (mode === "v2va_face_swap_lora" && ["swap_lora", "overview"].includes(active)) swapDuration.render(grid);
let names = fieldNames(group, mode);
if (active === "finish") names = [...new Set([...names, ...OPEN_H3_REFINE_FIELDS.filter(name => widget(node, name))])];
if (active === "swap_lora" && !names.includes("duration_seconds")) names.unshift("duration_seconds");
if (active === "overview" && (mode !== "longvid_guides" || String(widget(node,"upscale_mode")?.value) === "h3_fast_latent_2pass")) {
names = names.filter((name) => !["upscale_link_to_native", "upscale_link_factor"].includes(name));
@@ -2774,7 +2851,7 @@ function mount(node) {
if (!check.checked) {
const selected = selectedAcceleration(node);
const fields = {omni: ["h3_omni_enabled", "h3_clipproj_profile", "h3_clipproj_load_mode"], native: [], pdd: ["pdd_lora_name"], fasth3: ["turbo_lora_name"], sla: ["turbo_lora_name", "h3_sla_sparsity"], fused: ["fused_turbo_model_name"]}[selected] || ["turbo_mode", "turbo_lora_name"];
names = names.filter(name => ["acceleration", "h3_attention_backend", "h3_veda_enabled", "h3_veda_predictor", "h3_veda_verbose", ...fields].includes(name));
names = names.filter(name => ["acceleration", "h3_attention_backend", "h3_veda_enabled", "h3_veda_predictor", "h3_veda_verbose", ...fields, "secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength", "secondary_lora_continuation_only"].includes(name));
}
}
let layout = FUNCTIONAL_LAYOUT[active];
@@ -2784,7 +2861,7 @@ function mount(node) {
}
if (layout) {
const used = new Set();
layout.forEach(([title, fields]) => { const relevant = fields.filter((name) => names.includes(name)); if (!relevant.length) return; const section = document.createElement("section"); section.className = "h3p-functional"; section.innerHTML = `<div class="h3p-functional-title">${title}</div>`; const inner = document.createElement("div"); inner.className = "h3p-functional-grid"; relevant.forEach((name) => { used.add(name); const control = makeControl(name); if (control) inner.append(control); }); section.append(inner); grid.append(section); });
layout.forEach(([title, fields]) => { const relevant = fields.filter((name) => names.includes(name)); if (!relevant.length) return; const section = document.createElement("section"); section.className = "h3p-functional"; section.innerHTML = `<div class="h3p-functional-title">${title}</div>`; if (relevant.includes("h3_open_refine_scale")) section.append(openH3ResolutionSummary(node)); const inner = document.createElement("div"); inner.className = "h3p-functional-grid"; relevant.forEach((name) => { used.add(name); const control = makeControl(name); if (control) inner.append(control); }); section.append(inner); grid.append(section); });
const remaining = names.filter((name) => !used.has(name)); if (remaining.length) { const section = document.createElement("section"); section.className = "h3p-functional"; section.innerHTML = '<div class="h3p-functional-title">OTHER</div>'; const inner = document.createElement("div"); inner.className = "h3p-functional-grid"; remaining.forEach((name) => { const control = makeControl(name); if (control) inner.append(control); }); section.append(inner); grid.append(section); }
} else names.forEach((name) => { const control = makeControl(name); if (control) grid.append(control); });
if (active === "direction" && ["keyframe_joint_native", "latent_go_ahead"].includes(String(mode))) {
@@ -2806,6 +2883,7 @@ function mount(node) {
const assetIssues = selectedH3AssetCompatibility(node, mode);
const values = [["Sampling recipe", samplingRecipeLabel(node)], ["Generation authority", node.properties?.iamccs_settings_master ? "SETTINGS PRO" : "SHOTBOARD / INITIAL"], ["Duration authority", node.properties?.iamccs_settings_master ? `SETTINGS PRO · ${widget(node,"duration_seconds")?.value ?? "—"} s · synced to Shotboard` : `SHOTBOARD · ${widget(node,"duration_seconds")?.value ?? "—"} s`], ["Mode", `${mode} · Settings PRO when connected`], ["Editorial authority", "SHOTBOARD · prompts, guides, media, timeline and audio"], ["Asset compatibility", assetIssues.some(([kind]) => kind === "error") ? "INCOMPATIBLE" : assetIssues.length ? "REVIEW" : "COMPATIBLE"], ["VRAM preset", friendly(widget(node, "performance_profile")?.value)], ["Memory contract", `${widget(node,"motion_context_window_frames")?.value ?? "—"} frames · ${widget(node,"h3_exact_chunk_rows")?.value ?? "—"} rows`], ["Guide window", String(mode) === "longvid_guides" ? selectedGuideWindowStrategy(node) : "N/A"], ["Extension engine", String(mode) === "fl2va_extended_av" ? `${friendly(widget(node,"extended_av_profile")?.value)} · MASKED + EXACT · TRIM PINNED HEAD` : "OFF"], ["Sampling / legacy profile", friendly(widget(node, "acceleration")?.value)], ["Speed adapter (Turbo / FastH3 / SLA)", String(widget(node, "turbo_mode")?.value || "off") === "off" && !["fasth3_dense_6step", "h3_sla"].includes(String(widget(node, "acceleration")?.value || "")) ? "OFF" : (widget(node, "turbo_lora_name")?.value || "MISSING")], ["PDD LoRA", widget(node,"pdd_lora_name")?.value || "OFF"], ["Fused model", widget(node, "fused_turbo_model_name")?.value || "OFF"], ["Effective sampling", `${widget(node, "steps")?.value ?? "—"} steps · ${widget(node, "sampler_name")?.value ?? "—"} · ${widget(node, "scheduler")?.value ?? "—"}`], ["Canvas", `${widget(node, "width")?.value ?? "—"} × ${widget(node, "height")?.value ?? "—"}`], ["ControlNet", widget(node, "h3_controlnet_name")?.value || "OFF"], ["Delivery", widget(node, "upscale_enabled")?.value ? widget(node, "upscale_mode")?.value : "NATIVE"], ["Video Editor", Boolean(widget(node,"h3_editor_enabled")?.value) ? "ON · post-master" : "OFF · direct joined master"]];
values.splice(1,0,["Attention & memory",attentionLabel(node)]);
if (widget(node,"h3_open_refine_enabled")?.value) values.find(([label]) => label === "Delivery")[1] = `OPEN-H3 REFINEMENT · ${widget(node,"h3_open_refine_scale")?.value ?? 2}× · ${widget(node,"h3_open_refine_sigma_preset")?.value || "gentle_3step"}`;
values.push(["VEDA", widget(node,"h3_veda_enabled")?.value ? `ON · ${widget(node,"h3_veda_predictor")?.value || "auto"}` : "OFF"]);
const list = q(".h3p-truth-list"); list.replaceChildren(); values.forEach(([label, value]) => { const row = document.createElement("div"); row.className = "h3p-truth-row"; const caption = document.createElement("span"), content = document.createElement("b"); caption.textContent = label; content.textContent = String(value ?? "—"); row.append(caption, content); list.append(row); });
const issueList = warnings(mode), health = q(".h3p-health"); health.className = `h3p-health ${issueList.some(([kind]) => kind === "error") ? "error" : issueList.some(([kind]) => kind === "warn") ? "warn" : ""}`; health.innerHTML = issueList.map(([, message]) => `• ${message}`).join("<br>");
+6 -4
View File
@@ -12773,6 +12773,7 @@ function renderShotboardV3(node) {
addWidgetBoolSetting("Enable second H3 LoRA", "secondary_lora_enabled");
addSelectSetting("Second LoRA file", "secondary_lora_name", Array.isArray(secondaryLoraValues) && secondaryLoraValues.length ? secondaryLoraValues : [""]);
addSetting("Second LoRA strength", "secondary_lora_strength", "0.05", "-2");
addWidgetBoolSetting("Second LoRA: from segment 2", "secondary_lora_continuation_only");
const pddLoraValues = getWidget(node, "pdd_lora_name")?.options?.values;
addSelectSetting("PDD acceleration LoRA", "pdd_lora_name", Array.isArray(pddLoraValues) && pddLoraValues.length ? pddLoraValues : [""]);
addSetting("PDD strength / trained 1.0", "pdd_strength", "0.05", "0");
@@ -20902,7 +20903,7 @@ const H3_SETTINGS_UI_GROUPS = [
"fused_turbo_model_name", "fused_turbo_sigma_preset",
"h3_exact_profile", "h3_clipproj_profile", "h3_clipproj_load_mode",
"h3_exact_chunk_rows", "h3_exact_precision_mode", "h3_exact_qkv_streaming", "h3_exact_attention_memory",
"secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength",
"secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength", "secondary_lora_continuation_only",
"seed", "seed_stride", "sampler_name", "scheduler", "denoise", "shift_video", "shift_audio",
"ref_image_size", "sol_conditioning", "spectrum_profile", "vram_clean_before_decode", "rife_mode",
],
@@ -20981,7 +20982,7 @@ const H3_SETTINGS_FULL_UI_GROUPS = [
note: "Primary/secondary H3 LoRA and execution acceleration. Every visible box remains editable truth.",
names: [
"turbo_mode", "turbo_lora_name", "turbo_strength", "turbo_sampler_mode",
"secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength",
"secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength", "secondary_lora_continuation_only",
"acceleration", "ref_image_size", "sol_conditioning", "spectrum_profile", "vram_clean_before_decode", "rife_mode",
"fused_turbo_model_name", "fused_turbo_sigma_preset",
],
@@ -21087,7 +21088,7 @@ const H3_SETTINGS_PRO_UI_GROUPS = [
{
id: "speed", label: "4 · H3 SPEED", title: "STAGE A · NATIVE H3 ACCELERATION",
note: "Speeds up only the native H3 sample. It does not select an upscale or add a delivery stage. PDD and LightX2V Turbo are separate routes.",
names: ["acceleration", "pdd_lora_name", "pdd_strength", "turbo_mode", "turbo_lora_name", "turbo_strength", "turbo_sampler_mode", "fused_turbo_model_name", "fused_turbo_sigma_preset", "secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength", "ref_image_size", "sol_conditioning", "spectrum_profile", "vram_clean_before_decode", "rife_mode", "h3_exact_profile", "h3_clipproj_profile", "h3_clipproj_load_mode", "h3_exact_chunk_rows", "h3_exact_precision_mode", "h3_exact_qkv_streaming", "h3_exact_attention_memory"],
names: ["acceleration", "pdd_lora_name", "pdd_strength", "turbo_mode", "turbo_lora_name", "turbo_strength", "turbo_sampler_mode", "fused_turbo_model_name", "fused_turbo_sigma_preset", "secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength", "secondary_lora_continuation_only", "ref_image_size", "sol_conditioning", "spectrum_profile", "vram_clean_before_decode", "rife_mode", "h3_exact_profile", "h3_clipproj_profile", "h3_clipproj_load_mode", "h3_exact_chunk_rows", "h3_exact_precision_mode", "h3_exact_qkv_streaming", "h3_exact_attention_memory"],
},
{
id: "direction", label: "5 · DIRECT", title: "MODE-SPECIFIC CONTRACT",
@@ -21118,7 +21119,7 @@ const H3_SETTINGS_TAB_GROUPS = [...H3_SETTINGS_PRO_UI_GROUPS, ...H3_SETTINGS_FUL
const H3_SETTINGS_ADVANCED_FIELDS = new Set([
"reference_resize_megapixels", "reference_resize_filter", "h3_exact_profile", "h3_clipproj_load_mode",
"h3_exact_chunk_rows", "h3_exact_precision_mode", "h3_exact_qkv_streaming", "h3_exact_attention_memory",
"turbo_sampler_mode", "secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength",
"turbo_sampler_mode", "secondary_lora_enabled", "secondary_lora_name", "secondary_lora_strength", "secondary_lora_continuation_only",
"seed_stride", "sampler_name", "scheduler", "denoise", "shift_video", "shift_audio", "ref_image_size",
"sol_conditioning", "spectrum_profile", "vram_clean_before_decode", "rife_mode",
"upscale_prompt", "upscale_sage", "upscale_seed_offset", "wan_upscale_denoise", "ltx_seam_safe",
@@ -21206,6 +21207,7 @@ function h3SettingsUiLabel(name) {
turbo_strength: "Turbo strength", turbo_sampler_mode: "Turbo sampler", reference_resize_policy: "Fit policy",
secondary_lora_enabled: "Enable creative / projection H3 LoRA", secondary_lora_name: "Creative / projection H3 LoRA",
secondary_lora_strength: "Creative / projection strength",
secondary_lora_continuation_only: "Second LoRA: from segment 2",
pdd_lora_name: "PDD acceleration LoRA", pdd_strength: "PDD strength",
h3_controlnet_enabled: "Enable H3 Fun ControlNet", h3_controlnet_name: "Fun ControlNet model",
h3_controlnet_kind: "Control kind / preprocessor", h3_controlnet_strength: "Control strength",
+6 -5
View File
@@ -2115,6 +2115,7 @@ function mountPrompter(node) {
prompt:[shot.local_prompt,shot.h3_transition_prompt].filter(Boolean).join("\n")}));
project.injection_target = "global";
aiApiKey.value = ""; commit(); renderSections(); renderPreview();
workspace?.showReview();
aiStatus.className = "iamccs-pr-ai-status ok";
aiStatus.textContent = `Global + ${project.local_prompts.length} local prompts ready. Review, then INJECT → SHOTBOARD.`;
} catch (error) { aiStatus.className = "iamccs-pr-ai-status error"; aiStatus.textContent = error.message; }
@@ -2503,11 +2504,9 @@ function mountPrompter(node) {
? "Qwen CLIP: loading the encoder, reading references and writing the H3 prompt. The first REQUEST includes model loading; keep this window open."
: `Sending ${targetKeys.join(", ")} to ${aiProvider.options[aiProvider.selectedIndex]?.text || aiProvider.value}.`;
const imagePayload = await buildAIImagePayload();
// A new image request is authoritative. Never feed previous generated
// image fields back as rough text, otherwise local models tend to
// concatenate the old prompt with the new refinement.
const rewriteInputSections = isImageRequest || isSwapCharRequest
? Object.fromEntries(Object.keys(allSections).map((key) => [key, ""]))
// REQUEST replaces selected drafts; unselected fields remain context.
const rewriteInputSections = narrativeRequest !== null
? Object.fromEntries(Object.entries(allSections).map(([key, value]) => [key, targetKeys.includes(key) ? "" : value]))
: allSections;
const response = await fetchAI("/iamccs/prompter/rewrite", {
method: "POST",
@@ -2520,6 +2519,7 @@ function mountPrompter(node) {
task_mode: project.task_mode,
sections: rewriteInputSections,
target_keys: targetKeys,
request_text: narrativeRequest,
user_direction: project.omni_preset && project.task_mode === "ref2va" ? `${omniRequestDirection(project)}\n${direction}` : project.task_mode === "reference_image"
? `${project.image_preset === "reference_sheet" ? "REFERENCE SHEET: one combined canvas containing every requested character and prop." : "SINGLE STATIC IMAGE."}\n${direction}` : direction,
vision_model: aiVisionModel.value.trim(),
@@ -2589,6 +2589,7 @@ function mountPrompter(node) {
renderSections();
renderPreview();
commit();
if (narrativeRequest !== null) workspace?.showReview();
aiStatus.className = "iamccs-pr-ai-status ok";
const visualCount = Number(data.report?.visual_references?.length || 0);
aiStatus.textContent = isExtendedEvolvingRequest