#!/usr/bin/env python3 """ matrix_glitch_detect.py — 3D World Visual Artifact Detection for The Matrix. Scans screenshots or live pages for visual glitches: floating assets, z-fighting, texture pop-in, clipping, broken meshes, lighting artifacts. Outputs structured JSON, text, or standalone HTML report with annotated screenshots. Usage: # Scan a screenshot python scripts/matrix_glitch_detect.py --image screenshot.png # Scan with vision model python scripts/matrix_glitch_detect.py --image screenshot.png --vision # HTML report python scripts/matrix_glitch_detect.py --image screenshot.png --html report.html # Scan live Matrix page python scripts/matrix_glitch_detect.py --url https://matrix.alexanderwhitestone.com # Batch scan a directory python scripts/matrix_glitch_detect.py --batch ./screenshots/ --html batch-report.html Refs: timmy-config#491, #541, #543, #544 """ from __future__ import annotations import argparse import base64 import html as html_module import json import os import sys import time import urllib.error import urllib.request from dataclasses import dataclass, field, asdict from datetime import datetime from enum import Enum from pathlib import Path from typing import Optional # === Configuration === OLLAMA_BASE = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434") VISION_MODEL = os.environ.get("VISUAL_REVIEW_MODEL", "gemma3:12b") class Severity(str, Enum): CRITICAL = "critical" MAJOR = "major" MINOR = "minor" COSMETIC = "cosmetic" @dataclass class Glitch: """A single detected visual artifact.""" type: str = "" # floating_asset, z_fighting, texture_pop, clipping, lighting, mesh_break severity: Severity = Severity.MINOR region: str = "" # "upper-left", "center", "bottom-right", or coordinates description: str = "" confidence: float = 0.0 # 0.0-1.0 source: str = "" # "programmatic", "vision", "pixel_analysis" @dataclass class GlitchReport: """Complete glitch detection report.""" source: str = "" # file path or URL timestamp: str = "" status: str = "PASS" # PASS, WARN, FAIL score: int = 100 glitches: list[Glitch] = field(default_factory=list) summary: str = "" model_used: str = "" width: int = 0 height: int = 0 # === Programmatic Analysis === def analyze_pixels(image_path: str) -> list[Glitch]: """Programmatic pixel analysis for common 3D glitches.""" glitches = [] try: from PIL import Image img = Image.open(image_path).convert("RGB") w, h = img.size pixels = img.load() # Check for solid-color regions (render failure) corner_colors = [ pixels[0, 0], pixels[w-1, 0], pixels[0, h-1], pixels[w-1, h-1] ] if all(c == corner_colors[0] for c in corner_colors): # All corners same color — check if it's black (render failure) if corner_colors[0] == (0, 0, 0): glitches.append(Glitch( type="render_failure", severity=Severity.CRITICAL, region="entire frame", description="Entire frame is black — 3D scene failed to render", confidence=0.9, source="pixel_analysis" )) # Check for horizontal tearing lines tear_count = 0 for y in range(0, h, max(1, h // 20)): row_start = pixels[0, y] same_count = sum(1 for x in range(w) if pixels[x, y] == row_start) if same_count > w * 0.95: tear_count += 1 if tear_count > 3: glitches.append(Glitch( type="horizontal_tear", severity=Severity.MAJOR, region=f"{tear_count} lines", description=f"Horizontal tearing detected — {tear_count} mostly-solid scanlines", confidence=0.7, source="pixel_analysis" )) # Check for extreme brightness variance (lighting artifacts) import statistics brightness_samples = [] for y in range(0, h, max(1, h // 50)): for x in range(0, w, max(1, w // 50)): r, g, b = pixels[x, y] brightness_samples.append(0.299 * r + 0.587 * g + 0.114 * b) if brightness_samples: stdev = statistics.stdev(brightness_samples) if stdev > 100: glitches.append(Glitch( type="lighting", severity=Severity.MINOR, region="global", description=f"Extreme brightness variance (stdev={stdev:.0f}) — possible lighting artifacts", confidence=0.5, source="pixel_analysis" )) except ImportError: pass # PIL not available except Exception as e: pass return glitches # === Vision Analysis === GLITCH_VISION_PROMPT = """You are a 3D world QA engineer. Analyze this screenshot from a Three.js 3D world (The Matrix) for visual glitches and artifacts. Look for these specific issues: 1. FLOATING ASSETS: Objects hovering above surfaces where they should rest. Look for shadows detached from objects. 2. Z-FIGHTING: Flickering or shimmering surfaces where two polygons overlap at the same depth. Usually appears as striped or dithered patterns. 3. TEXTURE POP-IN: Low-resolution textures that haven't loaded, or textures that suddenly change quality between frames. 4. CLIPPING: Objects passing through walls, floors, or other objects. Characters partially inside geometry. 5. LIGHTING ARTIFACTS: Hard light seams, black patches, overexposed areas, lights not illuminating correctly. 6. MESH BREAKS: Visible seams in geometry, missing faces on 3D objects, holes in surfaces. 7. RENDER FAILURE: Black areas where geometry should be, missing skybox, incomplete frame rendering. 8. UI OVERLAP: UI elements overlapping 3D viewport incorrectly. Respond as JSON: { "glitches": [ { "type": "floating_asset|z_fighting|texture_pop|clipping|lighting|mesh_break|render_failure|ui_overlap", "severity": "critical|major|minor|cosmetic", "region": "description of where", "description": "detailed description of the artifact", "confidence": 0.0-1.0 } ], "overall_quality": 0-100, "summary": "brief assessment" }""" def run_vision_analysis(image_path: str, model: str = VISION_MODEL) -> tuple[list[Glitch], int]: """Run vision model glitch analysis.""" try: b64 = base64.b64encode(Path(image_path).read_bytes()).decode() payload = json.dumps({ "model": model, "messages": [{"role": "user", "content": [ {"type": "text", "text": GLITCH_VISION_PROMPT}, {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}} ]}], "stream": False, "options": {"temperature": 0.1} }).encode() req = urllib.request.Request( f"{OLLAMA_BASE}/api/chat", data=payload, headers={"Content-Type": "application/json"} ) with urllib.request.urlopen(req, timeout=120) as resp: result = json.loads(resp.read()) content = result.get("message", {}).get("content", "") parsed = _parse_json_response(content) glitches = [] for g in parsed.get("glitches", []): glitches.append(Glitch( type=g.get("type", "unknown"), severity=Severity(g.get("severity", "minor")), region=g.get("region", ""), description=g.get("description", ""), confidence=float(g.get("confidence", 0.5)), source="vision" )) return glitches, parsed.get("overall_quality", 80) except Exception as e: print(f" Vision analysis failed: {e}", file=sys.stderr) return [], 50 def _parse_json_response(text: str) -> dict: cleaned = text.strip() if cleaned.startswith("```"): lines = cleaned.split("\n")[1:] if lines and lines[-1].strip() == "```": lines = lines[:-1] cleaned = "\n".join(lines) try: return json.loads(cleaned) except json.JSONDecodeError: start = cleaned.find("{") end = cleaned.rfind("}") if start >= 0 and end > start: try: return json.loads(cleaned[start:end + 1]) except json.JSONDecodeError: pass return {} # === Screenshot Capture === def capture_screenshot(url: str, output_path: str) -> bool: """Take a screenshot of a URL.""" try: script = f""" from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page(viewport={{"width": 1280, "height": 720}}) page.goto("{url}", wait_until="networkidle", timeout=30000) page.wait_for_timeout(3000) page.screenshot(path="{output_path}") browser.close() """ result = subprocess.run(["python3", "-c", script], capture_output=True, text=True, timeout=60) return result.returncode == 0 and Path(output_path).exists() except Exception: return False # === Detection Logic === def detect_glitches(image_path: str, use_vision: bool = False, model: str = VISION_MODEL) -> GlitchReport: """Run full glitch detection on an image.""" report = GlitchReport( source=image_path, timestamp=datetime.now().isoformat(), model_used=model if use_vision else "none" ) if not Path(image_path).exists(): report.status = "FAIL" report.summary = f"File not found: {image_path}" report.score = 0 return report # Get image dimensions try: from PIL import Image img = Image.open(image_path) report.width, report.height = img.size except Exception: pass # Programmatic analysis prog_glitches = analyze_pixels(image_path) report.glitches.extend(prog_glitches) # Vision analysis if use_vision: print(f" Running vision analysis on {image_path}...", file=sys.stderr) vision_glitches, quality = run_vision_analysis(image_path, model) report.glitches.extend(vision_glitches) report.score = quality else: # Score based on programmatic results criticals = sum(1 for g in report.glitches if g.severity == Severity.CRITICAL) majors = sum(1 for g in report.glitches if g.severity == Severity.MAJOR) report.score = max(0, 100 - criticals * 40 - majors * 15) # Determine status criticals = sum(1 for g in report.glitches if g.severity == Severity.CRITICAL) majors = sum(1 for g in report.glitches if g.severity == Severity.MAJOR) if criticals > 0: report.status = "FAIL" elif majors > 0 or report.score < 70: report.status = "WARN" else: report.status = "PASS" report.summary = ( f"{report.status}: {len(report.glitches)} glitch(es) found " f"({criticals} critical, {majors} major), score {report.score}/100" ) return report # === HTML Report Generator === def generate_html_report(reports: list[GlitchReport], title: str = "Glitch Detection Report") -> str: """Generate a standalone HTML report with annotated details.""" total_glitches = sum(len(r.glitches) for r in reports) total_criticals = sum(sum(1 for g in r.glitches if g.severity == Severity.CRITICAL) for r in reports) avg_score = sum(r.score for r in reports) // max(1, len(reports)) if total_criticals > 0: overall_verdict = "FAIL" verdict_color = "#f44336" elif any(r.status == "WARN" for r in reports): overall_verdict = "WARN" verdict_color = "#ff9800" else: overall_verdict = "PASS" verdict_color = "#4caf50" # Build HTML parts = [] parts.append(f""" {html_module.escape(title)}

