Add Krea2 variant evidence node

This commit is contained in:
2026-06-29 03:27:32 +02:00
parent d384cb8a46
commit fae5423513
6 changed files with 95 additions and 3 deletions
+1
View File
@@ -1,6 +1,7 @@
{ {
"version": 1, "version": 1,
"purpose": "Structured fixed-seed Krea2 prompt/image evidence for SxCP atlas pose variants.", "purpose": "Structured fixed-seed Krea2 prompt/image evidence for SxCP atlas pose variants.",
"artifact_policy": "Image paths are external ComfyUI artifacts and may be cleaned; seed, summaries, observation, decision, and commit are the durable record.",
"entries": [ "entries": [
{ {
"id": "doggy-52-climax-target-structural", "id": "doggy-52-climax-target-structural",
+3 -1
View File
@@ -19,7 +19,9 @@ parsing the JSON directly.
In ComfyUI, use the `SxCP Krea2 Pose Variant` node when you want a workflow to In ComfyUI, use the `SxCP Krea2 Pose Variant` node when you want a workflow to
select one catalog variant and emit a compatible `hardcore_position_config` for select one catalog variant and emit a compatible `hardcore_position_config` for
the existing Position Pool / Action Filter / Insta-OF chain. the existing Position Pool / Action Filter / Insta-OF chain. Pair it with
`SxCP Krea2 Variant Evidence` to display the fixed-seed eval entry, image paths,
and generator decision behind that variant.
## Inventory ## Inventory
+3 -1
View File
@@ -39,7 +39,9 @@ Runtime logs are written under `.sxcp_eval/` and ignored by git.
Durable fixed-seed findings that justify a guide rule, generator patch, or pose Durable fixed-seed findings that justify a guide rule, generator patch, or pose
variant promotion are recorded in [`krea2-eval-log.json`](krea2-eval-log.json). variant promotion are recorded in [`krea2-eval-log.json`](krea2-eval-log.json).
Use runtime logs for scratch notes; use the JSON log only for evidence that Use runtime logs for scratch notes; use the JSON log only for evidence that
should remain tied to a catalog variant. should remain tied to a catalog variant. Image paths in that log point at
external ComfyUI artifacts and may be cleaned; the durable evidence is the fixed
seed, prompt summaries, observation, decision, and commit.
## Optional Command Hook ## Optional Command Hook
+57
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import json import json
try: try:
from . import krea2_eval_log
from . import krea2_pose_variant_catalog from . import krea2_pose_variant_catalog
from .hardcore_position_config import ( from .hardcore_position_config import (
build_hardcore_action_filter_json, build_hardcore_action_filter_json,
@@ -12,6 +13,7 @@ try:
hardcore_position_key_choices, hardcore_position_key_choices,
) )
except ImportError: # Allows local smoke tests from the repository root. except ImportError: # Allows local smoke tests from the repository root.
import krea2_eval_log
import krea2_pose_variant_catalog import krea2_pose_variant_catalog
from hardcore_position_config import ( from hardcore_position_config import (
build_hardcore_action_filter_json, build_hardcore_action_filter_json,
@@ -140,6 +142,59 @@ class SxCPKrea2PoseVariant:
) )
class SxCPKrea2VariantEvidence:
@classmethod
def INPUT_TYPES(cls):
keys = krea2_pose_variant_catalog.variant_keys()
return {
"required": {
"variant_key": (keys or ["missing_catalog_variant"], {"default": keys[0] if keys else "missing_catalog_variant"}),
"result": (["accepted", "rejected", "inconclusive", "any"], {"default": "accepted"}),
},
"optional": {
"variant_key_in": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "INT", "STRING")
RETURN_NAMES = (
"summary",
"baseline_image_path",
"candidate_image_path",
"evidence_json",
"seed",
"decision",
)
FUNCTION = "build"
CATEGORY = "prompt_builder"
def build(self, variant_key, result="accepted", variant_key_in=""):
key = str(variant_key_in or variant_key or "").strip()
result_filter = None if result == "any" else result
entries = krea2_eval_log.entries_for_variant(key, result=result_filter)
if not entries:
empty = {
"variant_key": key,
"result": result,
"summary": "no Krea2 eval evidence found",
}
return empty["summary"], "", "", json.dumps(empty, ensure_ascii=True, sort_keys=True), -1, ""
entry = entries[0]
summary = (
f"evidence={entry.get('id')}; variant={entry.get('variant_key')}; "
f"seed={entry.get('seed')}; result={entry.get('result')}; decision={entry.get('decision')}"
)
seed = entry.get("seed")
return (
summary,
str(entry.get("baseline_image") or ""),
str(entry.get("candidate_image") or ""),
json.dumps(entry, ensure_ascii=True, sort_keys=True),
int(seed) if isinstance(seed, int) else -1,
str(entry.get("decision") or ""),
)
class SxCPHardcoreActionFilter: class SxCPHardcoreActionFilter:
@classmethod @classmethod
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
@@ -203,10 +258,12 @@ NODE_CLASS_MAPPINGS = {
"SxCPHardcorePositionPool": SxCPHardcorePositionPool, "SxCPHardcorePositionPool": SxCPHardcorePositionPool,
"SxCPHardcoreActionFilter": SxCPHardcoreActionFilter, "SxCPHardcoreActionFilter": SxCPHardcoreActionFilter,
"SxCPKrea2PoseVariant": SxCPKrea2PoseVariant, "SxCPKrea2PoseVariant": SxCPKrea2PoseVariant,
"SxCPKrea2VariantEvidence": SxCPKrea2VariantEvidence,
} }
NODE_DISPLAY_NAME_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = {
"SxCPHardcorePositionPool": "SxCP Hardcore Position Pool", "SxCPHardcorePositionPool": "SxCP Hardcore Position Pool",
"SxCPHardcoreActionFilter": "SxCP Hardcore Action Filter", "SxCPHardcoreActionFilter": "SxCP Hardcore Action Filter",
"SxCPKrea2PoseVariant": "SxCP Krea2 Pose Variant", "SxCPKrea2PoseVariant": "SxCP Krea2 Pose Variant",
"SxCPKrea2VariantEvidence": "SxCP Krea2 Variant Evidence",
} }
+5
View File
@@ -327,6 +327,11 @@ NODE_INPUT_TOOLTIPS = {
"combine_mode": "replace discards incoming position choices; add merges this variant with the incoming position config.", "combine_mode": "replace discards incoming position choices; add merges this variant with the incoming position config.",
"hardcore_position_config": "Optional incoming hardcore position config. Connect this when layering a variant on an existing pool.", "hardcore_position_config": "Optional incoming hardcore position config. Connect this when layering a variant on an existing pool.",
}, },
"SxCPKrea2VariantEvidence": {
"variant_key": "Catalog variant whose fixed-seed eval evidence should be shown.",
"result": "Filter eval entries by result. accepted is the evidence used for proven variants.",
"variant_key_in": "Optional connected variant key from SxCP Krea2 Pose Variant. When connected, it overrides the selector.",
},
"SxCPHardcoreActionFilter": { "SxCPHardcoreActionFilter": {
"focus": "keep_pool preserves/broadens the incoming pool; *_only modes force one action family.", "focus": "keep_pool preserves/broadens the incoming pool; *_only modes force one action family.",
"allow_toys": "Allow toy/strap-on wording in hardcore actions.", "allow_toys": "Allow toy/strap-on wording in hardcore actions.",
+26 -1
View File
@@ -6807,6 +6807,7 @@ def smoke_krea2_pose_variant_catalog_policy() -> None:
def smoke_krea2_eval_log_policy() -> None: def smoke_krea2_eval_log_policy() -> None:
log = krea2_eval_log.load_eval_log() log = krea2_eval_log.load_eval_log()
_expect(log.get("version") == 1, "Krea2 eval log version changed unexpectedly") _expect(log.get("version") == 1, "Krea2 eval log version changed unexpectedly")
_expect("external ComfyUI artifacts" in str(log.get("artifact_policy") or ""), "Krea2 eval log should document external artifact policy")
entries = krea2_eval_log.entries() entries = krea2_eval_log.entries()
_expect(entries, "Krea2 eval log has no entries") _expect(entries, "Krea2 eval log has no entries")
catalog_keys = set(krea2_pose_variant_catalog.variant_keys()) catalog_keys = set(krea2_pose_variant_catalog.variant_keys())
@@ -6833,7 +6834,7 @@ def smoke_krea2_eval_log_policy() -> None:
image_path = str(entry.get(image_key) or "") image_path = str(entry.get(image_key) or "")
if image_path: if image_path:
_expect(Path(image_path).is_absolute(), f"{entry_id}.{image_key} should be absolute when present") _expect(Path(image_path).is_absolute(), f"{entry_id}.{image_key} should be absolute when present")
_expect(Path(image_path).is_file(), f"{entry_id}.{image_key} is missing: {image_path}") _expect(Path(image_path).suffix.lower() == ".png", f"{entry_id}.{image_key} should reference a PNG artifact")
boobjob_entries = krea2_eval_log.entries_for_variant("pov_boobjob_upright_cleavage", result="accepted") boobjob_entries = krea2_eval_log.entries_for_variant("pov_boobjob_upright_cleavage", result="accepted")
_expect(boobjob_entries and boobjob_entries[0].get("seed") == 7302, "Boobjob accepted eval evidence changed") _expect(boobjob_entries and boobjob_entries[0].get("seed") == 7302, "Boobjob accepted eval evidence changed")
mutation = krea2_eval_log.entries_for_variant("pov_handjob_upright_centered")[0] mutation = krea2_eval_log.entries_for_variant("pov_handjob_upright_centered")[0]
@@ -8870,6 +8871,7 @@ def smoke_node_hardcore_position_registration() -> None:
"SxCPHardcorePositionPool", "SxCPHardcorePositionPool",
"SxCPHardcoreActionFilter", "SxCPHardcoreActionFilter",
"SxCPKrea2PoseVariant", "SxCPKrea2PoseVariant",
"SxCPKrea2VariantEvidence",
] ]
for node_name in required_nodes: for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry") _expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
@@ -8931,6 +8933,29 @@ def smoke_node_hardcore_position_registration() -> None:
_expect("torso bent forward" in avoid_cues, "Krea2 Pose Variant lost avoid cues output") _expect("torso bent forward" in avoid_cues, "Krea2 Pose Variant lost avoid cues output")
_expect("variant=pov_boobjob_upright_cleavage" in variant_summary, "Krea2 Pose Variant summary lost key") _expect("variant=pov_boobjob_upright_cleavage" in variant_summary, "Krea2 Pose Variant summary lost key")
evidence_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2VariantEvidence"]
evidence_inputs = evidence_node.INPUT_TYPES().get("required") or {}
_expect("variant_key" in evidence_inputs, "Krea2 Variant Evidence lost variant selector")
_expect("tooltip" in evidence_inputs["variant_key"][1], "Krea2 Variant Evidence tooltip injection missing")
(
evidence_summary,
baseline_image,
candidate_image,
evidence_json,
evidence_seed,
evidence_decision,
) = evidence_node().build(
"pov_boobjob_upright_cleavage",
"accepted",
"",
)
parsed_evidence = json.loads(evidence_json)
_expect(evidence_seed == 7302, "Krea2 Variant Evidence returned wrong fixed seed")
_expect(evidence_decision == "generator_patch", "Krea2 Variant Evidence returned wrong decision")
_expect("boobjob-7302" in evidence_summary, "Krea2 Variant Evidence summary lost entry id")
_expect(baseline_image.endswith(".png") and candidate_image.endswith(".png"), "Krea2 Variant Evidence lost image paths")
_expect(parsed_evidence.get("variant_key") == "pov_boobjob_upright_cleavage", "Krea2 Variant Evidence returned wrong JSON")
def smoke_node_formatter_registration() -> None: def smoke_node_formatter_registration() -> None:
required_nodes = [ required_nodes = [