Validate pair seed simulation behavior
This commit is contained in:
@@ -26,6 +26,8 @@ The map audit currently sees:
|
|||||||
- Route simulation family coverage, so representative generated rows exercise
|
- Route simulation family coverage, so representative generated rows exercise
|
||||||
every registered action and position family except documented special cases
|
every registered action and position family except documented special cases
|
||||||
covered by dedicated smoke fixtures.
|
covered by dedicated smoke fixtures.
|
||||||
|
- Pair seed simulation, so Insta/OF soft/hard metadata and formatter outputs
|
||||||
|
prove locked determinism and pose-only reroll behavior.
|
||||||
|
|
||||||
## Architectural Finding
|
## Architectural Finding
|
||||||
|
|
||||||
@@ -643,6 +645,8 @@ Near-term:
|
|||||||
scene/camera/clothing fields.
|
scene/camera/clothing fields.
|
||||||
- Keep same-room pair continuity synchronized in both assembled prompt text and
|
- Keep same-room pair continuity synchronized in both assembled prompt text and
|
||||||
`hardcore_row.scene_text`; `tools/prompt_smoke.py` covers this drift case.
|
`hardcore_row.scene_text`; `tools/prompt_smoke.py` covers this drift case.
|
||||||
|
- Keep pair seed behavior synchronized across soft/hard rows; the route
|
||||||
|
simulator now checks locked pair determinism and pose-only hard-action rerolls.
|
||||||
|
|
||||||
Medium-term:
|
Medium-term:
|
||||||
|
|
||||||
|
|||||||
@@ -1005,6 +1005,9 @@ issues for:
|
|||||||
the primary `position_key`;
|
the primary `position_key`;
|
||||||
- route-family coverage for registered action and position families, excluding
|
- route-family coverage for registered action and position families, excluding
|
||||||
only documented special cases that have their own smoke fixtures;
|
only documented special cases that have their own smoke fixtures;
|
||||||
|
- pair seed determinism for Insta/OF metadata and formatted soft/hard outputs;
|
||||||
|
- pair pose rerolls changing hardcore action metadata while keeping cast,
|
||||||
|
scene, soft outfit, and composition axes stable;
|
||||||
- pose-axis rerolls changing cast/scene metadata or failing to move pose/action
|
- pose-axis rerolls changing cast/scene metadata or failing to move pose/action
|
||||||
metadata.
|
metadata.
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,14 @@ AUDIT_DOC_SNIPPETS: tuple[tuple[str, str], ...] = (
|
|||||||
"docs/prompt-pool-routing-map.md",
|
"docs/prompt-pool-routing-map.md",
|
||||||
"route-family coverage for registered action and position families",
|
"route-family coverage for registered action and position families",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"docs/prompt-pool-routing-map.md",
|
||||||
|
"pair seed determinism for Insta/OF metadata",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"docs/prompt-pool-routing-map.md",
|
||||||
|
"pair pose rerolls changing hardcore action metadata",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
PROMPT_ROW_READ_SCAN_GLOBS: tuple[str, ...] = (
|
PROMPT_ROW_READ_SCAN_GLOBS: tuple[str, ...] = (
|
||||||
|
|||||||
@@ -858,6 +858,29 @@ def _insta_pair_case(seed: int, *, pov: bool, position: str, focus: str, family:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _pair_seed_probe(seed: int, *, reroll_axis: str = "none", reroll_seed: int = -1) -> dict[str, Any]:
|
||||||
|
return pb.build_insta_of_pair(
|
||||||
|
row_number=1,
|
||||||
|
start_index=1,
|
||||||
|
seed=seed,
|
||||||
|
ethnicity="any",
|
||||||
|
figure="random",
|
||||||
|
no_plus_women=False,
|
||||||
|
no_black=False,
|
||||||
|
trigger=TRIGGER,
|
||||||
|
prepend_trigger_to_prompt=True,
|
||||||
|
seed_config=pb.build_seed_lock_config_json(base_seed=seed, reroll_axis=reroll_axis, reroll_seed=reroll_seed),
|
||||||
|
options_json=_insta_options(),
|
||||||
|
character_cast=_random_character_cast(),
|
||||||
|
hardcore_position_config=_position_filter("penetration_only", "penetration", ["missionary", "doggy", "cowgirl"]),
|
||||||
|
location_config=_seed_probe_location_config(),
|
||||||
|
composition_config=_seed_probe_composition_config(),
|
||||||
|
camera_config=_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=6.0),
|
||||||
|
softcore_camera_config=_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=5.5),
|
||||||
|
hardcore_camera_config=_orbit_camera(horizontal_angle=135, vertical_angle=20, zoom=7.5),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _seed_probe_row(seed: int, *, reroll_axis: str = "none", reroll_seed: int = -1) -> dict[str, Any]:
|
def _seed_probe_row(seed: int, *, reroll_axis: str = "none", reroll_seed: int = -1) -> dict[str, Any]:
|
||||||
return pb.build_prompt(
|
return pb.build_prompt(
|
||||||
category="Hardcore sexual poses",
|
category="Hardcore sexual poses",
|
||||||
@@ -903,6 +926,27 @@ def _seed_probe_snapshot(row: dict[str, Any]) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _pair_seed_snapshot(pair: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
soft_row = pair.get("softcore_row") if isinstance(pair.get("softcore_row"), dict) else {}
|
||||||
|
hard_row = pair.get("hardcore_row") if isinstance(pair.get("hardcore_row"), dict) else {}
|
||||||
|
return {
|
||||||
|
"shared_cast_descriptors": pair.get("shared_cast_descriptors"),
|
||||||
|
"soft_cast_descriptor_text": soft_row.get("cast_descriptor_text"),
|
||||||
|
"hard_cast_descriptor_text": hard_row.get("cast_descriptor_text"),
|
||||||
|
"soft_scene_text": soft_row.get("scene_text"),
|
||||||
|
"hard_scene_text": hard_row.get("scene_text"),
|
||||||
|
"soft_item": soft_row.get("item"),
|
||||||
|
"hard_item": hard_row.get("item"),
|
||||||
|
"hard_position_key": hard_row.get("position_key"),
|
||||||
|
"hard_position_keys": hard_row.get("position_keys") or [],
|
||||||
|
"hard_source_role_graph": hard_row.get("source_role_graph"),
|
||||||
|
"soft_composition": soft_row.get("composition"),
|
||||||
|
"hard_composition": hard_row.get("composition"),
|
||||||
|
"soft_expression": soft_row.get("character_expression_text"),
|
||||||
|
"hard_expression": hard_row.get("character_expression_text"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _same_fields_issues(
|
def _same_fields_issues(
|
||||||
name: str,
|
name: str,
|
||||||
base: dict[str, Any],
|
base: dict[str, Any],
|
||||||
@@ -926,6 +970,24 @@ def _formatter_output_texts(row: dict[str, Any]) -> dict[str, str]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _pair_formatter_output_texts(pair: dict[str, Any]) -> dict[str, str]:
|
||||||
|
texts: dict[str, str] = {}
|
||||||
|
for target in ("softcore", "hardcore"):
|
||||||
|
formatted = _format_metadata(pair, target)
|
||||||
|
texts[f"{target}.krea"] = str(
|
||||||
|
formatted["krea"].get(f"krea_{target}_prompt")
|
||||||
|
or formatted["krea"].get("krea_prompt")
|
||||||
|
or ""
|
||||||
|
)
|
||||||
|
texts[f"{target}.sdxl"] = str(
|
||||||
|
formatted["sdxl"].get(f"sdxl_{target}_prompt")
|
||||||
|
or formatted["sdxl"].get("sdxl_prompt")
|
||||||
|
or ""
|
||||||
|
)
|
||||||
|
texts[f"{target}.caption"] = str(formatted["caption"].get("natural_caption") or "")
|
||||||
|
return texts
|
||||||
|
|
||||||
|
|
||||||
def _seed_determinism_check(seed: int) -> dict[str, Any]:
|
def _seed_determinism_check(seed: int) -> dict[str, Any]:
|
||||||
first = _seed_probe_row(seed)
|
first = _seed_probe_row(seed)
|
||||||
second = _seed_probe_row(seed)
|
second = _seed_probe_row(seed)
|
||||||
@@ -942,6 +1004,22 @@ def _seed_determinism_check(seed: int) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _pair_seed_determinism_check(seed: int) -> dict[str, Any]:
|
||||||
|
first = _pair_seed_probe(seed)
|
||||||
|
second = _pair_seed_probe(seed)
|
||||||
|
issues: list[str] = []
|
||||||
|
if first != second:
|
||||||
|
issues.append("locked seed config did not reproduce identical pair metadata")
|
||||||
|
if _pair_formatter_output_texts(first) != _pair_formatter_output_texts(second):
|
||||||
|
issues.append("locked seed config did not reproduce identical pair formatter outputs")
|
||||||
|
return {
|
||||||
|
"name": "pair_seed.locked_determinism",
|
||||||
|
"base": _row_summary(first.get("hardcore_row") or {}),
|
||||||
|
"changed": False,
|
||||||
|
"issues": issues,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _seed_reroll_check(
|
def _seed_reroll_check(
|
||||||
seed: int,
|
seed: int,
|
||||||
*,
|
*,
|
||||||
@@ -982,6 +1060,51 @@ def _seed_reroll_check(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _pair_seed_pose_reroll_check(seed: int) -> dict[str, Any]:
|
||||||
|
name = "pair_seed.pose_reroll"
|
||||||
|
base = _pair_seed_probe(seed)
|
||||||
|
base_snapshot = _pair_seed_snapshot(base)
|
||||||
|
changed = False
|
||||||
|
changed_seed = None
|
||||||
|
changed_field_names: list[str] = []
|
||||||
|
issues: list[str] = []
|
||||||
|
stable_fields = (
|
||||||
|
"shared_cast_descriptors",
|
||||||
|
"soft_cast_descriptor_text",
|
||||||
|
"hard_cast_descriptor_text",
|
||||||
|
"soft_scene_text",
|
||||||
|
"hard_scene_text",
|
||||||
|
"soft_item",
|
||||||
|
"soft_composition",
|
||||||
|
"hard_composition",
|
||||||
|
)
|
||||||
|
changed_fields = ("hard_position_key", "hard_item", "hard_source_role_graph")
|
||||||
|
for reroll_seed in range(seed + 1, seed + 16):
|
||||||
|
rerolled = _pair_seed_probe(seed, reroll_axis="pose", reroll_seed=reroll_seed)
|
||||||
|
rerolled_snapshot = _pair_seed_snapshot(rerolled)
|
||||||
|
field_issues = _same_fields_issues(name, base_snapshot, rerolled_snapshot, stable_fields, reroll_seed)
|
||||||
|
if field_issues:
|
||||||
|
issues.extend(field_issues)
|
||||||
|
break
|
||||||
|
changed_field_names = [
|
||||||
|
field for field in changed_fields if base_snapshot.get(field) != rerolled_snapshot.get(field)
|
||||||
|
]
|
||||||
|
if changed_field_names:
|
||||||
|
changed = True
|
||||||
|
changed_seed = reroll_seed
|
||||||
|
break
|
||||||
|
if not changed:
|
||||||
|
issues.append("pair pose reroll did not change hard_position_key, hard_item, or hard_source_role_graph within 15 attempts")
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"base": _row_summary(base.get("hardcore_row") or {}),
|
||||||
|
"changed": changed,
|
||||||
|
"changed_seed": changed_seed,
|
||||||
|
"changed_fields": changed_field_names,
|
||||||
|
"issues": issues,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _seed_axis_checks(seed: int) -> list[dict[str, Any]]:
|
def _seed_axis_checks(seed: int) -> list[dict[str, Any]]:
|
||||||
return [
|
return [
|
||||||
_seed_determinism_check(seed),
|
_seed_determinism_check(seed),
|
||||||
@@ -1018,6 +1141,13 @@ def _seed_axis_checks(seed: int) -> list[dict[str, Any]]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _pair_seed_checks(seed: int) -> list[dict[str, Any]]:
|
||||||
|
return [
|
||||||
|
_pair_seed_determinism_check(seed),
|
||||||
|
_pair_seed_pose_reroll_check(seed),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _route_family_coverage_check(
|
def _route_family_coverage_check(
|
||||||
name: str,
|
name: str,
|
||||||
*,
|
*,
|
||||||
@@ -1100,6 +1230,7 @@ def run_simulation(seed: int = 3901, *, include_prompts: bool = False) -> dict[s
|
|||||||
cases.extend(_pair_reports("insta_pair.pov_outercourse", pov_pair, include_prompts=include_prompts))
|
cases.extend(_pair_reports("insta_pair.pov_outercourse", pov_pair, include_prompts=include_prompts))
|
||||||
coverage_checks = _route_family_coverage_checks(cases)
|
coverage_checks = _route_family_coverage_checks(cases)
|
||||||
axis_checks = _seed_axis_checks(seed + 3)
|
axis_checks = _seed_axis_checks(seed + 3)
|
||||||
|
pair_seed_checks = _pair_seed_checks(seed + 4)
|
||||||
issues = [
|
issues = [
|
||||||
{"case": case["name"], "issue": issue}
|
{"case": case["name"], "issue": issue}
|
||||||
for case in cases
|
for case in cases
|
||||||
@@ -1115,18 +1246,25 @@ def run_simulation(seed: int = 3901, *, include_prompts: bool = False) -> dict[s
|
|||||||
for check in axis_checks
|
for check in axis_checks
|
||||||
for issue in check.get("issues", [])
|
for issue in check.get("issues", [])
|
||||||
)
|
)
|
||||||
|
issues.extend(
|
||||||
|
{"case": check["name"], "issue": issue}
|
||||||
|
for check in pair_seed_checks
|
||||||
|
for issue in check.get("issues", [])
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
"summary": {
|
"summary": {
|
||||||
"seed": seed,
|
"seed": seed,
|
||||||
"cases": len(cases),
|
"cases": len(cases),
|
||||||
"coverage_checks": len(coverage_checks),
|
"coverage_checks": len(coverage_checks),
|
||||||
"axis_checks": len(axis_checks),
|
"axis_checks": len(axis_checks),
|
||||||
|
"pair_seed_checks": len(pair_seed_checks),
|
||||||
"issues": len(issues),
|
"issues": len(issues),
|
||||||
},
|
},
|
||||||
"issues": issues,
|
"issues": issues,
|
||||||
"cases": cases,
|
"cases": cases,
|
||||||
"coverage_checks": coverage_checks,
|
"coverage_checks": coverage_checks,
|
||||||
"axis_checks": axis_checks,
|
"axis_checks": axis_checks,
|
||||||
|
"pair_seed_checks": pair_seed_checks,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1135,7 +1273,8 @@ def _print_text_report(report: dict[str, Any]) -> None:
|
|||||||
print(
|
print(
|
||||||
f"Prompt route simulation: seed={summary.get('seed')} "
|
f"Prompt route simulation: seed={summary.get('seed')} "
|
||||||
f"cases={summary.get('cases')} coverage_checks={summary.get('coverage_checks')} "
|
f"cases={summary.get('cases')} coverage_checks={summary.get('coverage_checks')} "
|
||||||
f"axis_checks={summary.get('axis_checks')} issues={summary.get('issues')}"
|
f"axis_checks={summary.get('axis_checks')} pair_seed_checks={summary.get('pair_seed_checks')} "
|
||||||
|
f"issues={summary.get('issues')}"
|
||||||
)
|
)
|
||||||
for case in report.get("cases") or []:
|
for case in report.get("cases") or []:
|
||||||
summary_text = case.get("summary") or {}
|
summary_text = case.get("summary") or {}
|
||||||
@@ -1154,6 +1293,10 @@ def _print_text_report(report: dict[str, Any]) -> None:
|
|||||||
print(f"- {check.get('name')}: changed={check.get('changed')}")
|
print(f"- {check.get('name')}: changed={check.get('changed')}")
|
||||||
for issue in check.get("issues") or []:
|
for issue in check.get("issues") or []:
|
||||||
print(f" ISSUE {issue}")
|
print(f" ISSUE {issue}")
|
||||||
|
for check in report.get("pair_seed_checks") or []:
|
||||||
|
print(f"- {check.get('name')}: changed={check.get('changed')}")
|
||||||
|
for issue in check.get("issues") or []:
|
||||||
|
print(f" ISSUE {issue}")
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
|||||||
@@ -7918,6 +7918,7 @@ def smoke_prompt_route_simulation_policy() -> None:
|
|||||||
_expect(summary.get("cases") == 14, "Prompt route simulation case count changed unexpectedly")
|
_expect(summary.get("cases") == 14, "Prompt route simulation case count changed unexpectedly")
|
||||||
_expect(summary.get("coverage_checks") == 2, "Prompt route simulation lost family coverage checks")
|
_expect(summary.get("coverage_checks") == 2, "Prompt route simulation lost family coverage checks")
|
||||||
_expect(summary.get("axis_checks") == 6, "Prompt route simulation lost axis check coverage")
|
_expect(summary.get("axis_checks") == 6, "Prompt route simulation lost axis check coverage")
|
||||||
|
_expect(summary.get("pair_seed_checks") == 2, "Prompt route simulation lost pair seed check coverage")
|
||||||
_expect(summary.get("issues") == 0, f"Prompt route simulation reported issues: {report.get('issues')}")
|
_expect(summary.get("issues") == 0, f"Prompt route simulation reported issues: {report.get('issues')}")
|
||||||
cases = {case.get("name"): case for case in report.get("cases") or []}
|
cases = {case.get("name"): case for case in report.get("cases") or []}
|
||||||
for route_name in (
|
for route_name in (
|
||||||
@@ -7979,6 +7980,19 @@ def smoke_prompt_route_simulation_policy() -> None:
|
|||||||
"seed_axis.composition_reroll",
|
"seed_axis.composition_reroll",
|
||||||
):
|
):
|
||||||
_expect(axis_checks[check_name].get("changed") is True, f"{check_name} should prove its axis can reroll")
|
_expect(axis_checks[check_name].get("changed") is True, f"{check_name} should prove its axis can reroll")
|
||||||
|
pair_seed_checks = {check.get("name"): check for check in report.get("pair_seed_checks") or []}
|
||||||
|
for check_name in ("pair_seed.locked_determinism", "pair_seed.pose_reroll"):
|
||||||
|
check = pair_seed_checks.get(check_name) or {}
|
||||||
|
_expect(check, f"Prompt route simulation lost pair seed check {check_name}")
|
||||||
|
_expect(not check.get("issues"), f"Prompt route simulation pair seed check reported issues: {check_name}")
|
||||||
|
_expect(
|
||||||
|
pair_seed_checks["pair_seed.locked_determinism"].get("changed") is False,
|
||||||
|
"Pair locked determinism check should not be a reroll",
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
pair_seed_checks["pair_seed.pose_reroll"].get("changed") is True,
|
||||||
|
"Pair pose reroll should prove hard action can reroll while soft/cast/scene axes stay locked",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def smoke_node_camera_registration() -> None:
|
def smoke_node_camera_registration() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user