{html_module.escape(title)}

{overall_verdict}
{len(reports)}
Screenshots
{total_glitches}
Glitches
{total_criticals}
Critical
{avg_score}
Avg Score
""") # Score gauge score_color = "#4caf50" if avg_score >= 80 else "#ff9800" if avg_score >= 60 else "#f44336" circumference = 2 * 3.14159 * 50 dash_offset = circumference * (1 - avg_score / 100) parts.append(f"""
{avg_score}
""") # Per-screenshot reports for i, report in enumerate(reports): status_class = f"status-{report.status.lower()}" source_name = Path(report.source).name if report.source else f"Screenshot {i+1}" # Inline screenshot as base64 img_tag = "" if report.source and Path(report.source).exists(): try: b64 = base64.b64encode(Path(report.source).read_bytes()).decode() ext = Path(report.source).suffix.lower() mime = "image/png" if ext == ".png" else "image/jpeg" if ext in (".jpg", ".jpeg") else "image/webp" img_tag = f'Screenshot' except Exception: img_tag = '
Screenshot unavailable
' else: img_tag = '
No screenshot
' parts.append(f"""
{html_module.escape(source_name)} ({report.width}x{report.height}) {report.status} — {report.score}/100
{img_tag}
""") if report.glitches: parts.append('
') for g in sorted(report.glitches, key=lambda x: {"critical": 0, "major": 1, "minor": 2, "cosmetic": 3}.get(x.severity.value if hasattr(x.severity, "value") else str(x.severity), 4)): sev = g.severity.value if hasattr(g.severity, "value") else str(g.severity) sev_class = f"sev-{sev}" parts.append(f"""
{html_module.escape(g.type)} — {sev.upper()}
{html_module.escape(g.description)}
Region: {html_module.escape(g.region)} | Confidence: {g.confidence:.0%} | Source: {html_module.escape(g.source)}
""") parts.append('
') else: parts.append('
No glitches detected
') parts.append('
') # Footer parts.append(f"""
""") return "\n".join(parts) # === Output Formatting === def format_report(report: GlitchReport, fmt: str = "json") -> str: if fmt == "json": data = { "source": report.source, "timestamp": report.timestamp, "status": report.status, "score": report.score, "glitches": [asdict(g) for g in report.glitches], "summary": report.summary, "model_used": report.model_used, } for g in data["glitches"]: if hasattr(g["severity"], "value"): g["severity"] = g["severity"].value return json.dumps(data, indent=2) elif fmt == "text": lines = [ "=" * 50, " GLITCH DETECTION REPORT", "=" * 50, f" Source: {report.source}", f" Status: {report.status}", f" Score: {report.score}/100", f" Glitches: {len(report.glitches)}", "", ] icons = {"critical": "🔴", "major": "🟡", "minor": "🔵", "cosmetic": "⚪"} for g in report.glitches: sev = g.severity.value if hasattr(g.severity, "value") else str(g.severity) icon = icons.get(sev, "?") lines.append(f" {icon} [{g.type}] {sev.upper()}: {g.description}") lines.append(f" Region: {g.region} | Confidence: {g.confidence:.0%}") lines.append("") lines.append(f" {report.summary}") lines.append("=" * 50) return "\n".join(lines) return "" # === CLI === def main(): parser = argparse.ArgumentParser( description="3D World Glitch Detection — visual artifact scanner for The Matrix" ) group = parser.add_mutually_exclusive_group(required=True) group.add_argument("--image", help="Screenshot file to analyze") group.add_argument("--url", help="URL to screenshot and analyze") group.add_argument("--batch", help="Directory of screenshots to analyze") parser.add_argument("--vision", action="store_true", help="Include vision model analysis") parser.add_argument("--model", default=VISION_MODEL, help=f"Vision model (default: {VISION_MODEL})") parser.add_argument("--html", help="Generate HTML report at this path") parser.add_argument("--format", choices=["json", "text"], default="json", help="Output format") parser.add_argument("--output", "-o", help="Output file (default: stdout)") args = parser.parse_args() reports = [] if args.image: print(f"Analyzing {args.image}...", file=sys.stderr) report = detect_glitches(args.image, args.vision, args.model) reports.append(report) if not args.html: print(format_report(report, args.format)) elif args.url: import tempfile with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: screenshot_path = tmp.name print(f"Capturing screenshot of {args.url}...", file=sys.stderr) if capture_screenshot(args.url, screenshot_path): report = detect_glitches(screenshot_path, args.vision, args.model) report.source = args.url reports.append(report) if not args.html: print(format_report(report, args.format)) else: print(f"Failed to capture screenshot", file=sys.stderr) sys.exit(1) elif args.batch: batch_dir = Path(args.batch) images = sorted(batch_dir.glob("*.png")) + sorted(batch_dir.glob("*.jpg")) for img in images: print(f"Analyzing {img.name}...", file=sys.stderr) report = detect_glitches(str(img), args.vision, args.model) reports.append(report) # HTML report if args.html: html = generate_html_report(reports, title="The Matrix — Glitch Detection Report") Path(args.html).write_text(html) print(f"HTML report written to {args.html}", file=sys.stderr) elif args.batch and not args.html: # Print JSON array for batch print(json.dumps([json.loads(format_report(r, "json")) for r in reports], indent=2)) # Exit code if any(r.status == "FAIL" for r in reports): sys.exit(1) if __name__ == "__main__": import subprocess main()