Add scene-chain option nodes

This commit is contained in:
2026-06-27 23:39:30 +02:00
parent 29efb954fb
commit 3c54bb4bbe
6 changed files with 1269 additions and 23 deletions
+10
View File
@@ -128,6 +128,16 @@ blocking, action, performance, camera, composition, and lighting layers. Use
`SxCP Hardcore Branch Options`, then render both sides through `SxCP Hardcore Branch Options`, then render both sides through
`SxCP Scene Pair Output`. `SxCP Scene Pair Output`.
Each layer can stay light on the main chain and take optional side-node inputs:
`SxCP Scene Layer Seed Options`, `SxCP Scene Cast Options`,
`SxCP Scene Character Options`, `SxCP Scene Wardrobe Options`,
`SxCP Scene Location Layout Options`, `SxCP Scene Set Dressing Options`,
`SxCP Scene Blocking Options`, `SxCP Scene Action Options`,
`SxCP Scene Performance Options`, `SxCP Scene Camera Options`,
`SxCP Scene Composition Options`, `SxCP Scene Lighting Options`, and
`SxCP Scene Branch Options`. These side nodes are chainable and only override
the layer they are connected to.
The current v2 output nodes intentionally reuse the existing builder, The current v2 output nodes intentionally reuse the existing builder,
Insta/OF pair, and formatter metadata routes. This keeps old workflows working Insta/OF pair, and formatter metadata routes. This keeps old workflows working
while giving new workflows a cleaner movie-scene structure. while giving new workflows a cleaner movie-scene structure.
+13
View File
@@ -25,6 +25,19 @@ SXCP_CHARACTER_CAST = "SXCP_CHARACTER_CAST"
SXCP_CHARACTER_SLOT = "SXCP_CHARACTER_SLOT" SXCP_CHARACTER_SLOT = "SXCP_CHARACTER_SLOT"
SXCP_CHARACTER_PROFILE = "SXCP_CHARACTER_PROFILE" SXCP_CHARACTER_PROFILE = "SXCP_CHARACTER_PROFILE"
SXCP_SCENE = "SXCP_SCENE" SXCP_SCENE = "SXCP_SCENE"
SXCP_SCENE_LAYER_SEED = "SXCP_SCENE_LAYER_SEED"
SXCP_SCENE_CAST_OPTIONS = "SXCP_SCENE_CAST_OPTIONS"
SXCP_SCENE_CHARACTER_OPTIONS = "SXCP_SCENE_CHARACTER_OPTIONS"
SXCP_SCENE_WARDROBE_OPTIONS = "SXCP_SCENE_WARDROBE_OPTIONS"
SXCP_SCENE_LOCATION_OPTIONS = "SXCP_SCENE_LOCATION_OPTIONS"
SXCP_SCENE_SET_OPTIONS = "SXCP_SCENE_SET_OPTIONS"
SXCP_SCENE_BLOCKING_OPTIONS = "SXCP_SCENE_BLOCKING_OPTIONS"
SXCP_SCENE_ACTION_OPTIONS = "SXCP_SCENE_ACTION_OPTIONS"
SXCP_SCENE_PERFORMANCE_OPTIONS = "SXCP_SCENE_PERFORMANCE_OPTIONS"
SXCP_SCENE_CAMERA_OPTIONS = "SXCP_SCENE_CAMERA_OPTIONS"
SXCP_SCENE_COMPOSITION_OPTIONS = "SXCP_SCENE_COMPOSITION_OPTIONS"
SXCP_SCENE_LIGHTING_OPTIONS = "SXCP_SCENE_LIGHTING_OPTIONS"
SXCP_SCENE_BRANCH_OPTIONS = "SXCP_SCENE_BRANCH_OPTIONS"
try: try:
from .node_tooltips import install_input_tooltips as _install_input_tooltips from .node_tooltips import install_input_tooltips as _install_input_tooltips
+10
View File
@@ -80,6 +80,16 @@ V2 scene-chain display nodes: `SxCP Scene Cast`, `SxCP Scene Character`,
`SxCP Scene Branch Pair`, `SxCP Softcore Branch Options`, and `SxCP Scene Branch Pair`, `SxCP Softcore Branch Options`, and
`SxCP Hardcore Branch Options`. `SxCP Hardcore Branch Options`.
V2 scene-chain side option nodes: `SxCP Scene Layer Seed Options`,
`SxCP Scene Cast Options`, `SxCP Scene Character Options`,
`SxCP Scene Wardrobe Options`, `SxCP Scene Location Layout Options`,
`SxCP Scene Set Dressing Options`, `SxCP Scene Blocking Options`,
`SxCP Scene Action Options`, `SxCP Scene Performance Options`,
`SxCP Scene Camera Options`, `SxCP Scene Composition Options`,
`SxCP Scene Lighting Options`, and `SxCP Scene Branch Options`. These nodes
feed typed option sockets into the main scene chain so the visible chain can
stay organized by movie-scene layer while deeper knobs remain adjacent.
Core helper ownership: Core helper ownership:
| Python module | What it owns | | Python module | What it owns |
+1110 -18
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -17,6 +17,10 @@ COMMON_INPUT_TOOLTIPS = {
"filter_config": "Ethnicity/body filter config. Ethnicity List can feed this too.", "filter_config": "Ethnicity/body filter config. Ethnicity List can feed this too.",
"ethnicity_list": "Optional ethnicity pool. When connected, it overrides the slot or generator ethnicity picker.", "ethnicity_list": "Optional ethnicity pool. When connected, it overrides the slot or generator ethnicity picker.",
"seed_config": "Per-axis seed config. Use Global Seed for full reproducibility, Seed Locker to reroll one axis, or Seed Control for manual axis seeds.", "seed_config": "Per-axis seed config. Use Global Seed for full reproducibility, Seed Locker to reroll one axis, or Seed Control for manual axis seeds.",
"layer": "Scene layer affected by this side node. all applies to every compatible scene node that receives the options.",
"seed_mode": "follow_global uses the scene seed, fixed uses the seed field, random resolves a fresh seed at queue time, disabled does nothing.",
"row_behavior": "same_for_all_rows keeps the option seed as-is; vary_by_row offsets it by row number before writing axis seeds.",
"reroll_axis": "Specific generator axis group to reroll. none uses the default axes for the selected scene layer.",
"camera_config": "Camera config consumed only by nodes/options set to from_camera_config.", "camera_config": "Camera config consumed only by nodes/options set to from_camera_config.",
"location_config": "Location config from SxCP Location Pool. It can replace or add to the category scene pool.", "location_config": "Location config from SxCP Location Pool. It can replace or add to the category scene pool.",
"composition_config": "Composition config from SxCP Composition Pool or Location Theme. It can replace or add framing options.", "composition_config": "Composition config from SxCP Composition Pool or Location Theme. It can replace or add framing options.",
@@ -33,6 +37,20 @@ COMMON_INPUT_TOOLTIPS = {
"scene": "Structured v2 scene context. Chain Scene nodes in order, then connect to Scene Output or Scene Pair Output.", "scene": "Structured v2 scene context. Chain Scene nodes in order, then connect to Scene Output or Scene Pair Output.",
"softcore_scene": "Softcore branch scene from Scene Branch Pair, optionally refined by Softcore Branch Options.", "softcore_scene": "Softcore branch scene from Scene Branch Pair, optionally refined by Softcore Branch Options.",
"hardcore_scene": "Hardcore branch scene from Scene Branch Pair, optionally refined by Hardcore Branch Options.", "hardcore_scene": "Hardcore branch scene from Scene Branch Pair, optionally refined by Hardcore Branch Options.",
"options": "Incoming options of the same type. Chain option nodes with combine_mode=add when multiple side knobs should contribute.",
"seed_options": "Scene layer seed options. Connect Scene Layer Seed Options to reroll one layer without changing the whole scene.",
"cast_options": "Optional cast side-node settings that override the Cast node widgets only when connected.",
"character_options": "Optional character side-node settings that override descriptor, presence, and expression controls.",
"wardrobe_options": "Optional wardrobe side-node settings for subject-specific clothing, nudity state, and wardrobe prompt text.",
"location_options": "Optional location layout settings such as foreground anchors, midground, repetition, and public/private context.",
"set_options": "Optional set-dressing settings for props, repeated background, foreground anchors, and sensory details.",
"blocking_options": "Optional blocking settings for subject placement, orientation, depth plane, and exact body geography.",
"action_options": "Optional action settings for scene kind, action family, category preset, and manual action text.",
"performance_options": "Optional performance settings for expression, gaze, hands, body tension, and actor notes.",
"camera_options": "Optional camera side-node settings that describe camera source and freeform camera text.",
"composition_options": "Optional composition side-node settings for readability target, crop, occlusion, and framing text.",
"lighting_options": "Optional lighting side-node settings for source, softness, contrast, color, and time of day.",
"branch_options": "Optional branch settings that apply to softcore, hardcore, or both Insta/OF branches.",
"target_formatter": "Intended downstream formatter target. The scene stores this as metadata; use formatter nodes for final rewriting.", "target_formatter": "Intended downstream formatter target. The scene stores this as metadata; use formatter nodes for final rewriting.",
"category_preset": "Category preset this scene should render through when no explicit category config overrides it.", "category_preset": "Category preset this scene should render through when no explicit category config overrides it.",
"central_subject": "Who should be visually central in this scene metadata.", "central_subject": "Who should be visually central in this scene metadata.",
@@ -42,24 +60,43 @@ COMMON_INPUT_TOOLTIPS = {
"custom_location": "Exact location text for this scene. One line or JSON entry is enough.", "custom_location": "Exact location text for this scene. One line or JSON entry is enough.",
"location_note": "Additional location wording merged into the location pool entry.", "location_note": "Additional location wording merged into the location pool entry.",
"foreground_anchors": "Objects or surfaces that should stay near the camera or lower frame.", "foreground_anchors": "Objects or surfaces that should stay near the camera or lower frame.",
"midground_layer": "Readable middle-distance scene elements between the subject and background.",
"background_repetition": "Repeated environmental structure that helps the model keep a location coherent across rerolls.",
"visibility_level": "How visible or hidden the scene should feel inside the location.",
"public_level": "Private, semi-public, or public context for the location layer.",
"repeated_background": "Repeating background structure such as desks, doors, shelves, pillars, or windows.", "repeated_background": "Repeating background structure such as desks, doors, shelves, pillars, or windows.",
"props": "Scene props or set dressing objects that make the location readable.", "props": "Scene props or set dressing objects that make the location readable.",
"sensory_details": "Small material/light/surface details that make the set dressing feel specific.",
"set_prompt": "Freeform set-dressing sentence appended to the scene layer.", "set_prompt": "Freeform set-dressing sentence appended to the scene layer.",
"blocking_mode": "Broad body-placement mode. custom lets custom_blocking carry the exact placement.", "blocking_mode": "Broad body-placement mode. custom lets custom_blocking carry the exact placement.",
"subject_placement": "Where the subject or cast sits in the space: foreground, near desk edge, on bed, in aisle, etc.", "subject_placement": "Where the subject or cast sits in the space: foreground, near desk edge, on bed, in aisle, etc.",
"body_relation": "Spatial relationship between participants, separate from the action itself.", "body_relation": "Spatial relationship between participants, separate from the action itself.",
"body_orientation": "Front, side, back, three-quarter, or POV-facing body orientation.",
"depth_plane": "Whether the subjects sit in foreground, midground, background, or a layered composition.",
"distance_note": "Extra spatial distance wording, such as close together, across the table, or partly hidden behind a shelf.",
"custom_blocking": "Exact blocking/positioning sentence for the scene layer.", "custom_blocking": "Exact blocking/positioning sentence for the scene layer.",
"scene_kind": "Regular, softcore, or hardcore intent for this action layer.", "scene_kind": "Regular, softcore, or hardcore intent for this action layer.",
"action_family": "Broad action family such as softcore tease, oral, penetration, climax, group, or custom.",
"action_prompt": "Action text stored separately from blocking and camera. Use position pools for hardcore randomization when possible.", "action_prompt": "Action text stored separately from blocking and camera. Use position pools for hardcore randomization when possible.",
"gaze": "Where the character looks: camera, partner, down, away, over shoulder, or eyes closed.",
"hand_placement": "What hands are doing: relaxed, on body, on partner, holding camera, pulling clothing, or braced.",
"body_tension": "Body performance cue: relaxed, posed, arched, braced, or active motion.",
"performance_prompt": "Expression, gaze, hand, and body-performance note stored separately from the action.", "performance_prompt": "Expression, gaze, hand, and body-performance note stored separately from the action.",
"camera_source": "Where camera text comes from conceptually: config, qwen orbit, POV, phone, external, or manual.",
"preserve_location_layout": "Keep location layout wording compatible with the camera instead of letting camera text replace the space.",
"camera_prompt": "Optional freeform camera note kept as scene metadata. Camera config still controls existing formatter behavior.", "camera_prompt": "Optional freeform camera note kept as scene metadata. Camera config still controls existing formatter behavior.",
"custom_composition": "Exact composition/framing entry to add to the composition pool.", "custom_composition": "Exact composition/framing entry to add to the composition pool.",
"readability_target": "What the composition should keep most readable: face, body, action, room, anchor objects, or contact points.",
"crop": "Composition crop intent such as full body, three-quarter, close-up, or extreme close-up.",
"occlusion": "How much foreground or hidden-sightline occlusion the composition should allow.",
"composition_prompt": "Additional composition wording merged into the composition layer.", "composition_prompt": "Additional composition wording merged into the composition layer.",
"lighting_source": "Main light source family for the scene.", "lighting_source": "Main light source family for the scene.",
"lighting_softness": "Softness of the light: soft, balanced, or hard.", "lighting_softness": "Softness of the light: soft, balanced, or hard.",
"lighting_contrast": "Overall contrast level for the lighting layer.", "lighting_contrast": "Overall contrast level for the lighting layer.",
"color_temperature": "Warm, neutral, cool, or mixed color temperature.", "color_temperature": "Warm, neutral, cool, or mixed color temperature.",
"time_of_day": "Optional time-of-day lighting context.",
"custom_lighting": "Exact lighting sentence for the scene layer.", "custom_lighting": "Exact lighting sentence for the scene layer.",
"branch_target": "Whether branch options affect both Insta/OF branches, softcore only, or hardcore only.",
"continuity": "How branch outputs share cast/location setup between softcore and hardcore scenes.", "continuity": "How branch outputs share cast/location setup between softcore and hardcore scenes.",
"platform_style": "Instagram/OnlyFans styling bias for Scene Pair Output.", "platform_style": "Instagram/OnlyFans styling bias for Scene Pair Output.",
"softcore_cast": "Whether the softcore branch uses a solo creator or the same cast as the hardcore branch.", "softcore_cast": "Whether the softcore branch uses a solo creator or the same cast as the hardcore branch.",
@@ -126,6 +163,9 @@ COMMON_INPUT_TOOLTIPS = {
"presence_mode": "Controls whether the character is visible or acts as the male POV participant.", "presence_mode": "Controls whether the character is visible or acts as the male POV participant.",
"softcore_outfit": "Manual softcore outfit text for this character. Prefer Character Clothing for reusable outfit pools.", "softcore_outfit": "Manual softcore outfit text for this character. Prefer Character Clothing for reusable outfit pools.",
"hardcore_clothing": "Manual hardcore exposure text for this character. Use explicit nude states when you do not want clothing words repeated.", "hardcore_clothing": "Manual hardcore exposure text for this character. Use explicit nude states when you do not want clothing words repeated.",
"wardrobe_state": "High-level clothing/body-exposure state. explicit_nude avoids conflicting outfit text in hardcore prompts.",
"accessories": "Accessories that can remain visible without forcing full outfit wording.",
"avoid_clothing_when_nude": "When nude states are selected, avoid reintroducing clothing words that make the image model dress the subject.",
"custom_softcore_outfits": "One custom softcore outfit per line. Used when softcore_source is custom.", "custom_softcore_outfits": "One custom softcore outfit per line. Used when softcore_source is custom.",
"custom_hardcore_clothing": "One custom hardcore clothing/body exposure state per line.", "custom_hardcore_clothing": "One custom hardcore clothing/body exposure state per line.",
"condition": "Loop condition. When false, the loop stops and passes current values through.", "condition": "Loop condition. When false, the loop stops and passes current values through.",
+86 -5
View File
@@ -8792,6 +8792,19 @@ def smoke_node_insta_registration() -> None:
def smoke_node_scene_chain_registration() -> None: def smoke_node_scene_chain_registration() -> None:
required_nodes = [ required_nodes = [
"SxCPSceneLayerSeedOptions",
"SxCPSceneCastOptions",
"SxCPSceneCharacterOptions",
"SxCPSceneWardrobeOptions",
"SxCPSceneLocationLayoutOptions",
"SxCPSceneSetDressingOptions",
"SxCPSceneBlockingOptions",
"SxCPSceneActionOptions",
"SxCPScenePerformanceOptions",
"SxCPSceneCameraOptions",
"SxCPSceneCompositionOptions",
"SxCPSceneLightingOptions",
"SxCPSceneBranchOptions",
"SxCPSceneStart", "SxCPSceneStart",
"SxCPSceneCast", "SxCPSceneCast",
"SxCPSceneCharacter", "SxCPSceneCharacter",
@@ -8830,6 +8843,7 @@ def smoke_node_scene_chain_registration() -> None:
parsed_scene = json.loads(scene) parsed_scene = json.loads(scene)
_expect(parsed_scene.get("schema") == "sxcp_scene_v2", "Scene Start did not emit v2 schema") _expect(parsed_scene.get("schema") == "sxcp_scene_v2", "Scene Start did not emit v2 schema")
cast_options = nodes["SxCPSceneCastOptions"]().build("replace", "mixed_couple", 1, 1, "woman_a", "none")[0]
scene, _cast_config, _cast_summary, _cast_metadata = nodes["SxCPSceneCast"]().build( scene, _cast_config, _cast_summary, _cast_metadata = nodes["SxCPSceneCast"]().build(
scene, scene,
"mixed_couple", "mixed_couple",
@@ -8837,7 +8851,18 @@ def smoke_node_scene_chain_registration() -> None:
1, 1,
"woman_a", "woman_a",
"none", "none",
cast_options=cast_options,
) )
character_options = nodes["SxCPSceneCharacterOptions"]().build(
"replace",
"medium",
"visible",
"enabled",
0.5,
-1,
-1,
"controlled slot performance note",
)[0]
scene, character_cast, _slot, _summary, _metadata = nodes["SxCPSceneCharacter"]().build( scene, character_cast, _slot, _summary, _metadata = nodes["SxCPSceneCharacter"]().build(
scene, scene,
True, True,
@@ -8854,6 +8879,7 @@ def smoke_node_scene_chain_registration() -> None:
"visible", "visible",
-1, -1,
-1, -1,
character_options=character_options,
) )
scene, character_cast, _slot, _summary, _metadata = nodes["SxCPSceneCharacter"]().build( scene, character_cast, _slot, _summary, _metadata = nodes["SxCPSceneCharacter"]().build(
scene, scene,
@@ -8872,6 +8898,19 @@ def smoke_node_scene_chain_registration() -> None:
-1, -1,
-1, -1,
) )
wardrobe_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build("wardrobe", "fixed", 9981, "content", "same_for_all_rows", "replace_layer")[0]
wardrobe_options = nodes["SxCPSceneWardrobeOptions"]().build(
"replace",
"woman",
"A",
"full",
"explicit_nude",
True,
"simple black dress",
"",
"thin necklace",
"",
)[0]
scene, character_cast, _wardrobe_summary, _wardrobe_metadata = nodes["SxCPSceneWardrobe"]().build( scene, character_cast, _wardrobe_summary, _wardrobe_metadata = nodes["SxCPSceneWardrobe"]().build(
scene, scene,
True, True,
@@ -8881,12 +8920,24 @@ def smoke_node_scene_chain_registration() -> None:
"simple black dress", "simple black dress",
"fully nude", "fully nude",
"", "",
wardrobe_options=wardrobe_options,
seed_options=wardrobe_seed_options,
) )
slots = json.loads(character_cast).get("slots") or [] slots = json.loads(character_cast).get("slots") or []
woman_slot = next(slot for slot in slots if slot.get("subject_type") == "woman") woman_slot = next(slot for slot in slots if slot.get("subject_type") == "woman")
_expect(woman_slot.get("softcore_outfit") == "simple black dress", "Scene Wardrobe did not update softcore outfit") _expect(woman_slot.get("softcore_outfit") == "simple black dress", "Scene Wardrobe did not update softcore outfit")
_expect(woman_slot.get("hardcore_clothing") == "fully nude", "Scene Wardrobe did not update hardcore clothing") _expect(woman_slot.get("hardcore_clothing") == "fully nude", "Scene Wardrobe did not update hardcore clothing")
_expect(json.loads(scene).get("seed_trace", {}).get("wardrobe", {}).get("seed") == 9981, "Scene Wardrobe seed options did not write seed trace")
location_options = nodes["SxCPSceneLocationLayoutOptions"]().build(
"replace",
"mirror edge",
"soft curtain layer",
"repeating lamp reflections",
"private",
"private",
"warm mirror-room geometry",
)[0]
scene = nodes["SxCPSceneLocation"]().build( scene = nodes["SxCPSceneLocation"]().build(
scene, scene,
True, True,
@@ -8894,10 +8945,26 @@ def smoke_node_scene_chain_registration() -> None:
"custom_only", "custom_only",
"quiet studio room with a large mirror", "quiet studio room with a large mirror",
"", "",
location_options=location_options,
)[0] )[0]
scene = nodes["SxCPSceneSetDressing"]().build(scene, True, "mirror edge", "soft curtains", "small lamp", "")[0] set_options = nodes["SxCPSceneSetDressingOptions"]().build("replace", "mirror edge", "soft curtains", "small lamp", "warm fabric texture", "")[0]
scene = nodes["SxCPSceneBlocking"]().build(scene, True, "standing", "woman near mirror", "man behind her", "")[0] scene = nodes["SxCPSceneSetDressing"]().build(scene, True, "mirror edge", "soft curtains", "small lamp", "", set_options=set_options)[0]
scene = nodes["SxCPScenePerformance"]().build(scene, True, "fixed", 0.4, "controlled eye contact")[0] blocking_options = nodes["SxCPSceneBlockingOptions"]().build(
"replace",
"standing",
"woman near mirror",
"man behind her",
"three_quarter",
"foreground",
"standing close",
"",
)[0]
scene = nodes["SxCPSceneBlocking"]().build(scene, True, "standing", "woman near mirror", "man behind her", "", blocking_options=blocking_options)[0]
action_options = nodes["SxCPSceneActionOptions"]().build("replace", "softcore", "softcore_tease", "no_change", "quiet pose transition")[0]
scene = nodes["SxCPSceneAction"]().build(scene, True, "regular", "no_change", "", action_options=action_options)[0]
performance_options = nodes["SxCPScenePerformanceOptions"]().build("replace", "enabled", "fixed", 0.4, "camera", "on_body", "posed", "controlled eye contact")[0]
scene = nodes["SxCPScenePerformance"]().build(scene, True, "fixed", 0.4, "controlled eye contact", performance_options=performance_options)[0]
camera_options = nodes["SxCPSceneCameraOptions"]().build("replace", "from_camera_config", True, "mirror-aware camera note")[0]
scene = nodes["SxCPSceneCamera"]().build( scene = nodes["SxCPSceneCamera"]().build(
scene, scene,
True, True,
@@ -8911,9 +8978,12 @@ def smoke_node_scene_chain_registration() -> None:
"strong", "strong",
"compact", "compact",
"", "",
camera_options=camera_options,
)[0] )[0]
scene = nodes["SxCPSceneComposition"]().build(scene, True, "replace", "no_outfit_check", "mirror-aware three-quarter frame", "")[0] composition_options = nodes["SxCPSceneCompositionOptions"]().build("replace", "body", "three_quarter", "clear", "mirror-aware three-quarter frame")[0]
scene = nodes["SxCPSceneLighting"]().build(scene, True, "practical_lamps", "soft", "medium", "warm", "")[0] scene = nodes["SxCPSceneComposition"]().build(scene, True, "replace", "no_outfit_check", "", "", composition_options=composition_options)[0]
lighting_options = nodes["SxCPSceneLightingOptions"]().build("replace", "practical_lamps", "soft", "medium", "warm", "evening", "")[0]
scene = nodes["SxCPSceneLighting"]().build(scene, True, "practical_lamps", "soft", "medium", "warm", "", lighting_options=lighting_options)[0]
output = nodes["SxCPSceneOutput"]().build(scene) output = nodes["SxCPSceneOutput"]().build(scene)
_expect_text("node_scene_chain.prompt", output[0], 40) _expect_text("node_scene_chain.prompt", output[0], 40)
@@ -8921,10 +8991,14 @@ def smoke_node_scene_chain_registration() -> None:
row = json.loads(output[3]) row = json.loads(output[3])
_expect(row.get("scene_chain", {}).get("schema") == "sxcp_scene_v2", "Scene Output lost scene_chain metadata") _expect(row.get("scene_chain", {}).get("schema") == "sxcp_scene_v2", "Scene Output lost scene_chain metadata")
branch_options = nodes["SxCPSceneBranchOptions"]().build("replace", "both", "same_creator_same_room", "hybrid", "shared branch note")[0]
branch_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build("hardcore_branch", "fixed", 7799, "pose", "same_for_all_rows", "replace_layer")[0]
soft_scene, hard_scene, _branch_summary, _branch_metadata = nodes["SxCPSceneBranchPair"]().build( soft_scene, hard_scene, _branch_summary, _branch_metadata = nodes["SxCPSceneBranchPair"]().build(
scene, scene,
"same_creator_same_room", "same_creator_same_room",
"hybrid", "hybrid",
branch_options=branch_options,
seed_options=branch_seed_options,
) )
soft_scene = nodes["SxCPSoftcoreBranchOptions"]().build( soft_scene = nodes["SxCPSoftcoreBranchOptions"]().build(
soft_scene, soft_scene,
@@ -8935,6 +9009,7 @@ def smoke_node_scene_chain_registration() -> None:
"from_camera_config", "from_camera_config",
"compact", "compact",
"", "",
branch_options=branch_options,
)[0] )[0]
hard_scene = nodes["SxCPHardcoreBranchOptions"]().build( hard_scene = nodes["SxCPHardcoreBranchOptions"]().build(
hard_scene, hard_scene,
@@ -8949,6 +9024,8 @@ def smoke_node_scene_chain_registration() -> None:
"compact", "compact",
"balanced", "balanced",
"", "",
branch_options=branch_options,
seed_options=branch_seed_options,
)[0] )[0]
pair_output = nodes["SxCPScenePairOutput"]().build(soft_scene, hard_scene) pair_output = nodes["SxCPScenePairOutput"]().build(soft_scene, hard_scene)
_expect_text("node_scene_chain.softcore_prompt", pair_output[0], 40) _expect_text("node_scene_chain.softcore_prompt", pair_output[0], 40)
@@ -8957,6 +9034,10 @@ def smoke_node_scene_chain_registration() -> None:
_expect_pair(pair, "node_scene_chain_pair") _expect_pair(pair, "node_scene_chain_pair")
_expect(pair.get("options", {}).get("hardcore_cast") == "couple", "Scene Pair Output lost hardcore branch options") _expect(pair.get("options", {}).get("hardcore_cast") == "couple", "Scene Pair Output lost hardcore branch options")
_expect("scene_chain" in pair, "Scene Pair Output lost scene_chain metadata") _expect("scene_chain" in pair, "Scene Pair Output lost scene_chain metadata")
_expect(
pair.get("scene_chain", {}).get("hardcore", {}).get("seed_trace", {}).get("hardcore.hardcore_branch", {}).get("seed") == 7799,
"Scene branch seed options did not write hardcore branch seed trace",
)
def smoke_node_builder_registration() -> None: def smoke_node_builder_registration() -> None: