Files

158 lines
7.6 KiB
Python

"""Focus infusion: a camera focus now and then aims at a word the user's prompts keep returning to."""
import re
import pytest
from .. import engine, memory, nodes
WALK = "She walks down the street, her legs moving steadily."
CAMERA_ON = {"camera_moves": True, "camera_moves_chance": 1}
@pytest.fixture(autouse=True)
def store(tmp_path, monkeypatch):
monkeypatch.setenv("SHOT_CAMERA_MEMORY", str(tmp_path / "m.json"))
def _habit(*lemmas, prompts=3):
for k in range(prompts):
memory.observe(f"p{k}", list(lemmas), "P")
def _move(**values):
out, said, chose = nodes.rewrite(f"[Shot 1] {WALK}", {**CAMERA_ON, **values}, seconds=5, pieces=[], project="P")
return out, said, chose["arms"]
def test_at_zero_nothing_changes_even_with_a_habit_on_record():
_habit("hand", "head")
base = _move()[0]
assert _move(focus_infusion_chance=0)[0] == base and "hand" not in base and "head" not in base
def test_at_one_the_focus_is_a_word_from_other_prompts_not_the_shots_own():
_habit("hand", "head")
for variation in range(15):
out, said, arms = _move(focus_infusion_chance=1, variation=variation)
sentence = out.split(WALK)[1]
assert "hand" in sentence or "head" in sentence, out
if " from " not in sentence:
assert "legs" not in sentence and "street" not in sentence, "instead of the shot's own focus, unless it travels between the two"
assert "infuse:yes" in arms and any(a in arms for a in ("infused:hand", "infused:head"))
assert "infused:" in said or "infused" in said
def test_nothing_is_infused_before_there_is_a_habit_or_from_a_word_seen_in_one_prompt():
out, _, arms = _move(focus_infusion_chance=1)
assert "infuse:yes" not in arms
_habit("hand", prompts=1)
assert memory.infusion_bank("P") == []
_habit("hand", prompts=3)
assert dict(memory.infusion_bank("P")).keys() == {"hand"}
def test_the_modules_own_words_are_never_infused():
_habit("camera", "angle", "moment", "view", "zoom", "fade", "black")
for variation in range(10):
_, _, arms = _move(focus_infusion_chance=1, variation=variation)
assert "infuse:yes" not in arms and not any(a.startswith("infused:") for a in arms)
def test_a_reframe_can_be_infused_too():
_habit("hand")
a, b = "She sways her hips from side to side.", "She turns and walks out through the door."
out, info = engine.add_reframes(f"[Shot 1] {a} {b}", seed=1, chance=1, pieces=[a, b], infuse=1.0, bank=memory.infusion_bank("P"))
assert "hand" in out.split(a)[1] and "infuse:yes" in info["arms"] and "infused:hand" in info["arms"]
plain, plain_info = engine.add_reframes(f"[Shot 1] {a} {b}", seed=1, chance=1, pieces=[a, b])
assert "infuse:yes" not in plain_info["arms"] and "hand" not in plain.split(a)[1]
def test_the_chance_follows_the_slider_exactly_at_the_ends_and_is_tilted_by_ratings_between(tmp_path):
assert memory.infuse_chance(0.0) == 0.0 and memory.infuse_chance(1.0) == 1.0 and memory.infuse_chance(0.5) == 0.5
memory.record_run("good", {"views": [], "arms": ["infuse:yes", "infused:hand"]})
memory.on_rating("good", "liked")
assert memory.infuse_chance(0.5) > 0.5 and memory.infuse_chance(1.0) == 1.0
memory.on_rating("good", "liked") # same rating again changes nothing
for k in range(8):
memory.record_run(f"bad{k}", {"views": [], "arms": ["infuse:yes", "infused:hand"]})
memory.on_rating(f"bad{k}", "disliked")
assert 0.0 < memory.infuse_chance(0.5) < 0.5 and memory.infuse_chance(0.0) == 0.0
def test_a_dislike_blames_the_infused_shots_first_and_a_change_of_mind_takes_it_back():
memory.record_run("r", {"views": [], "arms": ["move:yes", "style:hold", "word:legs", "infuse:yes", "infused:hand"]})
memory.on_rating("r", "disliked")
stats = memory.arm_stats()
assert stats["infuse:yes"][1] > 0 and stats["infused:hand"][1] > 0
assert stats.get("style:hold", (0, 0))[1] == 0 and stats.get("move:yes", (0, 0))[1] == 0, "the rest of the run is not blamed"
memory.on_rating("r", "liked")
stats = memory.arm_stats()
assert stats["infuse:yes"] == (0.5, 0.0) or stats["infuse:yes"][1] == 0, "the blame was taken back"
assert stats["style:hold"][0] > 0
def test_a_run_without_infusion_shares_the_blame_as_before():
memory.record_run("r", {"views": [], "arms": ["move:yes", "style:hold", "infuse:no"]})
memory.on_rating("r", "disliked")
stats = memory.arm_stats()
assert stats["move:yes"][1] > 0 and stats["style:hold"][1] > 0 and stats["infuse:no"][1] > 0
def test_one_dislike_lowers_a_word_but_never_removes_it_and_a_like_brings_it_back():
_habit("hand", "head")
start = dict(memory.infusion_bank("P"))["hand"]
memory.record_run("r", {"views": [], "arms": ["infuse:yes", "infused:hand"]})
memory.on_rating("r", "disliked")
after = dict(memory.infusion_bank("P"))
assert 0 < after["hand"] < start and after["head"] == dict(memory.infusion_bank("P"))["head"] == start
for k in range(3):
memory.record_run(f"l{k}", {"views": [], "arms": ["infuse:yes", "infused:hand"]})
memory.on_rating(f"l{k}", "liked")
assert dict(memory.infusion_bank("P"))["hand"] > after["hand"]
def test_the_probe_lists_each_subject_with_what_ratings_taught_and_forgetting_clears_it():
_habit("hand", "head")
memory.record_run("r", {"views": [], "arms": ["infuse:yes", "infused:hand"]})
memory.on_rating("r", "disliked")
rows = {r["word"]: r for r in memory.summary()["infusion"]}
assert rows["hand"]["bad"] > 0 and rows["hand"]["seen"] == 3 and rows["head"]["bad"] == 0
assert all(not a["arm"].startswith("infused:") for a in memory.summary()["arms"]), "shown once, as a subject"
assert memory.forget("word", "hand")
assert "infused:hand" not in memory.arm_stats()
def test_a_project_only_aims_at_words_its_own_prompts_keep_returning_to():
_habit("hand", "head") # project P
assert dict(memory.infusion_bank("P")).keys() == {"hand", "head"}
assert memory.infusion_bank("other") == [] and memory.infusion_bank("") == [], "an unrelated project, or no project, has no bank"
out, _, chose = nodes.rewrite(f"[Shot 1] {WALK}", {**CAMERA_ON, "focus_infusion_chance": 1}, seconds=5, pieces=[], project="other")
assert "infuse:yes" not in chose["arms"] and "hand" not in out and "head" not in out
def test_the_oldest_projects_are_forgotten_past_the_cap_and_a_forgotten_word_leaves_every_project():
for k in range(memory.MAX_PROJECTS + 3):
memory.observe("x", ["lamp"], f"proj{k}")
kept = memory._read()["projects"]
assert len(kept) == memory.MAX_PROJECTS and "proj0" not in kept and f"proj{memory.MAX_PROJECTS + 2}" in kept
assert memory.forget("word", "lamp") and all("lamp" not in r["seen"] for r in memory._read()["projects"].values())
def test_an_infused_shot_often_travels_from_its_own_subject_to_the_infused_word_or_back():
text = "[Shot 1] A woman holds a red umbrella and a lantern while she walks along the wet street."
travels = 0
for seed in range(200):
out, report = engine.add_camera_moves(text, seed=seed, chance=1, infuse=1.0, bank=[("window", 1.0)])
assert report[0]["infused"] == "window"
added = out[len(text):]
if re.search(r"from .+ to .+", added):
assert "window" in added, added
travels += 1
assert 60 < travels < 140, f"about half travel, got {travels}"
def test_a_word_the_shot_already_says_is_not_infused_even_when_it_is_not_a_candidate_of_the_shot():
got = [engine._infused("A woman walks.", set(), [("woman", 1.0)], __import__("random").Random(k)) for k in range(20)]
assert got == [None] * 20