From e91fc711500c5ecdca2d185d13007379905a1b5c Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:01:04 +0300 Subject: [PATCH] feat: camera moves include static holds on one object; X to Y fires less often Co-Authored-By: Claude Sonnet 5.5 --- shot_camera.py | 9 ++++++++- tests/test_shot_camera.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/shot_camera.py b/shot_camera.py index 816eb90d..8be5d5b5 100644 --- a/shot_camera.py +++ b/shot_camera.py @@ -61,7 +61,10 @@ MOVES_THEN = ("Then the camera pans to {y}.", "Then the camera racks focus to {y MOVES_FINISH = ("Then the camera zooms out.", "Then the camera pulls back.", "Then the camera pulls back to a wider view.") COUNT_WEIGHTS = (6, 3, 1) # one move / two / three, in a shot that gets any -TRAVEL_SHARE = 0.5 # of one-move shots with two topics, how many travel X -> Y +TRAVEL_SHARE = 0.25 # of one-move shots with two topics, how many travel X -> Y +HOLD_SHARE = 0.25 # of the other one-move shots, how many stay still on one thing +MOVES_HOLD = ("The camera holds steady on {x}.", "The camera stays still, focused on {x}.", + "The camera stays fixed, focused on {x}.") DETERMINERS = {"the", "a", "an", "this", "that", "these", "those"} POSSESSIVE_PRONOUNS = {"his", "her", "their", "its", "my", "your", "our"} @@ -277,6 +280,10 @@ def _plan(pool, last, rng, prior=None): x, y = _name(topics[0]), _name(topics[-1]) move = _pick(MOVES_TRAVEL, last, rng) return move.format(x=x, y=y), f"{x} -> {y}", move + if rng is not None and k == 1 and rng.random() < HOLD_SHARE: + hold = _pick(MOVES_HOLD, last, rng) # some shots want one fixed focus + target = _name(best) + return hold.format(x=target), target, hold first = topics[0] if (k > 1 and topics) else best moves = MOVES_DETAIL if (first[2] or first[0] in PARTS) else MOVES_OTHER opening = _pick(moves, last, rng) diff --git a/tests/test_shot_camera.py b/tests/test_shot_camera.py index 3176b483..bf236a3e 100644 --- a/tests/test_shot_camera.py +++ b/tests/test_shot_camera.py @@ -417,3 +417,20 @@ def test_a_move_never_ends_on_a_dangling_word(): P = HEAD + "[Shot 1] touches the base, then the skin, while watches." _out, rep = sc.add_camera_moves(P, seed=1, chance=1.0) assert rep[0]["move"] is None + + +def test_some_shots_hold_still_on_one_object_and_pans_are_the_minority(): + """Not every shot is a move: a static focus is part of the mix, and X -> Y is rarer than it.""" + pytest.importorskip("spacy") + kinds = {"hold": 0, "pan": 0, "other": 0} + for seed in range(300): + _o, rep = sc.add_camera_moves(TWO, seed=seed) + m = rep[0]["move"] + if "holds steady" in m or "stays still" in m or "stays fixed" in m: + kinds["hold"] += 1 + elif " from " in m and " to " in m and " Then" not in m: + kinds["pan"] += 1 + else: + kinds["other"] += 1 + assert kinds["hold"] > 20 and 0 < kinds["pan"] < kinds["other"], kinds + assert kinds["pan"] < 0.3 * 300, kinds