Add seeded random character slots
This commit is contained in:
@@ -118,6 +118,12 @@ optional overrides for age, ethnicity, body/body phrase, skin, hair, and eyes.
|
||||
Leave any field on `random` or blank to let the generator fill that part from
|
||||
the normal pools; set exact values only where you want control.
|
||||
|
||||
Each slot has `slot_seed`. Leave it at `-1` to follow the generator's normal
|
||||
person seed. Set any fixed value when the slot's `random` fields should resolve
|
||||
as one stable character across scene, pose, outfit, or row rerolls. This seed is
|
||||
shared by that slot's random age/body/appearance choices, so you can keep the
|
||||
same participant while changing other generation axes.
|
||||
|
||||
Use `Woman Slot` for women because it exposes woman-focused body choices and a
|
||||
`figure_bias` selector. Use `Man Slot` for men because it exposes man-focused
|
||||
body choices and omits figure bias. The older generic `SxCP Character Slot`
|
||||
|
||||
@@ -756,6 +756,7 @@ class SxCPCharacterSlot:
|
||||
"enabled": ("BOOLEAN", {"default": True}),
|
||||
"subject_type": (["woman", "man"], {"default": "woman"}),
|
||||
"label": (character_label_choices(), {"default": "auto_chain"}),
|
||||
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
||||
"age": (character_age_choices(), {"default": "random"}),
|
||||
"manual_age": ("STRING", {"default": ""}),
|
||||
"ethnicity": (character_ethnicity_choices(), {"default": "random"}),
|
||||
@@ -790,6 +791,7 @@ class SxCPCharacterSlot:
|
||||
enabled,
|
||||
subject_type,
|
||||
label,
|
||||
slot_seed,
|
||||
age,
|
||||
manual_age,
|
||||
ethnicity,
|
||||
@@ -813,6 +815,7 @@ class SxCPCharacterSlot:
|
||||
result = build_character_slot_json(
|
||||
subject_type=subject_type,
|
||||
label=label,
|
||||
slot_seed=slot_seed,
|
||||
age=age,
|
||||
manual_age=manual_age,
|
||||
ethnicity=ethnicity,
|
||||
@@ -844,6 +847,7 @@ class SxCPWomanSlot:
|
||||
"required": {
|
||||
"enabled": ("BOOLEAN", {"default": True}),
|
||||
"label": (character_label_choices(), {"default": "auto_chain"}),
|
||||
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
||||
"age": (character_age_choices(), {"default": "random"}),
|
||||
"manual_age": ("STRING", {"default": ""}),
|
||||
"ethnicity": (character_ethnicity_choices(), {"default": "random"}),
|
||||
@@ -876,6 +880,7 @@ class SxCPWomanSlot:
|
||||
self,
|
||||
enabled,
|
||||
label,
|
||||
slot_seed,
|
||||
age,
|
||||
manual_age,
|
||||
ethnicity,
|
||||
@@ -898,6 +903,7 @@ class SxCPWomanSlot:
|
||||
result = build_character_slot_json(
|
||||
subject_type="woman",
|
||||
label=label,
|
||||
slot_seed=slot_seed,
|
||||
age=age,
|
||||
manual_age=manual_age,
|
||||
ethnicity=ethnicity,
|
||||
@@ -928,6 +934,7 @@ class SxCPManSlot:
|
||||
"required": {
|
||||
"enabled": ("BOOLEAN", {"default": True}),
|
||||
"label": (character_label_choices(), {"default": "auto_chain"}),
|
||||
"slot_seed": ("INT", {"default": -1, "min": -1, "max": 0xFFFFFFFF}),
|
||||
"age": (character_age_choices(), {"default": "random"}),
|
||||
"manual_age": ("STRING", {"default": ""}),
|
||||
"ethnicity": (character_ethnicity_choices(), {"default": "random"}),
|
||||
@@ -960,6 +967,7 @@ class SxCPManSlot:
|
||||
self,
|
||||
enabled,
|
||||
label,
|
||||
slot_seed,
|
||||
age,
|
||||
manual_age,
|
||||
ethnicity,
|
||||
@@ -982,6 +990,7 @@ class SxCPManSlot:
|
||||
result = build_character_slot_json(
|
||||
subject_type="man",
|
||||
label=label,
|
||||
slot_seed=slot_seed,
|
||||
age=age,
|
||||
manual_age=manual_age,
|
||||
ethnicity=ethnicity,
|
||||
|
||||
+63
-4
@@ -178,6 +178,7 @@ CHARACTER_MAN_BODY_CHOICES = [
|
||||
CHARACTER_DESCRIPTOR_DETAIL_CHOICES = ["auto", "full", "medium", "compact", "minimal"]
|
||||
CHARACTER_PRESENCE_CHOICES = ["visible", "pov"]
|
||||
CHARACTER_RANDOM_TOKENS = {"", "random", "auto", "global", "from_global", "default"}
|
||||
CHARACTER_SLOT_SEED_MAX = 0xFFFFFFFF
|
||||
|
||||
CAMERA_DETAIL_CHOICES = ["off", "compact", "full"]
|
||||
HARDCORE_DETAIL_DENSITY_CHOICES = ["compact", "balanced", "dense"]
|
||||
@@ -2307,6 +2308,47 @@ def _slot_expression_intensity_for_phase(slot: dict[str, Any] | None, phase: str
|
||||
return _slot_expression_intensity(slot)
|
||||
|
||||
|
||||
def _normalize_slot_seed(value: Any) -> int:
|
||||
try:
|
||||
seed = int(value)
|
||||
except (TypeError, ValueError):
|
||||
return -1
|
||||
if seed < 0:
|
||||
return -1
|
||||
return min(seed, CHARACTER_SLOT_SEED_MAX)
|
||||
|
||||
|
||||
def _slot_seed(slot: dict[str, Any] | None) -> int:
|
||||
if not slot:
|
||||
return -1
|
||||
return _normalize_slot_seed(slot.get("slot_seed"))
|
||||
|
||||
|
||||
def _slot_seeded_rng(slot: dict[str, Any] | None, salt: int) -> random.Random | None:
|
||||
seed = _slot_seed(slot)
|
||||
if seed < 0:
|
||||
return None
|
||||
return random.Random(_row_seed(seed, 1, salt))
|
||||
|
||||
|
||||
def _slot_context_rng(slot: dict[str, Any], fallback_rng: random.Random) -> random.Random:
|
||||
return _slot_seeded_rng(slot, 701) or fallback_rng
|
||||
|
||||
|
||||
def _slot_effective_figure(
|
||||
slot: dict[str, Any],
|
||||
subject_type: str,
|
||||
fallback_figure: str,
|
||||
) -> str:
|
||||
raw_figure = str(slot.get("figure") or "random").strip()
|
||||
if raw_figure in ("curvy", "balanced", "bombshell"):
|
||||
return raw_figure
|
||||
seeded_rng = _slot_seeded_rng(slot, 709)
|
||||
if subject_type == "woman" and seeded_rng is not None:
|
||||
return g.choose(seeded_rng, ["curvy", "balanced", "bombshell"])
|
||||
return fallback_figure
|
||||
|
||||
|
||||
def _mean(values: list[float]) -> float:
|
||||
return sum(values) / len(values)
|
||||
|
||||
@@ -2468,6 +2510,7 @@ def _normalize_character_slot(slot: dict[str, Any]) -> dict[str, Any]:
|
||||
"profile_type": "character_slot",
|
||||
"subject_type": subject_type,
|
||||
"label": label,
|
||||
"slot_seed": _normalize_slot_seed(slot.get("slot_seed")),
|
||||
"age": age,
|
||||
"ethnicity": _normalize_slot_ethnicity(slot.get("ethnicity")),
|
||||
"figure": figure,
|
||||
@@ -2522,12 +2565,14 @@ def _character_slot_summary(slot: dict[str, Any]) -> str:
|
||||
parts = [
|
||||
subject,
|
||||
label_text,
|
||||
f"seed={slot.get('slot_seed')}" if _slot_seed(slot) >= 0 else "",
|
||||
f"age={slot.get('age', 'random')}",
|
||||
f"ethnicity={slot.get('ethnicity', 'random')}",
|
||||
f"figure={slot.get('figure', 'random')}",
|
||||
f"body={slot.get('body', 'random')}",
|
||||
f"detail={slot.get('descriptor_detail', 'auto')}",
|
||||
]
|
||||
parts = [part for part in parts if part]
|
||||
if _slot_is_pov(slot):
|
||||
parts.append("presence=pov")
|
||||
if not _slot_expression_enabled(slot):
|
||||
@@ -2556,6 +2601,7 @@ def _character_slot_summary(slot: dict[str, Any]) -> str:
|
||||
def build_character_slot_json(
|
||||
subject_type: str = "woman",
|
||||
label: str = "auto_chain",
|
||||
slot_seed: int = -1,
|
||||
age: str = "random",
|
||||
manual_age: str = "",
|
||||
ethnicity: str = "random",
|
||||
@@ -2582,6 +2628,7 @@ def build_character_slot_json(
|
||||
{
|
||||
"subject_type": subject_type,
|
||||
"label": label,
|
||||
"slot_seed": slot_seed,
|
||||
"age": age,
|
||||
"manual_age": manual_age,
|
||||
"ethnicity": ethnicity,
|
||||
@@ -2786,14 +2833,14 @@ def _context_from_character_slot(
|
||||
no_black: bool,
|
||||
) -> dict[str, str]:
|
||||
slot_ethnicity = _slot_value(slot.get("ethnicity"))
|
||||
slot_figure = _slot_value(slot.get("figure"))
|
||||
slot_body = _slot_value(slot.get("body"))
|
||||
effective_ethnicity = slot_ethnicity or ethnicity
|
||||
effective_figure = slot_figure if slot_figure in ("curvy", "balanced", "bombshell") else figure
|
||||
effective_figure = _slot_effective_figure(slot, subject_type, figure)
|
||||
effective_no_plus = bool(no_plus_women) and not slot_body
|
||||
effective_no_black = bool(no_black) and not slot_ethnicity
|
||||
appearance_rng = _slot_context_rng(slot, rng)
|
||||
context = _appearance_for_subject(
|
||||
rng,
|
||||
appearance_rng,
|
||||
subject_type,
|
||||
effective_ethnicity,
|
||||
effective_figure,
|
||||
@@ -2914,7 +2961,19 @@ def _row_from_character_slot(character_slot: str | dict[str, Any] | None) -> dic
|
||||
slots = _parse_character_cast(character_slot)
|
||||
if not slots:
|
||||
return {}
|
||||
return slots[-1]
|
||||
slot = slots[-1]
|
||||
if _slot_seed(slot) >= 0:
|
||||
subject_type = str(slot.get("subject_type") or "woman")
|
||||
return _context_from_character_slot(
|
||||
random.Random(_row_seed(_slot_seed(slot), 1, 719)),
|
||||
slot,
|
||||
subject_type,
|
||||
"any",
|
||||
"curvy",
|
||||
False,
|
||||
False,
|
||||
)
|
||||
return slot
|
||||
|
||||
|
||||
def _character_profile_descriptor(profile: dict[str, Any]) -> str:
|
||||
|
||||
Reference in New Issue
Block a user