diff --git a/README.md b/README.md index 14af51d..aa6e99b 100644 --- a/README.md +++ b/README.md @@ -331,11 +331,11 @@ prompt result. Manual fields and explicitly fixed per-axis or character-slot seeds still override the global seed for those parts. `SxCP Seed Control` outputs `seed_config`, which can be connected to the prompt -builder's optional `seed_config` input. When an axis is set to `random`, the -visible seed value is materialized before the workflow queues, and that exact -value is used for the queued prompt. The mode returns to `random` after queueing -so the next run can reroll. Use `Lock Random Seeds Now` on the node when you want -to convert the current random axes into fixed reusable seeds. +builder's optional `seed_config` input. It also outputs a `summary` string with +the resolved value for every axis. When an axis is set to `random`, the widget +can stay at `-1`, but the emitted `seed_config` and `summary` contain the +concrete seed used for that queued prompt. Use `Lock Random Seeds Now` on the +node when you want to convert the current random axes into fixed reusable seeds. `SxCP Seed Locker` is the fast version for iteration. Set `base_seed` to a seed you like, choose one `reroll_axis`, and connect its `seed_config`. All other @@ -866,10 +866,12 @@ axis has its own mode plus seed value: - `follow_main`: always follows the final generator's main `seed` input and ignores the entered axis seed. - `fixed`: always uses the entered axis seed. -- `random`: generates a fresh visible axis seed when the workflow queues. +- `random`: generates a fresh resolved axis seed when the workflow queues. -The `Lock Random Seeds Now` button turns every current `random` axis into a -visible concrete seed and switches those axes to `fixed`. +The `summary` output lists the resolved value for every axis, including random +axes whose visible widget value remains `-1`. The `Lock Random Seeds Now` button +turns every current `random` axis into a visible concrete seed and switches +those axes to `fixed`. For exact prompt reproduction, `SxCP Global Seed` is the shortest path: diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 98fc0ce..a126b1b 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -192,7 +192,10 @@ Seed routing is centralized in `seed_config.py` around `SEED_AXIS_SALTS`, `SxCP Global Seed`, `SxCP Seed Control`, and `SxCP Seed Locker` all feed `seed_config`. Values below zero mean the row's main seed still drives that axis. Fixed axis seeds allow changing only one road, for example changing -`pose`/`role` while keeping person, scene, and category stable. +`pose`/`role` while keeping person, scene, and category stable. `SxCP Seed +Control` keeps `seed_config` as its first output and also emits a `summary` +showing resolved per-axis values, including random-mode seeds whose widget value +may still be `-1`. ## Seed Playbook diff --git a/node_seed_resolution.py b/node_seed_resolution.py index 8808634..873d4a3 100644 --- a/node_seed_resolution.py +++ b/node_seed_resolution.py @@ -76,8 +76,8 @@ class SxCPSeedControl: required[f"{axis}_seed"] = ("INT", seed_spec) return {"required": required} - RETURN_TYPES = (SXCP_SEED_CONFIG,) - RETURN_NAMES = ("seed_config",) + RETURN_TYPES = (SXCP_SEED_CONFIG, "STRING") + RETURN_NAMES = ("seed_config", "summary") FUNCTION = "build" CATEGORY = "prompt_builder" @@ -88,6 +88,21 @@ class SxCPSeedControl: return random.random() return tuple(args), tuple(sorted(kwargs.items())) + @classmethod + def _summary(cls, config_json): + try: + config = json.loads(config_json) + except (TypeError, ValueError, json.JSONDecodeError): + return "invalid seed config" + parts = [] + for axis in cls.SEED_AXES: + try: + value = int(config.get(f"{axis}_seed", -1)) + except (TypeError, ValueError): + value = -1 + parts.append(f"{axis}={'follow_main' if value < 0 else value}") + return "resolved seeds: " + "; ".join(parts) + def build( self, category_seed_mode, @@ -109,27 +124,29 @@ class SxCPSeedControl: composition_seed_mode, composition_seed, ): + config = build_seed_config_json( + category_seed=category_seed, + subcategory_seed=subcategory_seed, + content_seed=content_seed, + person_seed=person_seed, + scene_seed=scene_seed, + pose_seed=pose_seed, + role_seed=role_seed, + expression_seed=expression_seed, + composition_seed=composition_seed, + category_seed_mode=category_seed_mode, + subcategory_seed_mode=subcategory_seed_mode, + content_seed_mode=content_seed_mode, + person_seed_mode=person_seed_mode, + scene_seed_mode=scene_seed_mode, + pose_seed_mode=pose_seed_mode, + role_seed_mode=role_seed_mode, + expression_seed_mode=expression_seed_mode, + composition_seed_mode=composition_seed_mode, + ) return ( - build_seed_config_json( - category_seed=category_seed, - subcategory_seed=subcategory_seed, - content_seed=content_seed, - person_seed=person_seed, - scene_seed=scene_seed, - pose_seed=pose_seed, - role_seed=role_seed, - expression_seed=expression_seed, - composition_seed=composition_seed, - category_seed_mode=category_seed_mode, - subcategory_seed_mode=subcategory_seed_mode, - content_seed_mode=content_seed_mode, - person_seed_mode=person_seed_mode, - scene_seed_mode=scene_seed_mode, - pose_seed_mode=pose_seed_mode, - role_seed_mode=role_seed_mode, - expression_seed_mode=expression_seed_mode, - composition_seed_mode=composition_seed_mode, - ), + config, + self._summary(config), ) diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 8313024..8b634ea 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -5225,6 +5225,7 @@ def smoke_node_utility_registration() -> None: seed_inputs = seed_control.INPUT_TYPES().get("required") or {} _expect("category_seed_mode" in seed_inputs, "Seed Control lost category seed mode input") _expect("tooltip" in seed_inputs["category_seed_mode"][1], "Seed Control tooltip injection missing") + _expect(seed_control.RETURN_NAMES == ("seed_config", "summary"), "Seed Control lost visible summary output") _expect( node_tooltips._tooltip_for_input("SxCPSeedControl", "category_seed_mode") == "auto/follow_main follows the main seed; fixed uses category_seed; random rerolls this axis each queue.", @@ -5234,6 +5235,40 @@ def smoke_node_utility_registration() -> None: "Autoscaling switch input" in node_tooltips._tooltip_for_input("SxCPIndexSwitch", "input_12"), "Node tooltip policy lost autoscaling input fallback", ) + seed_control_config, seed_control_summary = seed_control().build( + "fixed", + -1, + "follow_main", + -1, + "random", + -1, + "auto", + 123, + "auto", + -1, + "fixed", + 777, + "follow_main", + 888, + "auto", + -1, + "fixed", + 999, + ) + parsed_seed_control = json.loads(seed_control_config) + _expect(parsed_seed_control.get("category_seed") == 0, "Seed Control fixed mode did not clamp negative seed") + _expect(parsed_seed_control.get("subcategory_seed") == -1, "Seed Control follow_main mode should emit -1") + _expect(int(parsed_seed_control.get("content_seed", -1)) >= 0, "Seed Control random mode did not emit resolved seed") + _expect(parsed_seed_control.get("person_seed") == 123, "Seed Control auto mode did not preserve explicit value") + _expect(parsed_seed_control.get("pose_seed") == 777, "Seed Control fixed mode did not preserve positive seed") + _expect(parsed_seed_control.get("role_seed") == -1, "Seed Control follow_main role did not emit -1") + _expect(parsed_seed_control.get("composition_seed") == 999, "Seed Control fixed composition seed changed") + _expect("category=0" in seed_control_summary, "Seed Control summary lost fixed resolved seed") + _expect("subcategory=follow_main" in seed_control_summary, "Seed Control summary lost follow_main marker") + _expect( + f"content={parsed_seed_control['content_seed']}" in seed_control_summary, + "Seed Control summary lost random resolved seed value", + ) seed, seed_config, summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGlobalSeed"]().build(12345) parsed_seed = json.loads(seed_config)