Preserve location route metadata
This commit is contained in:
@@ -420,6 +420,10 @@ spaces: front/side/back views, zoom, and elevation change which desks, windows,
|
||||
partitions, bookshelves, reading tables, lamps, or aisles are kept visible. In
|
||||
male-POV setups this becomes a first-person spatial description and the
|
||||
external camera sentence is suppressed.
|
||||
Rows keep the selected `scene_entry`, `location_theme`, `scene_theme`,
|
||||
`composition_entry`, `composition_theme`, and `scene_camera_profile_key` in
|
||||
`metadata_json` so location/camera behavior can be debugged without guessing
|
||||
from prompt text alone.
|
||||
|
||||
`SxCP SDXL Formatter` rewrites prompt builder output or `metadata_json` into
|
||||
comma-tag SDXL/Pony-style prompts. Connect `metadata_json` when possible so
|
||||
|
||||
@@ -508,6 +508,8 @@ plain prompt text. When debugging, inspect these fields before editing pools.
|
||||
| `role_graph` | `_role_graph`, POV adapter | Krea/Naturalizer | Choreography/action relationship text after POV adaptation. |
|
||||
| `source_role_graph` | `_role_graph` before POV rewrite | Krea hardcore rewrite | Raw action graph used to infer position and contact. |
|
||||
| `scene_text` | `row_prompt_axes.resolve_prompt_axes` | All formatters | Final location text. |
|
||||
| `scene_entry` | `row_prompt_axes.resolve_prompt_axes` / `row_location` | Debug/future route rules | Structured selected scene entry, preserving slug/prompt plus theme metadata when available. |
|
||||
| `location_theme`, `scene_theme` | `location_config.py`, selected scene entry | Debug/camera route rules | Active theme on the location config and theme of the selected scene. This makes theme-driven behavior inspectable instead of only string-inferred. |
|
||||
| `source_scene_text` | location/body-exposure/camera adapters | Debug/continuity | Previous scene text before an override. |
|
||||
| `location_config` | Location config parser | Debug | Active location pool config, if connected. |
|
||||
| `pose` | `row_prompt_axes.resolve_prompt_axes` | Formatters | Generic pose text. Less important for hardcore action categories than `item`/`role_graph`. |
|
||||
@@ -517,11 +519,13 @@ plain prompt text. When debugging, inspect these fields before editing pools.
|
||||
| `expression_enabled`, `expression_disabled` | Builder/slot override | All formatters | Hard gate for whether expression text should appear. |
|
||||
| `expression_intensity_source` | Builder/slot override | Debug | Explains whether intensity came from input, random, slot, or disabled state. |
|
||||
| `composition` | `row_prompt_axes.resolve_prompt_axes` | All formatters | Final framing phrase. |
|
||||
| `composition_entry`, `composition_theme` | `row_prompt_axes.resolve_prompt_axes` / `row_location` | Debug/future route rules | Structured selected composition entry and active composition theme. |
|
||||
| `source_composition` | `row_prompt_axes.resolve_prompt_axes` | Krea hardcore rewrite | Previous/raw composition, often better for action inference. |
|
||||
| `composition_config` | Composition config parser | Debug | Active composition pool config, if connected. |
|
||||
| `camera_config` | Camera nodes/parser | Krea/SDXL/debug | Structured camera settings. |
|
||||
| `camera_directive` | `_camera_directive` | Krea/Naturalizer/prompt text | Human camera sentence. Suppressed for POV. |
|
||||
| `camera_scene_directive` | scene-camera adapter | Krea/Naturalizer/prompt text | Location-aware camera layout sentence. |
|
||||
| `scene_camera_profile`, `scene_camera_profile_key` | `row_camera.apply_camera_config` | Debug/camera route rules | Structured camera profile selected for the current scene, e.g. `classical_library` or `coworking_lounge`. |
|
||||
| `subject_type`, `subject_phrase` | `row_subject_route.resolve_subject_route` | Formatters | Single/couple/group/configured cast route. |
|
||||
| `women_count`, `men_count`, `person_count` | `row_subject_route.resolve_subject_route` | Pair/formatters/debug | Effective cast counts. |
|
||||
| `cast_descriptors`, `cast_descriptor_text` | `row_subject_route.resolve_subject_route` | Krea/SDXL/Naturalizer | Visible cast descriptors. |
|
||||
@@ -651,6 +655,9 @@ Current camera-aware scene adapter:
|
||||
- Scene profiles live in `scene_camera_adapters.SCENE_CAMERA_PROFILES`.
|
||||
- Coworking/business-cafe/office scenes and classical library/book-stack scenes
|
||||
are detected by `scene_camera_profile`.
|
||||
- Location themes preserve `theme` on configs and selected scene entries, and
|
||||
rows expose `location_theme`, `scene_theme`, `composition_theme`, and
|
||||
`scene_camera_profile_key` for debugging and future route rules.
|
||||
- Direction, distance, and elevation details come from profile-aware helpers
|
||||
such as `scene_direction_detail`, `scene_distance_detail`, and
|
||||
`scene_elevation_detail`.
|
||||
|
||||
+25
-4
@@ -322,6 +322,7 @@ def build_location_pool_json(
|
||||
merged_entries = entries
|
||||
|
||||
active = bool(enabled) and bool(merged_entries)
|
||||
theme = str(incoming.get("theme") or "") if combine_mode == "add" and incoming.get("enabled") else ""
|
||||
summary = (
|
||||
f"{apply_mode}; pools={len(merged_pool_names)}; locations={len(merged_entries)}"
|
||||
if active
|
||||
@@ -334,6 +335,7 @@ def build_location_pool_json(
|
||||
"pool_names": merged_pool_names,
|
||||
"scene_entries": merged_entries,
|
||||
"summary": summary,
|
||||
"theme": theme,
|
||||
},
|
||||
ensure_ascii=True,
|
||||
sort_keys=True,
|
||||
@@ -342,7 +344,7 @@ def build_location_pool_json(
|
||||
|
||||
def parse_location_config(location_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||
if not location_config:
|
||||
return {"enabled": False, "apply_mode": "replace", "pool_names": [], "scene_entries": []}
|
||||
return {"enabled": False, "apply_mode": "replace", "pool_names": [], "scene_entries": [], "theme": ""}
|
||||
if isinstance(location_config, dict):
|
||||
raw = dict(location_config)
|
||||
else:
|
||||
@@ -361,6 +363,7 @@ def parse_location_config(location_config: str | dict[str, Any] | None) -> dict[
|
||||
"pool_names": [str(name) for name in _list_from(raw.get("pool_names")) if str(name).strip()],
|
||||
"scene_entries": entries,
|
||||
"summary": str(raw.get("summary") or ""),
|
||||
"theme": str(raw.get("theme") or ""),
|
||||
}
|
||||
|
||||
|
||||
@@ -432,6 +435,7 @@ def build_composition_pool_json(
|
||||
merged_entries = entries
|
||||
|
||||
active = bool(enabled) and bool(merged_entries)
|
||||
theme = str(incoming.get("theme") or "") if combine_mode == "add" and incoming.get("enabled") else ""
|
||||
summary = (
|
||||
f"{apply_mode}; pools={len(merged_pool_names)}; compositions={len(merged_entries)}"
|
||||
if active
|
||||
@@ -444,6 +448,7 @@ def build_composition_pool_json(
|
||||
"pool_names": merged_pool_names,
|
||||
"composition_entries": merged_entries,
|
||||
"summary": summary,
|
||||
"theme": theme,
|
||||
},
|
||||
ensure_ascii=True,
|
||||
sort_keys=True,
|
||||
@@ -452,7 +457,7 @@ def build_composition_pool_json(
|
||||
|
||||
def parse_composition_config(composition_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||
if not composition_config:
|
||||
return {"enabled": False, "apply_mode": "replace", "pool_names": [], "composition_entries": []}
|
||||
return {"enabled": False, "apply_mode": "replace", "pool_names": [], "composition_entries": [], "theme": ""}
|
||||
if isinstance(composition_config, dict):
|
||||
raw = dict(composition_config)
|
||||
else:
|
||||
@@ -471,6 +476,7 @@ def parse_composition_config(composition_config: str | dict[str, Any] | None) ->
|
||||
"pool_names": [str(name) for name in _list_from(raw.get("pool_names")) if str(name).strip()],
|
||||
"composition_entries": entries,
|
||||
"summary": str(raw.get("summary") or ""),
|
||||
"theme": str(raw.get("theme") or ""),
|
||||
}
|
||||
|
||||
|
||||
@@ -512,8 +518,23 @@ def build_thematic_location_json(
|
||||
custom_compositions=composition_lines,
|
||||
composition_config=composition_config or "",
|
||||
)
|
||||
location_summary = json.loads(resolved_location_config).get("summary", "")
|
||||
composition_summary = json.loads(resolved_composition_config).get("summary", "")
|
||||
location_payload = json.loads(resolved_location_config)
|
||||
composition_payload = json.loads(resolved_composition_config)
|
||||
location_payload["theme"] = str(theme or "")
|
||||
composition_payload["theme"] = str(theme or "")
|
||||
themed_scene_entries = []
|
||||
for entry in location_payload.get("scene_entries") or []:
|
||||
if isinstance(entry, dict):
|
||||
themed_entry = dict(entry)
|
||||
themed_entry.setdefault("theme", str(theme or ""))
|
||||
themed_scene_entries.append(themed_entry)
|
||||
else:
|
||||
themed_scene_entries.append(entry)
|
||||
location_payload["scene_entries"] = themed_scene_entries
|
||||
resolved_location_config = json.dumps(location_payload, ensure_ascii=True, sort_keys=True)
|
||||
resolved_composition_config = json.dumps(composition_payload, ensure_ascii=True, sort_keys=True)
|
||||
location_summary = location_payload.get("summary", "")
|
||||
composition_summary = composition_payload.get("summary", "")
|
||||
summary = f"{theme}; locations={location_summary}; compositions={composition_summary}"
|
||||
return resolved_location_config, resolved_composition_config, summary
|
||||
|
||||
|
||||
@@ -2380,6 +2380,7 @@ def _build_custom_row(
|
||||
)
|
||||
scene_slug = prompt_axes.scene_slug
|
||||
scene = prompt_axes.scene
|
||||
scene_entry = dict(prompt_axes.scene_entry)
|
||||
pose = prompt_axes.pose
|
||||
expression = prompt_axes.expression
|
||||
shared_expression = prompt_axes.shared_expression
|
||||
@@ -2387,6 +2388,7 @@ def _build_custom_row(
|
||||
character_expression_text = prompt_axes.character_expression_text
|
||||
source_composition = prompt_axes.source_composition
|
||||
composition = prompt_axes.composition
|
||||
composition_entry = dict(prompt_axes.composition_entry)
|
||||
action_route = _action_position_route(
|
||||
is_pose_category=is_pose_category,
|
||||
subcategory=subcategory,
|
||||
@@ -2424,6 +2426,7 @@ def _build_custom_row(
|
||||
negative_prompt=text_fields.negative_prompt,
|
||||
scene_slug=scene_slug,
|
||||
scene=scene,
|
||||
scene_entry=scene_entry,
|
||||
pose=pose,
|
||||
expression=expression,
|
||||
shared_expression=shared_expression,
|
||||
@@ -2434,6 +2437,7 @@ def _build_custom_row(
|
||||
expression_intensity_source=expression_intensity_source,
|
||||
composition=composition,
|
||||
source_composition=source_composition,
|
||||
composition_entry=composition_entry,
|
||||
role_graph=role_graph,
|
||||
source_role_graph=source_role_graph,
|
||||
action_family=action_family,
|
||||
|
||||
@@ -37,6 +37,7 @@ class CustomRowAssemblyRequest:
|
||||
negative_prompt: str
|
||||
scene_slug: str
|
||||
scene: str
|
||||
scene_entry: dict[str, Any]
|
||||
pose: str
|
||||
expression: str
|
||||
shared_expression: str
|
||||
@@ -47,6 +48,7 @@ class CustomRowAssemblyRequest:
|
||||
expression_intensity_source: str
|
||||
composition: str
|
||||
source_composition: str
|
||||
composition_entry: dict[str, Any]
|
||||
role_graph: str
|
||||
source_role_graph: str
|
||||
action_family: str
|
||||
@@ -85,6 +87,7 @@ def assemble_custom_row(request: CustomRowAssemblyRequest) -> dict[str, Any]:
|
||||
"style": r.style,
|
||||
"scene": r.scene,
|
||||
"scene_slug": r.scene_slug,
|
||||
"scene_entry": r.scene_entry,
|
||||
"pose": r.pose,
|
||||
"expression": r.expression,
|
||||
"shared_expression": r.shared_expression,
|
||||
@@ -95,6 +98,7 @@ def assemble_custom_row(request: CustomRowAssemblyRequest) -> dict[str, Any]:
|
||||
"expression_intensity": r.expression_intensity,
|
||||
"expression_intensity_source": r.expression_intensity_source,
|
||||
"composition": r.composition,
|
||||
"composition_entry": r.composition_entry,
|
||||
"source_composition": r.source_composition,
|
||||
"composition_prompt": row_camera_policy.composition_prompt(r.composition),
|
||||
"composition_config": r.composition_config or {},
|
||||
@@ -156,6 +160,13 @@ def assemble_custom_row(request: CustomRowAssemblyRequest) -> dict[str, Any]:
|
||||
"item_template_metadata": r.item_template_metadata,
|
||||
"formatter_hints": r.formatter_hints,
|
||||
"scene_text": r.scene,
|
||||
"scene_entry": r.scene_entry,
|
||||
"location_theme": (r.location_config or {}).get("theme", ""),
|
||||
"scene_theme": r.scene_entry.get("theme", "") or (
|
||||
(r.location_config or {}).get("theme", "")
|
||||
if (r.location_config or {}).get("apply_mode") == "replace"
|
||||
else ""
|
||||
),
|
||||
"location_config": r.location_config or {},
|
||||
"pose": r.pose,
|
||||
"seed_config": r.seed_config,
|
||||
@@ -168,6 +179,8 @@ def assemble_custom_row(request: CustomRowAssemblyRequest) -> dict[str, Any]:
|
||||
"position_key": r.position_key,
|
||||
"position_keys": r.position_keys,
|
||||
"source_composition": r.source_composition,
|
||||
"composition_entry": r.composition_entry,
|
||||
"composition_theme": (r.composition_config or {}).get("theme", ""),
|
||||
"pov_character_labels": r.pov_character_labels,
|
||||
"pov_prompt_directive": pov_prompt_directive,
|
||||
"shared_expression": r.shared_expression,
|
||||
|
||||
@@ -70,6 +70,18 @@ def apply_contextual_composition(row: dict[str, Any], subject_kind: str) -> dict
|
||||
return row
|
||||
|
||||
|
||||
def scene_camera_profile_metadata(scene_text: Any) -> dict[str, str]:
|
||||
profile = scene_camera_adapters.scene_camera_profile(scene_text)
|
||||
if not profile:
|
||||
return {}
|
||||
return {
|
||||
"key": str(profile.get("key") or ""),
|
||||
"family": str(profile.get("family") or ""),
|
||||
"layout_label": str(profile.get("layout_label") or ""),
|
||||
"place": str(profile.get("place") or ""),
|
||||
}
|
||||
|
||||
|
||||
def camera_scene_directive_for_context(
|
||||
scene_text: Any,
|
||||
composition: Any,
|
||||
@@ -129,6 +141,10 @@ def apply_camera_config(
|
||||
pov_labels = row_pov_labels(row, pov_label_resolver)
|
||||
subject_kind = row_camera_subject_kind(row)
|
||||
row = apply_contextual_composition(row, subject_kind)
|
||||
profile_metadata = scene_camera_profile_metadata(row.get("scene_text") or row.get("source_scene_text") or row.get("scene"))
|
||||
if profile_metadata:
|
||||
row["scene_camera_profile"] = profile_metadata
|
||||
row["scene_camera_profile_key"] = profile_metadata.get("key", "")
|
||||
scene_directive, parsed = camera_scene_directive_for_context(
|
||||
row.get("scene_text") or row.get("source_scene_text") or row.get("scene"),
|
||||
row.get("composition") or row.get("source_composition"),
|
||||
|
||||
+38
-2
@@ -89,8 +89,31 @@ def _choose_pair(rng: random.Random, items: list[Any]) -> tuple[str, str]:
|
||||
return _pair_from(_weighted_choice(rng, items))
|
||||
|
||||
|
||||
def _metadata_entry(value: Any, *, slug: str = "", text: str = "") -> dict[str, Any]:
|
||||
if isinstance(value, dict):
|
||||
entry = dict(value)
|
||||
elif isinstance(value, (list, tuple)) and len(value) == 2:
|
||||
entry = {"slug": str(value[0]), "prompt": str(value[1])}
|
||||
else:
|
||||
entry = {"prompt": str(value or "")}
|
||||
if slug:
|
||||
entry["slug"] = slug
|
||||
if text:
|
||||
if "prompt" in entry:
|
||||
entry["prompt"] = text
|
||||
elif "text" in entry:
|
||||
entry["text"] = text
|
||||
else:
|
||||
entry["prompt"] = text
|
||||
return entry
|
||||
|
||||
|
||||
def _choose_text(rng: random.Random, items: list[Any]) -> str:
|
||||
item = _weighted_choice(rng, items)
|
||||
return _text_from_entry(item)
|
||||
|
||||
|
||||
def _text_from_entry(item: Any) -> str:
|
||||
if isinstance(item, dict):
|
||||
return str(
|
||||
item.get("template")
|
||||
@@ -134,13 +157,22 @@ def apply_location_config_to_legacy_row(
|
||||
else:
|
||||
choices = location_entries
|
||||
scene_rng = seed_policy.axis_rng(seed_config, "scene", seed, row_number)
|
||||
scene_slug, scene_text = _choose_pair(scene_rng, choices)
|
||||
scene_choice = _weighted_choice(scene_rng, choices)
|
||||
scene_slug, scene_text = _pair_from(scene_choice)
|
||||
scene_entry = _metadata_entry(scene_choice, slug=scene_slug, text=scene_text)
|
||||
old_slug = str(row.get("scene") or "")
|
||||
old_text = legacy_scene_text_for_slug(old_slug)
|
||||
row["source_scene"] = old_slug
|
||||
row["source_scene_text"] = old_text
|
||||
row["scene"] = scene_slug
|
||||
row["scene_text"] = scene_text
|
||||
row["scene_entry"] = scene_entry
|
||||
row["location_theme"] = str(location_config.get("theme") or "")
|
||||
row["scene_theme"] = scene_entry.get("theme", "") or (
|
||||
str(location_config.get("theme") or "")
|
||||
if location_config.get("apply_mode") == "replace"
|
||||
else ""
|
||||
)
|
||||
row["location_config"] = location_config
|
||||
if old_text:
|
||||
row["prompt"] = str(row.get("prompt") or "").replace(f"Scene: {old_text}.", f"Scene: {scene_text}.")
|
||||
@@ -178,12 +210,16 @@ def apply_composition_config_to_legacy_row(
|
||||
else:
|
||||
choices = composition_entries
|
||||
composition_rng = seed_policy.axis_rng(seed_config, "composition", seed, row_number)
|
||||
new_composition = _choose_text(composition_rng, choices)
|
||||
composition_choice = _weighted_choice(composition_rng, choices)
|
||||
new_composition = _text_from_entry(composition_choice)
|
||||
composition_entry = _metadata_entry(composition_choice, text=new_composition)
|
||||
old_composition = str(row.get("composition") or "")
|
||||
old_prompt_fragment = f"Composition: vertical {old_composition}."
|
||||
new_prompt_fragment = f"Composition: {row_camera.composition_prompt(new_composition)}."
|
||||
row["source_composition"] = old_composition
|
||||
row["composition"] = new_composition
|
||||
row["composition_entry"] = composition_entry
|
||||
row["composition_theme"] = str(composition_config.get("theme") or "")
|
||||
row["composition_prompt"] = row_camera.composition_prompt(new_composition)
|
||||
row["composition_config"] = composition_config
|
||||
if old_composition:
|
||||
|
||||
+34
-8
@@ -23,6 +23,7 @@ except ImportError: # Allows local smoke tests from the repository root.
|
||||
class PromptAxesRoute:
|
||||
scene_slug: str
|
||||
scene: str
|
||||
scene_entry: dict[str, Any]
|
||||
pose: str
|
||||
expression: str
|
||||
shared_expression: str
|
||||
@@ -30,11 +31,13 @@ class PromptAxesRoute:
|
||||
character_expression_text: str
|
||||
source_composition: str
|
||||
composition: str
|
||||
composition_entry: dict[str, Any]
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"scene_slug": self.scene_slug,
|
||||
"scene": self.scene,
|
||||
"scene_entry": dict(self.scene_entry),
|
||||
"pose": self.pose,
|
||||
"expression": self.expression,
|
||||
"shared_expression": self.shared_expression,
|
||||
@@ -42,9 +45,29 @@ class PromptAxesRoute:
|
||||
"character_expression_text": self.character_expression_text,
|
||||
"source_composition": self.source_composition,
|
||||
"composition": self.composition,
|
||||
"composition_entry": dict(self.composition_entry),
|
||||
}
|
||||
|
||||
|
||||
def _metadata_entry(value: Any, *, slug: str = "", text: str = "") -> dict[str, Any]:
|
||||
if isinstance(value, dict):
|
||||
entry = dict(value)
|
||||
elif isinstance(value, (list, tuple)) and len(value) == 2:
|
||||
entry = {"slug": str(value[0]), "prompt": str(value[1])}
|
||||
else:
|
||||
entry = {"prompt": str(value or "")}
|
||||
if slug:
|
||||
entry["slug"] = slug
|
||||
if text:
|
||||
if "prompt" in entry:
|
||||
entry["prompt"] = text
|
||||
elif "text" in entry:
|
||||
entry["text"] = text
|
||||
else:
|
||||
entry["prompt"] = text
|
||||
return entry
|
||||
|
||||
|
||||
def resolve_prompt_axes_result(
|
||||
*,
|
||||
category: dict[str, Any],
|
||||
@@ -75,14 +98,14 @@ def resolve_prompt_axes_result(
|
||||
character_slot_map = character_slot_map or {}
|
||||
pov_character_labels = pov_character_labels or []
|
||||
|
||||
scene_slug, scene = row_item_policy.choose_pair(
|
||||
scene_rng,
|
||||
category_policy.compatible_entries(
|
||||
scene_entries = category_policy.compatible_entries(
|
||||
row_pool_policy.scene_pool(category, subcategory, item, subject_type, location_config),
|
||||
women_count,
|
||||
men_count,
|
||||
),
|
||||
)
|
||||
scene_choice = row_item_policy.weighted_choice(scene_rng, scene_entries)
|
||||
scene_slug, scene = row_item_policy.pair_from(scene_choice)
|
||||
scene_entry = _metadata_entry(scene_choice, slug=scene_slug, text=scene)
|
||||
pose = str(
|
||||
category_policy.merged_field(category, subcategory, item, "pose", "")
|
||||
or context.get("fallback_pose")
|
||||
@@ -137,21 +160,23 @@ def resolve_prompt_axes_result(
|
||||
if character_expression_text:
|
||||
expression = character_expression_text
|
||||
|
||||
source_composition = row_item_policy.choose_text(
|
||||
composition_rng,
|
||||
category_policy.compatible_entries(
|
||||
composition_entries = category_policy.compatible_entries(
|
||||
row_pool_policy.composition_pool(category, subcategory, item, subject_type, composition_config),
|
||||
women_count,
|
||||
men_count,
|
||||
),
|
||||
)
|
||||
composition_choice = row_item_policy.weighted_choice(composition_rng, composition_entries)
|
||||
source_composition = row_item_policy.item_text(composition_choice)
|
||||
composition_entry = _metadata_entry(composition_choice, text=source_composition)
|
||||
if is_pose_category:
|
||||
source_composition = sanitize_hardcore_environment_anchors(source_composition)
|
||||
composition_entry["prompt"] = source_composition
|
||||
composition = pov_policy.pov_composition_prompt(source_composition, pov_character_labels)
|
||||
|
||||
return PromptAxesRoute(
|
||||
scene_slug=scene_slug,
|
||||
scene=scene,
|
||||
scene_entry=scene_entry,
|
||||
pose=pose,
|
||||
expression=expression,
|
||||
shared_expression=shared_expression,
|
||||
@@ -159,6 +184,7 @@ def resolve_prompt_axes_result(
|
||||
character_expression_text=character_expression_text,
|
||||
source_composition=source_composition,
|
||||
composition=composition,
|
||||
composition_entry=composition_entry,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+68
-4
@@ -611,9 +611,16 @@ def smoke_config_route_location_theme() -> None:
|
||||
composition = _expect_text("config_route_location_theme.composition", row.get("composition"), 10)
|
||||
camera = _expect_text("config_route_location_theme.camera_directive", row.get("camera_directive"), 20)
|
||||
scene_directive = _expect_text("config_route_location_theme.camera_scene_directive", row.get("camera_scene_directive"), 40)
|
||||
scene_profile = row.get("scene_camera_profile") if isinstance(row.get("scene_camera_profile"), dict) else {}
|
||||
_expect("library" in scene.lower() or "bookshelves" in scene.lower(), "location theme did not drive scene")
|
||||
_expect("books" in composition.lower() or "shelf" in composition.lower() or "library" in composition.lower(), "location theme did not drive composition")
|
||||
_expect(row.get("location_theme") == "classical_library", "location theme did not survive into row metadata")
|
||||
_expect(row.get("scene_theme") == "classical_library", "selected scene theme did not survive into row metadata")
|
||||
_expect(row.get("composition_theme") == "classical_library", "composition theme did not survive into row metadata")
|
||||
_expect(row.get("scene_entry", {}).get("theme") == "classical_library", "selected scene entry lost theme metadata")
|
||||
_expect("Library camera layout" in scene_directive, "location theme did not drive library camera-scene adapter")
|
||||
_expect(row.get("scene_camera_profile_key") == "classical_library", "row lost scene camera profile key")
|
||||
_expect(scene_profile.get("family") == "library", "row lost scene camera profile family")
|
||||
_expect("front-left quarter view" in scene_directive, "library camera-scene adapter missed orbit direction")
|
||||
_expect("bag" not in composition.lower() and "shoes" not in composition.lower(), "location theme composition leaked outfit-check props")
|
||||
_expect("315-degree front-left quarter view" in camera, "config route did not preserve orbit camera directive")
|
||||
@@ -964,8 +971,44 @@ def smoke_location_config_policy() -> None:
|
||||
theme="classical_library",
|
||||
)
|
||||
_expect("classical_library" in theme_summary, "Themed location summary lost theme name")
|
||||
_expect(json.loads(themed_location).get("scene_entries"), "Themed location did not output locations")
|
||||
_expect(json.loads(themed_composition).get("composition_entries"), "Themed location did not output compositions")
|
||||
themed_location_payload = json.loads(themed_location)
|
||||
themed_composition_payload = json.loads(themed_composition)
|
||||
_expect(themed_location_payload.get("scene_entries"), "Themed location did not output locations")
|
||||
_expect(themed_location_payload.get("theme") == "classical_library", "Themed location config lost theme metadata")
|
||||
_expect(
|
||||
all(
|
||||
not isinstance(entry, dict) or entry.get("theme") == "classical_library"
|
||||
for entry in themed_location_payload.get("scene_entries") or []
|
||||
),
|
||||
"Themed location entries lost theme metadata",
|
||||
)
|
||||
_expect(themed_composition_payload.get("composition_entries"), "Themed location did not output compositions")
|
||||
_expect(themed_composition_payload.get("theme") == "classical_library", "Themed composition config lost theme metadata")
|
||||
parsed_themed = pb._parse_location_config(themed_location_payload)
|
||||
_expect(parsed_themed.get("theme") == "classical_library", "Location parser lost theme metadata")
|
||||
replaced_after_theme = json.loads(
|
||||
location_config.build_location_pool_json(
|
||||
enabled=True,
|
||||
combine_mode="replace",
|
||||
preset="custom_only",
|
||||
custom_locations="plain_room: plain room after theme",
|
||||
location_config=themed_location_payload,
|
||||
)
|
||||
)
|
||||
_expect(replaced_after_theme.get("theme") == "", "Location replace mode should not inherit upstream theme metadata")
|
||||
replaced_composition_after_theme = json.loads(
|
||||
location_config.build_composition_pool_json(
|
||||
enabled=True,
|
||||
combine_mode="replace",
|
||||
preset="custom_only",
|
||||
custom_compositions="plain composition after theme",
|
||||
composition_config=themed_composition_payload,
|
||||
)
|
||||
)
|
||||
_expect(
|
||||
replaced_composition_after_theme.get("theme") == "",
|
||||
"Composition replace mode should not inherit upstream theme metadata",
|
||||
)
|
||||
|
||||
|
||||
def smoke_row_location_policy() -> None:
|
||||
@@ -997,11 +1040,16 @@ def smoke_row_location_policy() -> None:
|
||||
"Row location policy did not apply forced custom scene text",
|
||||
)
|
||||
_expect(updated.get("source_scene") == "unknown_old_scene", "Row location policy lost source scene slug")
|
||||
_expect(updated.get("scene_entry", {}).get("slug") == "archive_corner", "Row location policy lost selected scene entry")
|
||||
_expect(
|
||||
"Scene: hidden archive corner with repeated shelves and warm table lamps. Pose:" in updated.get("prompt", ""),
|
||||
"Row location policy did not rewrite prompt scene",
|
||||
)
|
||||
_expect(updated.get("composition") == "long archive aisle composition", "Row location policy did not apply forced composition")
|
||||
_expect(
|
||||
updated.get("composition_entry", {}).get("prompt") == "long archive aisle composition",
|
||||
"Row location policy lost selected composition entry",
|
||||
)
|
||||
_expect(
|
||||
updated.get("composition_prompt") == "vertical long archive aisle composition",
|
||||
"Row location policy did not compute composition prompt",
|
||||
@@ -1239,10 +1287,19 @@ def smoke_row_prompt_axes_policy() -> None:
|
||||
_expect(route_result.scene_slug == "studio", "Typed prompt axes route lost selected scene slug")
|
||||
_expect(route["scene_slug"] == "studio", "Prompt axes route lost selected scene slug")
|
||||
_expect(route["scene"] == "quiet studio with repeatable anchors", "Prompt axes route lost selected scene text")
|
||||
_expect(route["scene_entry"].get("slug") == "studio", "Prompt axes route lost selected scene entry slug")
|
||||
_expect(
|
||||
route["scene_entry"].get("prompt") == "quiet studio with repeatable anchors",
|
||||
"Prompt axes route lost selected scene entry prompt",
|
||||
)
|
||||
_expect(route["pose"] == "standing fallback pose", "Prompt axes route lost selected fallback pose")
|
||||
_expect(route["expression"] == "", "Prompt axes route should omit expression when disabled")
|
||||
_expect(route["shared_expression"] == "", "Prompt axes route should omit shared expression when disabled")
|
||||
_expect(route["source_composition"] == "all participants visible centered frame", "Prompt axes route lost source composition")
|
||||
_expect(
|
||||
route["composition_entry"].get("prompt") == "all participants visible centered frame",
|
||||
"Prompt axes route lost selected composition entry",
|
||||
)
|
||||
|
||||
pov_route = row_prompt_axes.resolve_prompt_axes(
|
||||
**{**base_kwargs, "expression_disabled": True},
|
||||
@@ -2348,6 +2405,7 @@ def smoke_row_assembly_policy() -> None:
|
||||
"negative_prompt": "bad anatomy",
|
||||
"scene_slug": "test_room",
|
||||
"scene": "warm test room",
|
||||
"scene_entry": {"slug": "test_room", "prompt": "warm test room", "theme": "fixture_theme"},
|
||||
"pose": "standing close",
|
||||
"expression": "focused look",
|
||||
"shared_expression": "focused look",
|
||||
@@ -2358,6 +2416,7 @@ def smoke_row_assembly_policy() -> None:
|
||||
"expression_intensity_source": "disabled",
|
||||
"composition": "centered frame",
|
||||
"source_composition": "centered frame",
|
||||
"composition_entry": {"prompt": "centered frame"},
|
||||
"role_graph": "the visible partner stays centered",
|
||||
"source_role_graph": "Man A stays centered",
|
||||
"action_family": "test_action",
|
||||
@@ -2369,8 +2428,8 @@ def smoke_row_assembly_policy() -> None:
|
||||
"cast_descriptor_text": "Woman A: adult woman; Man A: adult man",
|
||||
"seed_config": {"content_seed": 123},
|
||||
"hardcore_position_config": {"family": "standing"},
|
||||
"location_config": {"location": "test_room"},
|
||||
"composition_config": {"composition": "centered"},
|
||||
"location_config": {"location": "test_room", "theme": "fixture_theme", "apply_mode": "replace"},
|
||||
"composition_config": {"composition": "centered", "theme": "fixture_theme"},
|
||||
"content_seed_axis": "pose",
|
||||
"count_adjustment": count_adjustment,
|
||||
"applied_profile": {"name": "profile_a"},
|
||||
@@ -2390,6 +2449,11 @@ def smoke_row_assembly_policy() -> None:
|
||||
_expect(row["source"] == "json_category", "Row assembly lost source marker")
|
||||
_expect(row["figure"] == "balanced cast", "Row assembly lost figure metadata")
|
||||
_expect(row["formatter_hints"] == {"krea": ["test_hint"]}, "Row assembly lost formatter hints")
|
||||
_expect(row["scene_entry"].get("slug") == "test_room", "Row assembly lost selected scene entry")
|
||||
_expect(row["location_theme"] == "fixture_theme", "Row assembly lost location theme")
|
||||
_expect(row["scene_theme"] == "fixture_theme", "Row assembly lost selected scene theme")
|
||||
_expect(row["composition_entry"].get("prompt") == "centered frame", "Row assembly lost selected composition entry")
|
||||
_expect(row["composition_theme"] == "fixture_theme", "Row assembly lost composition theme")
|
||||
_expect(row["cast_count_adjustment"] == count_adjustment, "Row assembly lost configured-cast count adjustment")
|
||||
_expect(row["content_seed_axis"] == "pose", "Row assembly lost content seed axis")
|
||||
_expect("POV participant: Man A" in row["prompt"], "Row assembly lost POV prompt directive")
|
||||
|
||||
Reference in New Issue
Block a user