motionloom 2.5.1 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,19 +18,21 @@ from pathlib import Path
18
18
  ROOT = Path(__file__).resolve().parents[2]
19
19
  FIXTURE_ROOT = ROOT / "examples/agent-consumer/asset-consistency"
20
20
  ANALYZER_PATH = ROOT / "scripts/asset-consistency.py"
21
+ PREFLIGHT_PATH = ROOT / "scripts/frame-set-preflight.py"
21
22
 
22
23
 
23
- def load_analyzer():
24
- spec = importlib.util.spec_from_file_location("asset_consistency", ANALYZER_PATH)
24
+ def load_module(path: Path, name: str):
25
+ spec = importlib.util.spec_from_file_location(name, path)
25
26
  if spec is None or spec.loader is None:
26
- raise RuntimeError("cannot load asset consistency analyzer")
27
+ raise RuntimeError(f"cannot load {path.name}")
27
28
  module = importlib.util.module_from_spec(spec)
28
29
  sys.modules[spec.name] = module
29
30
  spec.loader.exec_module(module)
30
31
  return module
31
32
 
32
33
 
33
- AC = load_analyzer()
34
+ AC = load_module(ANALYZER_PATH, "asset_consistency")
35
+ PREFLIGHT = load_module(PREFLIGHT_PATH, "frame_set_preflight")
34
36
 
35
37
 
36
38
  def read_json(name: str) -> dict:
@@ -112,6 +114,65 @@ def test_frame_contamination_and_pivot_drift() -> None:
112
114
  check(not result["ready"] and any(item["code"] == "pivot_drift" for item in result["errors"]), "pivot drift must block")
113
115
 
114
116
 
117
+ def test_generated_frame_set_preflight() -> None:
118
+ geometry = read_json("hero-walk-frame-geometry.json")
119
+ result = PREFLIGHT.validate(geometry, FIXTURE_ROOT)
120
+ check(result["ready"], f"isolated generated frame fixture must pass preflight: {result}")
121
+ check(result.get("approval") is False, "preflight must never emit approval")
122
+
123
+ shared = read_json("hero-walk-frame-geometry.json")
124
+ shared["frames"][1]["image"] = shared["frames"][0]["image"]
125
+ shared["frames"][1]["sha256"] = shared["frames"][0]["sha256"]
126
+ result = PREFLIGHT.validate(shared, FIXTURE_ROOT)
127
+ check(
128
+ not result["ready"] and any(item["code"] == "shared_source_image" for item in result["errors"]),
129
+ "generated multi-frame source must reject a shared pose-sheet image",
130
+ )
131
+
132
+ guard = read_json("hero-walk-frame-geometry.json")
133
+ guard["frames"][0]["safe_rect"] = {"x": 2, "y": 1, "width": 4, "height": 5}
134
+ guard["frames"][0]["bleed_margin_px"] = 1
135
+ result = PREFLIGHT.validate(guard, FIXTURE_ROOT)
136
+ check(
137
+ not result["ready"] and any(item["code"] == "guard_band_violation" for item in result["errors"]),
138
+ "generated frame alpha must preserve the declared transparent guard band",
139
+ )
140
+
141
+ with tempfile.TemporaryDirectory() as td:
142
+ root = Path(td)
143
+ geometry = read_json("hero-walk-frame-geometry.json")
144
+ geometry["frames"] = [copy.deepcopy(geometry["frames"][0]), copy.deepcopy(geometry["frames"][1])]
145
+ geometry["invariants"]["bbox_drift_tolerance_px"] = 0
146
+ for frame in geometry["frames"]:
147
+ frame["rect"] = {"x": 0, "y": 0, "width": 8, "height": 8}
148
+ frame["safe_rect"] = {"x": 0, "y": 0, "width": 8, "height": 8}
149
+ frame["bleed_margin_px"] = 0
150
+
151
+ first_pixels = solid(8, 8, (0, 0, 0, 0))
152
+ for y in range(1, 6):
153
+ for x in range(2, 6):
154
+ first_pixels[y * 8 + x] = (120, 160, 220, 255)
155
+ second_pixels = solid(8, 8, (0, 0, 0, 0))
156
+ for y in range(1, 6):
157
+ for x in range(1, 7):
158
+ second_pixels[y * 8 + x] = (120, 160, 220, 255)
159
+ write_rgba_png(root / "frame-00.png", 8, 8, first_pixels)
160
+ write_rgba_png(root / "frame-01.png", 8, 8, second_pixels)
161
+
162
+ geometry["frames"][0]["image"] = "frame-00.png"
163
+ geometry["frames"][0]["alpha_bbox"] = {"x": 2, "y": 1, "width": 4, "height": 5}
164
+ geometry["frames"][0]["sha256"] = hashlib.sha256((root / "frame-00.png").read_bytes()).hexdigest()
165
+ geometry["frames"][1]["image"] = "frame-01.png"
166
+ geometry["frames"][1]["alpha_bbox"] = {"x": 1, "y": 1, "width": 6, "height": 5}
167
+ geometry["frames"][1]["sha256"] = hashlib.sha256((root / "frame-01.png").read_bytes()).hexdigest()
168
+
169
+ result = PREFLIGHT.validate(geometry, root)
170
+ check(
171
+ not result["ready"] and any(item["code"] == "bbox_drift" for item in result["errors"]),
172
+ "generated frame apparent-size drift beyond tolerance must block instead of warning",
173
+ )
174
+
175
+
115
176
  def test_atlas_overlap_and_contamination() -> None:
116
177
  atlas = read_json("hero-atlas-contract.json")
117
178
  atlas["regions"][1]["rect"]["x"] = 4
@@ -179,11 +240,23 @@ def test_missing_file_and_cli_surface() -> None:
179
240
  data = json.loads(cli.stdout)
180
241
  check(cli.returncode == 0 and data.get("ready") is True and data.get("contract") == "layered_map", "CLI must expose the consistency validator")
181
242
 
243
+ preflight_cli = subprocess.run([
244
+ sys.executable, str(PREFLIGHT_PATH),
245
+ "--input", str(FIXTURE_ROOT / "hero-walk-frame-geometry.json"),
246
+ "--root", str(FIXTURE_ROOT), "--json",
247
+ ], cwd=ROOT, capture_output=True, text=True)
248
+ preflight_data = json.loads(preflight_cli.stdout)
249
+ check(
250
+ preflight_cli.returncode == 0 and preflight_data.get("ready") is True and preflight_data.get("approval") is False,
251
+ "generated frame-set preflight CLI must pass isolated fixture without granting approval",
252
+ )
253
+
182
254
 
183
255
  def main() -> int:
184
256
  test_pass_fixtures()
185
257
  test_identity_and_action_fail_closed()
186
258
  test_frame_contamination_and_pivot_drift()
259
+ test_generated_frame_set_preflight()
187
260
  test_atlas_overlap_and_contamination()
188
261
  test_layered_map_order_seam_and_bounds()
189
262
  test_missing_file_and_cli_surface()