163 lines
7.9 KiB
Python
163 lines
7.9 KiB
Python
import json
|
|
|
|
import iamccs_prompter as prompter
|
|
|
|
|
|
def test_evolving_natural_language_infers_boundaries_and_frames():
|
|
beats = prompter.parse_evolving_timeline(
|
|
"0-5 seconds: walks through the city.\n"
|
|
"At 5 seconds she stops and looks up.\n"
|
|
"A 10 secondi inizia a correre.",
|
|
duration_seconds=20,
|
|
fps=24,
|
|
)
|
|
assert [(beat["start_frame"], beat["end_frame"]) for beat in beats] == [
|
|
(0, 120), (120, 240), (240, 480),
|
|
]
|
|
assert beats[2]["action"] == "inizia a correre"
|
|
|
|
|
|
def test_evolving_rejects_untimed_prose():
|
|
try:
|
|
prompter.parse_evolving_timeline("She walks and later starts running.", duration_seconds=20)
|
|
except ValueError as exc:
|
|
assert "second marker" in str(exc)
|
|
else:
|
|
raise AssertionError("untimed evolving prose must fail instead of silently becoming continuous")
|
|
|
|
|
|
def test_prompter_exports_evolving_policy_and_demo_text():
|
|
project = prompter.default_project()
|
|
project["task_mode"] = "fl2va"
|
|
project["sections"]["action"] = "The woman crosses the city in one uninterrupted take."
|
|
project["extended_conditioning_policy"] = "evolving"
|
|
project["evolving_timeline"] = prompter.EVOLVING_DEMO_TIMELINE
|
|
output = prompter.IAMCCS_Prompter().compose(
|
|
json.dumps(project), "fl2va", "global", "guided", "replace", 6800
|
|
)
|
|
result = output["result"]
|
|
linx = result[0]
|
|
request = linx["resources"]["iamccs_prompter_injection"]
|
|
report = json.loads(result[3])
|
|
assert request["extended_conditioning_policy"] == "evolving"
|
|
assert "At 10 seconds" in request["evolving_timeline"]
|
|
assert report["extended_conditioning"]["evolving_event_count"] == 4
|
|
assert report["extended_conditioning"]["required_shotboard_mode"] == "fl2va_extended_av"
|
|
|
|
|
|
def test_existing_projects_without_policy_migrate_to_default():
|
|
project = prompter._safe_project({"schema_version": 4, "sections": {"scene": "A street."}})
|
|
assert project["schema_version"] == 7
|
|
assert project["extended_conditioning_policy"] == "default"
|
|
assert project["evolving_timeline"] == ""
|
|
|
|
|
|
def test_fl2va_continuous_is_one_untimed_global_injection_without_local_prompt():
|
|
project = prompter.default_project()
|
|
project["task_mode"] = "fl2va"
|
|
project["extended_conditioning_policy"] = "continuous"
|
|
project["sections"].update({
|
|
"boundary_frames": "Picture 1 is the exact opening visual authority.",
|
|
"identity_continuity_locks": "Preserve the same woman and bicycle.",
|
|
"action": "0-8 seconds: the woman pedals steadily toward the bridge.",
|
|
"shot_list": "At 8 seconds she keeps pedaling steadily toward the bridge.",
|
|
"camera": "One steady backward tracking camera.",
|
|
})
|
|
output = prompter.IAMCCS_Prompter().compose(
|
|
json.dumps(project), "fl2va", "local_1", "guided", "replace", 6800
|
|
)
|
|
linx, final_prompt, _project_json, report_json, *_ = output["result"]
|
|
report = json.loads(report_json)
|
|
injections = linx["resources"]["iamccs_prompter_injections"]
|
|
assert [item["target"] for item in injections] == ["global"]
|
|
assert "continuous_action:" in final_prompt
|
|
assert "the woman pedals steadily toward the bridge" in final_prompt
|
|
assert "0-8 seconds" not in final_prompt
|
|
assert "At 8 seconds" not in final_prompt
|
|
assert linx["resources"]["iamccs_prompter_local_prompt"] == ""
|
|
assert report["local_characters"] == 0
|
|
|
|
|
|
def test_fl2va_default_keeps_normal_global_and_local_contract():
|
|
project = prompter.default_project()
|
|
project["task_mode"] = "fl2va"
|
|
project["sections"].update({
|
|
"identity_continuity_locks": "Preserve the same woman.",
|
|
"action": "The woman turns toward the window.",
|
|
})
|
|
output = prompter.IAMCCS_Prompter().compose(
|
|
json.dumps(project), "fl2va", "local_1", "guided", "replace", 6800
|
|
)
|
|
linx = output["result"][0]
|
|
assert project["extended_conditioning_policy"] == "default"
|
|
assert [item["target"] for item in linx["resources"]["iamccs_prompter_injections"]] == ["global", "local_1"]
|
|
|
|
|
|
def test_fl2va_backend_separates_static_global_from_action_local():
|
|
project = prompter.default_project()
|
|
project["task_mode"] = "fl2va"
|
|
project["sections"].update({
|
|
"boundary_frames": "Picture 1 opens the take and Picture 2 defines the final composition.",
|
|
"identity_continuity_locks": "Preserve the same soldier woman and army throughout.",
|
|
"action": "The soldier woman walks toward camera and raises her sword.",
|
|
"shot_list": "At 4 seconds she raises her sword.",
|
|
"camera": "One steady backward tracking shot; no cuts or camera reset.",
|
|
"production_sound": "Footsteps and one sword draw at 4 seconds.",
|
|
"non_diegetic_music": "No score.",
|
|
})
|
|
final_prompt, details = prompter._compose_prompt(project, "fl2va", "guided", "")
|
|
assert "raises her sword" not in final_prompt
|
|
assert "At 4 seconds" not in final_prompt
|
|
assert "no cuts" not in final_prompt.lower()
|
|
assert "Preserve the same soldier woman" in final_prompt
|
|
assert "raises her sword" in details["local_prompt"]
|
|
assert "At 4 seconds" in details["local_prompt"]
|
|
|
|
|
|
def test_final_prompt_override_is_preserved_by_backend():
|
|
project = prompter.default_project()
|
|
project["task_mode"] = "fl2va"
|
|
project["sections"]["identity_continuity_locks"] = "Generated identity continuity."
|
|
project["sections"]["action"] = "Generated local action."
|
|
project["final_prompt_override_enabled"] = True
|
|
project["final_prompt_override"] = "MANUALLY EDITED GLOBAL"
|
|
project["final_local_prompt_override_enabled"] = True
|
|
project["final_local_prompt_override"] = "MANUALLY EDITED LOCAL"
|
|
output = prompter.IAMCCS_Prompter().compose(json.dumps(project), "fl2va", "global", "guided", "replace", 6800)
|
|
linx, final_prompt, _project_json, report_json, *_ = output["result"]
|
|
report = json.loads(report_json)
|
|
assert final_prompt == "MANUALLY EDITED GLOBAL"
|
|
assert linx["resources"]["iamccs_prompter_local_prompt"] == "MANUALLY EDITED LOCAL"
|
|
assert report["final_prompt_override"] is True
|
|
assert report["final_local_prompt_override"] is True
|
|
|
|
|
|
def test_canonical_evolving_repairs_malformed_ai_timeline_before_queue():
|
|
raw = (
|
|
"0-12 seconds: the soldier woman takes a deep breath and walks toward the camera while the army follows behind her in the same formation and direction. "
|
|
"[ONSET_once] 12-22 seconds: the soldier woman gives one brief rage scream toward the camera and the soldiers immediately answer with one brief collective scream "
|
|
"[SUSTAIN] after both screams have finished, they continue advancing toward the camera in the same formation without repeating either scream before\n"
|
|
"22 seconds. 22-30 seconds: the soldier woman stops and starts to laugh while the army behind her starts to sing epic battle songs; continue this final performance naturally through the end"
|
|
)
|
|
canonical = prompter._validate_canonical_evolving_timeline(raw)
|
|
lines = canonical.splitlines()
|
|
assert len(lines) == 3
|
|
assert lines[0].startswith("0-12 seconds: [ONSET ONCE]")
|
|
assert "[THEN SUSTAIN] the soldier woman walks toward the camera" in lines[0]
|
|
assert lines[1].startswith("12-22 seconds: [ONSET ONCE]")
|
|
assert "[THEN SUSTAIN] they continue advancing toward the camera" in lines[1]
|
|
assert "after both screams" not in lines[1].lower()
|
|
assert "without repeating" not in lines[1].lower()
|
|
assert lines[2].startswith("22-30 seconds: [ONSET ONCE] the soldier woman stops")
|
|
assert "continues laughing" in lines[2]
|
|
assert "continues singing" in lines[2]
|
|
assert "[ONSET_once]" not in canonical
|
|
assert "[SUSTAIN]" not in canonical
|
|
|
|
|
|
def test_canonical_evolving_timestamp_precedes_all_tags():
|
|
raw = "[ONSET_once] 12-22 seconds: she shouts once [SUSTAIN] she keeps marching"
|
|
canonical = prompter._validate_canonical_evolving_timeline(raw)
|
|
assert canonical == "12-22 seconds: [ONSET ONCE] she shouts once; [THEN SUSTAIN] she keeps marching"
|
|
|