Compare commits

...

2 Commits

Author SHA1 Message Date
Ethanfel 38979a79a1 Make scene layer random seeds reproducible 2026-07-01 15:53:07 +02:00
Ethanfel 39782ce843 Document scene layer seed simplification 2026-07-01 15:47:05 +02:00
4 changed files with 222 additions and 2 deletions
@@ -0,0 +1,128 @@
# Scene Layer Seed Simplification Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make `SxCPSceneLayerSeedOptions` use the visible node seed for `random` mode so prompt clothing/content choices are reproducible from the workflow.
**Architecture:** Keep the existing scene seed pipeline. Change only layer seed option resolution so `random` no longer replaces the visible seed with a hidden `SystemRandom` value. Preserve existing `seed_trace` and scene pair metadata behavior.
**Tech Stack:** Python ComfyUI custom nodes, existing smoke tests in `tools/prompt_smoke.py`.
---
### Task 1: Add Failing Smoke Coverage
**Files:**
- Modify: `tools/prompt_smoke.py`
- [ ] **Step 1: Add assertions to `smoke_node_scene_chain_registration`**
Add a small check after the node registry assertions or near the existing scene layer seed checks:
```python
random_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build(
"softcore_branch",
"random",
123456789,
"content_pose",
"same_for_all_rows",
"replace_layer",
)[0]
fixed_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build(
"softcore_branch",
"fixed",
123456789,
"content_pose",
"same_for_all_rows",
"replace_layer",
)[0]
random_seed_item = json.loads(random_seed_options)["items"][0]
fixed_seed_item = json.loads(fixed_seed_options)["items"][0]
_expect(random_seed_item.get("seed") == 123456789, "Scene random layer seed should use the visible node seed")
_expect(
random_seed_item.get("seed") == fixed_seed_item.get("seed"),
"Scene random and fixed layer seeds should match when the visible seed matches",
)
```
- [ ] **Step 2: Run focused smoke test and verify it fails**
Run:
```bash
python tools/prompt_smoke.py --case node_scene_chain_registration --quiet
```
Expected: FAIL with `Scene random layer seed should use the visible node seed`.
### Task 2: Make Visible Seed Authoritative
**Files:**
- Modify: `node_scene.py`
- [ ] **Step 1: Remove hidden random replacement**
In `_layer_seed_options_json`, remove the `SystemRandom` override:
```python
resolved_seed = max(0, min(0xFFFFFFFF, int(seed)))
```
The `if seed_mode == "random": ...` branch should be deleted. Leave `seed_mode` in metadata unchanged for compatibility.
- [ ] **Step 2: Run focused smoke test and verify it passes**
Run:
```bash
python tools/prompt_smoke.py --case node_scene_chain_registration --quiet
```
Expected: `OK: smoke passed (1 cases).`
### Task 3: Verify and Commit
**Files:**
- Verify: `node_scene.py`
- Verify: `tools/prompt_smoke.py`
- Commit: implementation and plan file
- [ ] **Step 1: Compile changed Python files**
Run:
```bash
python -m py_compile node_scene.py tools/prompt_smoke.py
```
Expected: exit code 0.
- [ ] **Step 2: Run full smoke suite**
Run:
```bash
python tools/prompt_smoke.py --quiet
```
Expected: either all smoke cases pass, or only the existing unrelated `krea2_prompt_guide_policy` methodology-memory failure remains.
- [ ] **Step 3: Review diff**
Run:
```bash
git --git-dir=.git-real --work-tree=. diff --stat
git --git-dir=.git-real --work-tree=. diff -- node_scene.py tools/prompt_smoke.py
```
Expected: only the random seed behavior and its smoke coverage changed.
- [ ] **Step 4: Commit implementation**
Run:
```bash
git --git-dir=.git-real --work-tree=. add node_scene.py tools/prompt_smoke.py docs/superpowers/plans/2026-07-01-scene-layer-seed-simplification.md
git --git-dir=.git-real --work-tree=. commit -m "Make scene layer random seeds reproducible"
```
@@ -0,0 +1,68 @@
# Scene Layer Seed Design
## Problem
`SxCPSceneLayerSeedOptions` currently has a confusing double-seed behavior. When
`seed_mode=random`, the node displays one seed in the widget, but the build
method replaces it with a hidden `SystemRandom` value. The generated prompt may
therefore use a clothing/content seed that is not visible in the workflow after
the run.
This makes softcore branch clothing hard to reproduce. In a scene pair, the
woman's softcore outfit is selected from the softcore branch content seed, then
the hardcore branch may inherit that outfit through clothing continuity. If the
resolved content seed is hidden, the user cannot reliably answer which seed
picked the clothes.
## Approved Behavior
The visible `seed` field in `SxCPSceneLayerSeedOptions` is the authoritative
seed. `seed_mode=random` must no longer replace it with a hidden random value.
For layer-seed behavior:
- `follow_global`: use the scene seed.
- `fixed`: use the visible node `seed`.
- `random`: use the visible node `seed`.
- `disabled`: apply no layer seed.
The `random` option can remain in the UI for compatibility, but it behaves like
an explicit seed mode. If a user wants a new random value, they should randomize
the visible seed field in the node or use ComfyUI's widget randomization.
## Seed Reporting
The existing scene `seed_trace` remains the source of truth for resolved prompt
axis seeds. For softcore branch clothing, the relevant trace is usually:
- layer: `softcore_branch`
- reroll axis: `content` or `content_pose`
- affected axes: `content_seed`, and for `content_pose` also `pose_seed` and
`role_seed`
Scene pair metadata should continue to include the full softcore and hardcore
scene chain so `seed_trace` is preserved in `metadata_json` and
`scene_metadata_json`.
## Non-Goals
This change does not introduce a separate `clothing_seed` axis. Clothing is still
part of the existing content axis. Splitting clothing from content would be a
larger behavior change and should be handled separately if needed.
This change does not alter image sampler seeds. KSampler/image seeds remain
separate from prompt layer seeds.
## Testing
Add or update smoke coverage for `SxCPSceneLayerSeedOptions`:
- A `random` mode layer seed emits the visible widget seed in its metadata.
- A `fixed` mode layer seed emits the same seed as `random` when given the same
visible seed.
- A softcore branch `content` or `content_pose` seed appears in the generated
softcore row `seed_config`.
- The generated softcore outfit can be traced to that visible content seed.
Existing scene pair tests should continue to verify that softcore branch seeds do
not leak into hardcore pose/content seeds unless explicitly applied to the hard
branch.
-2
View File
@@ -446,8 +446,6 @@ def _layer_seed_options_json(
if combine_mode == "replace_layer": if combine_mode == "replace_layer":
items = [item for item in _seed_option_items(seed_options) if item.get("layer") != layer] items = [item for item in _seed_option_items(seed_options) if item.get("layer") != layer]
resolved_seed = max(0, min(0xFFFFFFFF, int(seed))) resolved_seed = max(0, min(0xFFFFFFFF, int(seed)))
if seed_mode == "random":
resolved_seed = random.SystemRandom().randint(0, 0xFFFFFFFF)
items.append( items.append(
{ {
"layer": layer, "layer": layer,
+26
View File
@@ -15980,6 +15980,32 @@ def smoke_node_scene_chain_registration() -> None:
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry") _expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
nodes = sxcp_nodes.NODE_CLASS_MAPPINGS nodes = sxcp_nodes.NODE_CLASS_MAPPINGS
random_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build(
"softcore_branch",
"random",
123456789,
"content_pose",
"same_for_all_rows",
"replace_layer",
)[0]
fixed_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build(
"softcore_branch",
"fixed",
123456789,
"content_pose",
"same_for_all_rows",
"replace_layer",
)[0]
random_seed_item = json.loads(random_seed_options)["items"][0]
fixed_seed_item = json.loads(fixed_seed_options)["items"][0]
_expect(
random_seed_item.get("seed") == 123456789,
"Scene random layer seed should use the visible node seed",
)
_expect(
random_seed_item.get("seed") == fixed_seed_item.get("seed"),
"Scene random and fixed layer seeds should match when the visible seed matches",
)
scene, start_summary, _start_metadata = nodes["SxCPSceneStart"]().build( scene, start_summary, _start_metadata = nodes["SxCPSceneStart"]().build(
1, 1,
41, 41,