Synchronize pair side metadata

This commit is contained in:
2026-06-27 02:32:38 +02:00
parent cfe11a4634
commit ab2a13ecde
4 changed files with 65 additions and 5 deletions
+7 -3
View File
@@ -195,7 +195,9 @@ Already isolated:
- final row and pair text normalization lives in `row_normalization.py`,
covering trigger prepending, extra-positive append, negative merge/dedupe,
caption-part joining, embedded soft/hard row output synchronization, and row
sanitation before metadata leaves generation.
sanitation before metadata leaves generation. It also copies side-specific
pair metadata, such as soft partner styling and hardcore clothing/detail
state, onto the embedded soft/hard rows.
### Pair / Adapter Layer
@@ -232,7 +234,9 @@ Already isolated:
shape; the final cleanup step is delegated to `row_normalization.py`.
Embedded soft/hard rows are synchronized to the final pair prompt, caption,
and negative outputs during normalization so serialized pair metadata does
not carry stale standalone row text.
not carry stale standalone row text. Side-specific structured fields are
synchronized there too, including soft partner styling and hardcore clothing
continuity metadata.
### Krea2 Formatter Path
@@ -441,7 +445,7 @@ Medium-term:
Near-term:
- Normalize pair metadata with one helper, including embedded row prompt,
caption, and negative synchronization.
caption, negative, and side-specific metadata synchronization.
- Confirm pair prompts, captions, and soft/hard rows carry the same sanitized
scene/camera/clothing fields.
- Keep same-room pair continuity synchronized in both assembled prompt text and
+2 -2
View File
@@ -96,7 +96,7 @@ Core helper ownership:
| `scene_camera_adapters.py` | Location-aware camera/scene prose such as coworking lounge camera layout. |
| `krea_cast.py` | Shared formatter cast descriptor parsing, cast labels, cast prose, natural cast descriptor text, and label replacement used by Krea2 and caption routes. |
| `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup. |
| `row_normalization.py` | Final prompt-row and pair metadata normalization: trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output synchronization, and embedded row sanitation. |
| `row_normalization.py` | Final prompt-row and pair metadata normalization: trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output and side-metadata synchronization, and embedded row sanitation. |
| `formatter_input.py` | Shared formatter input parsing: text cleanup, metadata/source JSON detection, trigger-prefix stripping, shared prompt field-label inventory, fallback field-label stripping, `Avoid:` splitting, prompt-field extraction, and metadata row-value fallback. |
| `sdxl_presets.py` | SDXL formatter profiles, style presets, quality presets, default negative prompt, and metadata-family tag hints used by the SDXL formatter and node choice lists. |
| `caption_policy.py` | Caption naturalizer policy data and helpers: caption profiles, style tails, item labels, metadata-family caption labels, detail/style-policy normalization, clothing cleanup, and composition cleanup. |
@@ -494,7 +494,7 @@ plain prompt text. When debugging, inspect these fields before editing pools.
| `options` | `SxCP Insta/OF Options` | Formatters/debug | Soft/hard level, cast mode, continuity, camera modes, expression settings. |
| `shared_descriptor` | `pair_cast.py` | Pair formatters | Primary creator descriptor. |
| `shared_cast_descriptors` | `pair_cast.py` | Pair formatters | Full cast descriptor list. |
| `softcore_row`, `hardcore_row` | Pair route | Pair formatters | Full normal metadata rows for each side; their prompt, caption, and negative fields are synchronized to the final pair outputs during pair normalization. |
| `softcore_row`, `hardcore_row` | Pair route | Pair formatters | Full normal metadata rows for each side; their prompt, caption, negative, and side-specific metadata fields are synchronized to the final pair outputs/root fields during pair normalization. |
| `softcore_prompt`, `hardcore_prompt` | `pair_output.py` | Direct output/fallback | Raw pair prompts before formatter rewrite. |
| `softcore_negative_prompt`, `hardcore_negative_prompt` | `pair_output.py` | Formatter negatives | Separate negatives for each side. |
| `softcore_partner_styling` | `pair_cast.py` | Krea/SDXL pair branch | Partner softcore clothing and pose when same-cast softcore is enabled. |
+24
View File
@@ -119,10 +119,34 @@ def synchronize_pair_row_outputs(pair: dict[str, Any]) -> dict[str, Any]:
return pair
def synchronize_pair_side_metadata(pair: dict[str, Any]) -> dict[str, Any]:
side_keys = {
"softcore_row": (
"softcore_partner_styling",
),
"hardcore_row": (
"hardcore_clothing_state",
"character_hardcore_clothing",
"default_man_hardcore_clothing",
"hardcore_detail_density",
"hardcore_position_config",
),
}
for row_key, keys in side_keys.items():
row = pair.get(row_key)
if not isinstance(row, dict):
continue
for key in keys:
if key in pair:
row[key] = pair.get(key)
return pair
def normalize_pair_metadata(pair: dict[str, Any], *, active_trigger: str = "") -> dict[str, Any]:
trigger = str(active_trigger or "").strip()
triggers = _trigger_tuple(trigger)
synchronize_pair_row_outputs(pair)
synchronize_pair_side_metadata(pair)
for key in ("softcore_prompt", "hardcore_prompt"):
if key in pair:
pair[key] = sanitize_prompt_text(pair.get(key, ""), triggers=triggers)
+32
View File
@@ -835,6 +835,12 @@ def smoke_row_normalization_policy() -> None:
"hardcore_caption": f"{Trigger}, {Trigger}, hard caption.",
"softcore_negative_prompt": "bad anatomy, bad anatomy",
"hardcore_negative_prompt": "bad anatomy, low quality, bad anatomy",
"softcore_partner_styling": {"outfits": ["partner outfit"], "pose": "partner pose"},
"hardcore_clothing_state": "structured hard clothing state",
"character_hardcore_clothing": ["Woman A custom hard clothing"],
"default_man_hardcore_clothing": ["Man A default hard clothing"],
"hardcore_detail_density": "dense",
"hardcore_position_config": {"family": "oral"},
"softcore_row": {
"prompt": f"{Trigger}, {Trigger}, embedded soft.",
"caption": f"{Trigger}, {Trigger}, embedded soft caption.",
@@ -860,6 +866,18 @@ def smoke_row_normalization_policy() -> None:
pair["hardcore_row"].get("caption") == pair.get("hardcore_caption"),
"Pair normalization left stale hard row caption text",
)
_expect(
pair["softcore_row"].get("softcore_partner_styling") == pair.get("softcore_partner_styling"),
"Pair normalization left stale soft side metadata",
)
_expect(
pair["hardcore_row"].get("hardcore_clothing_state") == pair.get("hardcore_clothing_state"),
"Pair normalization left stale hard clothing metadata",
)
_expect(
pair["hardcore_row"].get("default_man_hardcore_clothing") == pair.get("default_man_hardcore_clothing"),
"Pair normalization left stale hard default clothing metadata",
)
_expect_no_duplicate_comma_items("row_normalization.pair.soft_negative", pair.get("softcore_negative_prompt"))
_expect_no_duplicate_comma_items("row_normalization.pair.hard_row_negative", pair["hardcore_row"].get("negative_prompt"))
@@ -1511,6 +1529,20 @@ def _expect_pair(pair: dict[str, Any], name: str) -> None:
pair["hardcore_row"].get("negative_prompt") == pair.get("hardcore_negative_prompt"),
f"{name}.hardcore_row negative drifted from pair negative",
)
if "softcore_partner_styling" in pair:
_expect(
pair["softcore_row"].get("softcore_partner_styling") == pair.get("softcore_partner_styling"),
f"{name}.softcore_row partner styling drifted from pair root",
)
for key in (
"hardcore_clothing_state",
"character_hardcore_clothing",
"default_man_hardcore_clothing",
"hardcore_detail_density",
"hardcore_position_config",
):
if key in pair:
_expect(pair["hardcore_row"].get(key) == pair.get(key), f"{name}.hardcore_row {key} drifted from pair root")
_expect_no_duplicate_comma_items(f"{name}.softcore_negative", pair.get("softcore_negative_prompt"))
_expect_no_duplicate_comma_items(f"{name}.hardcore_negative", pair.get("hardcore_negative_prompt"))
_expect_formatter_outputs(pair, name, target="softcore")