dimagent-linux-arm64
Advanced tools
| #!/usr/bin/env python3 | ||
| """Conditionally derive running-left by mirroring the approved running-right strip.""" | ||
| from __future__ import annotations | ||
| import argparse | ||
| import json | ||
| from datetime import datetime, timezone | ||
| from pathlib import Path | ||
| from PIL import Image, ImageOps | ||
| RUNNING_FRAME_COUNT = 8 | ||
| def load_manifest(run_dir: Path) -> dict[str, object]: | ||
| path = run_dir / "imagegen-jobs.json" | ||
| if not path.exists(): | ||
| raise SystemExit(f"job manifest not found: {path}") | ||
| return json.loads(path.read_text(encoding="utf-8")) | ||
| def job_list(manifest: dict[str, object]) -> list[dict[str, object]]: | ||
| jobs = manifest.get("jobs") | ||
| if not isinstance(jobs, list): | ||
| raise SystemExit("invalid imagegen-jobs.json: jobs must be a list") | ||
| return [job for job in jobs if isinstance(job, dict)] | ||
| def find_job(manifest: dict[str, object], job_id: str) -> dict[str, object]: | ||
| for job in job_list(manifest): | ||
| if job.get("id") == job_id: | ||
| return job | ||
| raise SystemExit(f"unknown job id: {job_id}") | ||
| def image_metadata(path: Path) -> dict[str, object]: | ||
| with Image.open(path) as image: | ||
| image.verify() | ||
| with Image.open(path) as image: | ||
| return { | ||
| "width": image.width, | ||
| "height": image.height, | ||
| "mode": image.mode, | ||
| "format": image.format, | ||
| } | ||
| def manifest_relative(path: Path, run_dir: Path) -> str: | ||
| return str(path.resolve().relative_to(run_dir.resolve())) | ||
| def mirror_strip_preserving_frame_order( | ||
| source: Image.Image, | ||
| frame_count: int = RUNNING_FRAME_COUNT, | ||
| ) -> Image.Image: | ||
| rgba = source.convert("RGBA") | ||
| mirrored = Image.new("RGBA", rgba.size, (0, 0, 0, 0)) | ||
| slot_width = rgba.width / frame_count | ||
| for index in range(frame_count): | ||
| left = round(index * slot_width) | ||
| right = round((index + 1) * slot_width) | ||
| mirrored.alpha_composite( | ||
| ImageOps.mirror(rgba.crop((left, 0, right, rgba.height))), | ||
| (left, 0), | ||
| ) | ||
| return mirrored | ||
| def main() -> None: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument("--run-dir", required=True) | ||
| parser.add_argument( | ||
| "--confirm-appropriate-mirror", | ||
| action="store_true", | ||
| help="Required after visually confirming the rightward strip can be mirrored without identity/prop issues.", | ||
| ) | ||
| parser.add_argument( | ||
| "--decision-note", | ||
| required=True, | ||
| help="Short note explaining why mirroring is acceptable for this pet.", | ||
| ) | ||
| parser.add_argument("--force", action="store_true") | ||
| args = parser.parse_args() | ||
| if not args.confirm_appropriate_mirror: | ||
| raise SystemExit("refusing to mirror without --confirm-appropriate-mirror") | ||
| if not args.decision_note.strip(): | ||
| raise SystemExit("--decision-note must explain why mirroring is appropriate") | ||
| run_dir = Path(args.run_dir).expanduser().resolve() | ||
| manifest_path = run_dir / "imagegen-jobs.json" | ||
| manifest = load_manifest(run_dir) | ||
| right_job = find_job(manifest, "running-right") | ||
| left_job = find_job(manifest, "running-left") | ||
| if right_job.get("status") != "complete": | ||
| raise SystemExit("running-right must be complete before deriving running-left") | ||
| mirror_policy = left_job.get("mirror_policy") | ||
| if ( | ||
| not isinstance(mirror_policy, dict) | ||
| or mirror_policy.get("may_derive_from") != "running-right" | ||
| ): | ||
| raise SystemExit("running-left is not configured for conditional mirroring") | ||
| source = run_dir / "decoded" / "running-right.png" | ||
| output = run_dir / "decoded" / "running-left.png" | ||
| if not source.is_file(): | ||
| raise SystemExit(f"running-right decoded strip not found: {source}") | ||
| if output.exists() and not args.force: | ||
| raise SystemExit(f"{output} already exists; pass --force to replace it") | ||
| output.parent.mkdir(parents=True, exist_ok=True) | ||
| with Image.open(source) as image: | ||
| mirrored = mirror_strip_preserving_frame_order(image) | ||
| mirrored.save(output) | ||
| left_job["status"] = "complete" | ||
| left_job["source_path"] = manifest_relative(source, run_dir) | ||
| left_job["derived_from"] = "running-right" | ||
| left_job["completed_at"] = datetime.now(timezone.utc).isoformat() | ||
| left_job["metadata"] = image_metadata(output) | ||
| left_job["mirror_decision"] = { | ||
| "approved": True, | ||
| "approved_at": left_job["completed_at"], | ||
| "note": args.decision_note.strip(), | ||
| "transform": "framewise-horizontal-mirror-preserving-order", | ||
| } | ||
| for key in [ | ||
| "last_error", | ||
| "repair_reason", | ||
| "queued_at", | ||
| ]: | ||
| left_job.pop(key, None) | ||
| manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") | ||
| print( | ||
| json.dumps( | ||
| { | ||
| "ok": True, | ||
| "job_id": "running-left", | ||
| "derived_from": "running-right", | ||
| "output": str(output), | ||
| "decision_note": args.decision_note.strip(), | ||
| "transform": "framewise-horizontal-mirror-preserving-order", | ||
| }, | ||
| indent=2, | ||
| ) | ||
| ) | ||
| if __name__ == "__main__": | ||
| main() |
@@ -8,4 +8,4 @@ # V2 Animation Rows | ||
| | 0 | idle | 0-5 | 280, 110, 110, 140, 140, 320 ms | | ||
| | 1 | move_left | 0-7 | 120 ms each, final 220 ms | | ||
| | 2 | move_right | 0-7 | 120 ms each, final 220 ms | | ||
| | 1 | running-right | 0-7 | 120 ms each, final 220 ms | | ||
| | 2 | running-left | 0-7 | 120 ms each, final 220 ms | | ||
| | 3 | waving | 0-3 | 140 ms each, final 280 ms | | ||
@@ -27,4 +27,4 @@ | 4 | jumping | 0-4 | 140 ms each, final 280 ms | | ||
| - `idle`: calm, low-distraction breathing/blinking loop and reduced-motion first frame. | ||
| - `move_left`: locomotion to the left with a readable alternating cadence. Mirror from `move_right` only when identity and prop handedness remain correct, preserving frame order. | ||
| - `move_right`: locomotion to the right with a readable alternating cadence. | ||
| - `running-right`: locomotion to the right with a readable alternating cadence. | ||
| - `running-left`: locomotion to the left with a readable alternating cadence. Mirror from `running-right` only when identity and prop handedness remain correct, preserving frame order. | ||
| - `waving`: greeting or attention gesture with a clear start, raised gesture, and return. | ||
@@ -31,0 +31,0 @@ - `jumping`: anticipation, lift, peak, descent, and settle. |
@@ -15,2 +15,4 @@ # Codex V2 Pet Contract | ||
| Rows `0-8` follow `references/animation-rows.md`; row `1` is `running-right` and row `2` is `running-left`. | ||
| The 8x9 `1536x1872` atlas is an intermediate assembly artifact only. Never package it as a newly hatched pet. | ||
@@ -17,0 +19,0 @@ |
@@ -25,2 +25,3 @@ # V2 Pet QA Rubric | ||
| - Rows `0-8` contain the exact required frame counts and recognizable state semantics. | ||
| - Row `1` is `running-right` and row `2` is `running-left` in viewer/screen coordinates. | ||
| - Loops do not pop, reverse cadence, face the wrong direction, or remain effectively static. | ||
@@ -27,0 +28,0 @@ - The first idle frame works as a reduced-motion still. |
@@ -20,4 +20,4 @@ #!/usr/bin/env python3 | ||
| ("idle", 0, 6), | ||
| ("move_left", 1, 8), | ||
| ("move_right", 2, 8), | ||
| ("running-right", 1, 8), | ||
| ("running-left", 2, 8), | ||
| ("waving", 3, 4), | ||
@@ -24,0 +24,0 @@ ("jumping", 4, 5), |
@@ -18,4 +18,4 @@ #!/usr/bin/env python3 | ||
| "idle": 6, | ||
| "move_left": 8, | ||
| "move_right": 8, | ||
| "running-right": 8, | ||
| "running-left": 8, | ||
| "waving": 4, | ||
@@ -22,0 +22,0 @@ "jumping": 5, |
@@ -18,4 +18,4 @@ #!/usr/bin/env python3 | ||
| "idle": 6, | ||
| "move_left": 8, | ||
| "move_right": 8, | ||
| "running-right": 8, | ||
| "running-left": 8, | ||
| "waving": 4, | ||
@@ -22,0 +22,0 @@ "jumping": 5, |
@@ -18,4 +18,4 @@ #!/usr/bin/env python3 | ||
| "idle", | ||
| "move_left", | ||
| "move_right", | ||
| "running-right", | ||
| "running-left", | ||
| "waving", | ||
@@ -22,0 +22,0 @@ "jumping", |
@@ -22,13 +22,2 @@ #!/usr/bin/env python3 | ||
| SPRITESHEET_FILENAME = "spritesheet.webp" | ||
| EXPECTED_FPS_NAMES = { | ||
| "idle", | ||
| "move_left", | ||
| "move_right", | ||
| "waving", | ||
| "jumping", | ||
| "failed", | ||
| "waiting", | ||
| "running", | ||
| "review", | ||
| } | ||
| EXPECTED_DIRECTIONS = [ | ||
@@ -106,11 +95,2 @@ "000", | ||
| fps = request.get("fps") | ||
| if not isinstance(fps, dict): | ||
| fail("pet_request.fps must be an object") | ||
| if set(fps) != EXPECTED_FPS_NAMES: | ||
| fail("pet_request.fps must contain exactly the nine DimAgent animation names") | ||
| for animation_name, value in fps.items(): | ||
| if not isinstance(value, int) or isinstance(value, bool) or not 1 <= value <= 30: | ||
| fail(f"pet_request.fps.{animation_name} must be an integer between 1 and 30") | ||
| return { | ||
@@ -117,0 +97,0 @@ "id": pet_id, |
@@ -17,4 +17,4 @@ #!/usr/bin/env python3 | ||
| "idle", | ||
| "move_left", | ||
| "move_right", | ||
| "running-right", | ||
| "running-left", | ||
| "waving", | ||
@@ -35,4 +35,4 @@ "jumping", | ||
| "idle": 6, | ||
| "move_left": 8, | ||
| "move_right": 8, | ||
| "running-right": 8, | ||
| "running-left": 8, | ||
| "waving": 4, | ||
@@ -39,0 +39,0 @@ "jumping": 5, |
@@ -24,4 +24,4 @@ #!/usr/bin/env python3 | ||
| ("idle", 0, 6, "calm resting, breathing, and blinking loop"), | ||
| ("move_left", 1, 8, "leftward drag movement loop"), | ||
| ("move_right", 2, 8, "rightward drag movement loop"), | ||
| ("running-right", 1, 8, "rightward drag movement loop"), | ||
| ("running-left", 2, 8, "leftward drag movement loop"), | ||
| ("waving", 3, 4, "greeting or attention gesture"), | ||
@@ -59,4 +59,4 @@ ("jumping", 4, 5, "hover or playful jump"), | ||
| "idle": "Calm low-distraction resting loop: subtle breathing, tiny blink, slight head/body bob, and only quiet persona-preserving motion.", | ||
| "move_right": "Dragging-right loop: show directional movement to the right through body and limb poses only.", | ||
| "move_left": "Dragging-left loop: show directional movement to the left through body and limb poses only.", | ||
| "running-right": "Dragging-right loop: show directional movement to the right through body and limb poses only.", | ||
| "running-left": "Dragging-left loop: show directional movement to the left through body and limb poses only.", | ||
| "waving": "Greeting loop: paw or limb down, raised, tilted, and returning in a friendly attention gesture.", | ||
@@ -106,3 +106,3 @@ "jumping": "Hover jump loop: anticipation, lift, airborne peak, descent, and settle through body height.", | ||
| ], | ||
| "move_right": [ | ||
| "running-right": [ | ||
| "Show directional drag movement to the right through body, limb, and prop movement only.", | ||
@@ -113,3 +113,3 @@ "The row must unmistakably face and travel right.", | ||
| ], | ||
| "move_left": [ | ||
| "running-left": [ | ||
| "Show directional drag movement to the left through body, limb, and prop movement only.", | ||
@@ -131,15 +131,2 @@ "The row must unmistakably face and travel left.", | ||
| # Default per-animation FPS matching the DimAgent bundled pet. | ||
| DEFAULT_FPS = { | ||
| "idle": 1, | ||
| "move_left": 8, | ||
| "move_right": 8, | ||
| "waving": 6, | ||
| "jumping": 8, | ||
| "failed": 6, | ||
| "waiting": 6, | ||
| "running": 8, | ||
| "review": 6, | ||
| } | ||
| PET_SAFE_STYLE = ( | ||
@@ -810,7 +797,7 @@ "Pet-safe sprite: compact full-body mascot, readable in a 192x208 cell, " | ||
| } | ||
| if state == "move_left": | ||
| depends_on.append("move_right") | ||
| if state == "running-left": | ||
| depends_on.append("running-right") | ||
| extra_inputs.append( | ||
| { | ||
| "path": "decoded/move_right.png", | ||
| "path": "decoded/running-right.png", | ||
| "role": "rightward gait reference for leftward row decision", | ||
@@ -821,3 +808,3 @@ } | ||
| "may_derive": True, | ||
| "may_derive_from": "move_right", | ||
| "may_derive_from": "running-right", | ||
| "derivation": "framewise-horizontal-mirror-preserving-order", | ||
@@ -856,3 +843,3 @@ "requires_explicit_approval": True, | ||
| "derivation_policy": derivation_policy, | ||
| "mirror_policy": derivation_policy if state == "move_left" else {}, | ||
| "mirror_policy": derivation_policy if state == "running-left" else {}, | ||
| } | ||
@@ -1122,3 +1109,2 @@ ) | ||
| "format": "dim-sprite-v2", | ||
| "fps": DEFAULT_FPS, | ||
| "atlas": ATLAS, | ||
@@ -1125,0 +1111,0 @@ "rows": [ |
@@ -14,4 +14,4 @@ #!/usr/bin/env python3 | ||
| "idle": [280, 110, 110, 140, 140, 320], | ||
| "move_left": [120, 120, 120, 120, 120, 120, 120, 220], | ||
| "move_right": [120, 120, 120, 120, 120, 120, 120, 220], | ||
| "running-right": [120, 120, 120, 120, 120, 120, 120, 220], | ||
| "running-left": [120, 120, 120, 120, 120, 120, 120, 220], | ||
| "waving": [140, 140, 140, 280], | ||
@@ -18,0 +18,0 @@ "jumping": [140, 140, 140, 140, 280], |
@@ -25,4 +25,4 @@ #!/usr/bin/env python3 | ||
| 0: ("idle", 6), | ||
| 1: ("move_left", 8), | ||
| 2: ("move_right", 8), | ||
| 1: ("running-right", 8), | ||
| 2: ("running-left", 8), | ||
| 3: ("waving", 4), | ||
@@ -29,0 +29,0 @@ 4: ("jumping", 5), |
@@ -19,4 +19,4 @@ import hashlib | ||
| "idle", | ||
| "move_left", | ||
| "move_right", | ||
| "running-right", | ||
| "running-left", | ||
| "waving", | ||
@@ -54,15 +54,3 @@ "jumping", | ||
| ] | ||
| FPS = { | ||
| "idle": 8, | ||
| "move_left": 12, | ||
| "move_right": 12, | ||
| "waving": 10, | ||
| "jumping": 12, | ||
| "failed": 8, | ||
| "waiting": 8, | ||
| "running": 12, | ||
| "review": 8, | ||
| } | ||
| def write_json(path: Path, value: object) -> None: | ||
@@ -160,3 +148,2 @@ path.parent.mkdir(parents=True, exist_ok=True) | ||
| "format": "dim-sprite-v2", | ||
| "fps": FPS, | ||
| "chroma_key": {"hex": "#FF00FF", "name": "magenta"}, | ||
@@ -163,0 +150,0 @@ "references": [], |
@@ -15,4 +15,4 @@ import json | ||
| "idle", | ||
| "move_left", | ||
| "move_right", | ||
| "running-right", | ||
| "running-left", | ||
| "waving", | ||
@@ -60,3 +60,3 @@ "jumping", | ||
| ) | ||
| self.assertEqual(set(request["fps"]), set(STANDARD_STATES)) | ||
| self.assertNotIn("fps", request) | ||
| self.assertEqual(len(request["references"]), 1) | ||
@@ -78,7 +78,7 @@ | ||
| move_left = next(job for job in jobs if job["id"] == "move_left") | ||
| self.assertIn("move_right", move_left["depends_on"]) | ||
| running_left = next(job for job in jobs if job["id"] == "running-left") | ||
| self.assertIn("running-right", running_left["depends_on"]) | ||
| self.assertEqual( | ||
| move_left["mirror_policy"]["may_derive_from"], | ||
| "move_right", | ||
| running_left["mirror_policy"]["may_derive_from"], | ||
| "running-right", | ||
| ) | ||
@@ -85,0 +85,0 @@ |
+1
-1
| { | ||
| "name": "dimagent-linux-arm64", | ||
| "version": "0.3.11", | ||
| "version": "0.3.12", | ||
| "description": "dimagent binary for Linux ARM64", | ||
@@ -5,0 +5,0 @@ "os": [ |
| #!/usr/bin/env python3 | ||
| """Conditionally derive move_left by mirroring the approved move_right strip.""" | ||
| from __future__ import annotations | ||
| import argparse | ||
| import json | ||
| from datetime import datetime, timezone | ||
| from pathlib import Path | ||
| from PIL import Image, ImageOps | ||
| RUNNING_FRAME_COUNT = 8 | ||
| def load_manifest(run_dir: Path) -> dict[str, object]: | ||
| path = run_dir / "imagegen-jobs.json" | ||
| if not path.exists(): | ||
| raise SystemExit(f"job manifest not found: {path}") | ||
| return json.loads(path.read_text(encoding="utf-8")) | ||
| def job_list(manifest: dict[str, object]) -> list[dict[str, object]]: | ||
| jobs = manifest.get("jobs") | ||
| if not isinstance(jobs, list): | ||
| raise SystemExit("invalid imagegen-jobs.json: jobs must be a list") | ||
| return [job for job in jobs if isinstance(job, dict)] | ||
| def find_job(manifest: dict[str, object], job_id: str) -> dict[str, object]: | ||
| for job in job_list(manifest): | ||
| if job.get("id") == job_id: | ||
| return job | ||
| raise SystemExit(f"unknown job id: {job_id}") | ||
| def image_metadata(path: Path) -> dict[str, object]: | ||
| with Image.open(path) as image: | ||
| image.verify() | ||
| with Image.open(path) as image: | ||
| return { | ||
| "width": image.width, | ||
| "height": image.height, | ||
| "mode": image.mode, | ||
| "format": image.format, | ||
| } | ||
| def manifest_relative(path: Path, run_dir: Path) -> str: | ||
| return str(path.resolve().relative_to(run_dir.resolve())) | ||
| def mirror_strip_preserving_frame_order( | ||
| source: Image.Image, | ||
| frame_count: int = RUNNING_FRAME_COUNT, | ||
| ) -> Image.Image: | ||
| rgba = source.convert("RGBA") | ||
| mirrored = Image.new("RGBA", rgba.size, (0, 0, 0, 0)) | ||
| slot_width = rgba.width / frame_count | ||
| for index in range(frame_count): | ||
| left = round(index * slot_width) | ||
| right = round((index + 1) * slot_width) | ||
| mirrored.alpha_composite( | ||
| ImageOps.mirror(rgba.crop((left, 0, right, rgba.height))), | ||
| (left, 0), | ||
| ) | ||
| return mirrored | ||
| def main() -> None: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument("--run-dir", required=True) | ||
| parser.add_argument( | ||
| "--confirm-appropriate-mirror", | ||
| action="store_true", | ||
| help="Required after visually confirming the rightward strip can be mirrored without identity/prop issues.", | ||
| ) | ||
| parser.add_argument( | ||
| "--decision-note", | ||
| required=True, | ||
| help="Short note explaining why mirroring is acceptable for this pet.", | ||
| ) | ||
| parser.add_argument("--force", action="store_true") | ||
| args = parser.parse_args() | ||
| if not args.confirm_appropriate_mirror: | ||
| raise SystemExit("refusing to mirror without --confirm-appropriate-mirror") | ||
| if not args.decision_note.strip(): | ||
| raise SystemExit("--decision-note must explain why mirroring is appropriate") | ||
| run_dir = Path(args.run_dir).expanduser().resolve() | ||
| manifest_path = run_dir / "imagegen-jobs.json" | ||
| manifest = load_manifest(run_dir) | ||
| right_job = find_job(manifest, "move_right") | ||
| left_job = find_job(manifest, "move_left") | ||
| if right_job.get("status") != "complete": | ||
| raise SystemExit("move_right must be complete before deriving move_left") | ||
| mirror_policy = left_job.get("mirror_policy") | ||
| if ( | ||
| not isinstance(mirror_policy, dict) | ||
| or mirror_policy.get("may_derive_from") != "move_right" | ||
| ): | ||
| raise SystemExit("move_left is not configured for conditional mirroring") | ||
| source = run_dir / "decoded" / "move_right.png" | ||
| output = run_dir / "decoded" / "move_left.png" | ||
| if not source.is_file(): | ||
| raise SystemExit(f"move_right decoded strip not found: {source}") | ||
| if output.exists() and not args.force: | ||
| raise SystemExit(f"{output} already exists; pass --force to replace it") | ||
| output.parent.mkdir(parents=True, exist_ok=True) | ||
| with Image.open(source) as image: | ||
| mirrored = mirror_strip_preserving_frame_order(image) | ||
| mirrored.save(output) | ||
| left_job["status"] = "complete" | ||
| left_job["source_path"] = manifest_relative(source, run_dir) | ||
| left_job["derived_from"] = "move_right" | ||
| left_job["completed_at"] = datetime.now(timezone.utc).isoformat() | ||
| left_job["metadata"] = image_metadata(output) | ||
| left_job["mirror_decision"] = { | ||
| "approved": True, | ||
| "approved_at": left_job["completed_at"], | ||
| "note": args.decision_note.strip(), | ||
| "transform": "framewise-horizontal-mirror-preserving-order", | ||
| } | ||
| for key in [ | ||
| "last_error", | ||
| "repair_reason", | ||
| "queued_at", | ||
| ]: | ||
| left_job.pop(key, None) | ||
| manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") | ||
| print( | ||
| json.dumps( | ||
| { | ||
| "ok": True, | ||
| "job_id": "move_left", | ||
| "derived_from": "move_right", | ||
| "output": str(output), | ||
| "decision_note": args.decision_note.strip(), | ||
| "transform": "framewise-horizontal-mirror-preserving-order", | ||
| }, | ||
| indent=2, | ||
| ) | ||
| ) | ||
| if __name__ == "__main__": | ||
| main() |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
179994905
5.98%40138
-0.11%