2026-04-14 22:17:32 +00:00
#!/usr/bin/env python3
"""
matrix_glitch_detect . py — 3 D 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
2026-04-12 23:25:53 +00:00
import json
2026-04-14 22:17:32 +00:00
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 3 D objects , holes in surfaces .
7. RENDER FAILURE : Black areas where geometry should be , missing skybox , incomplete frame rendering .
8. UI OVERLAP : UI elements overlapping 3 D 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
2026-04-12 23:25:53 +00:00
2026-04-14 22:17:32 +00:00
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 """ <!DOCTYPE html>
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< meta name = " viewport " content = " width=device-width, initial-scale=1.0 " >
< title > { html_module . escape ( title ) } < / title >
< style >
* { { margin : 0 ; padding : 0 ; box - sizing : border - box } }
body { { font - family : - apple - system , BlinkMacSystemFont , ' Segoe UI ' , Roboto , monospace ; background : #0a0a14;color:#c0c0d0;font-size:13px;line-height:1.5}}
. container { { max - width : 1000 px ; margin : 0 auto ; padding : 20 px } }
header { { text - align : center ; padding : 24 px 0 ; border - bottom : 1 px solid #1a1a2e;margin-bottom:24px}}
header h1 { { font - size : 20 px ; font - weight : 300 ; letter - spacing : 3 px ; color : #4a9eff;margin-bottom:8px}}
. verdict { { display : inline - block ; padding : 6 px 20 px ; border - radius : 4 px ; font - size : 14 px ; font - weight : 700 ; letter - spacing : 2 px ; color : #fff;background:{verdict_color}}}
. stats { { display : flex ; gap : 16 px ; justify - content : center ; margin : 16 px 0 ; flex - wrap : wrap } }
. stat { { background : #0e0e1a;border:1px solid #1a1a2e;border-radius:4px;padding:8px 16px;text-align:center}}
. stat . val { { font - size : 20 px ; font - weight : 700 ; color : #4a9eff}}
. stat . lbl { { font - size : 9 px ; color : #666;text-transform:uppercase;letter-spacing:1px}}
. score - gauge { { width : 120 px ; height : 120 px ; margin : 0 auto 16 px ; position : relative } }
. score - gauge svg { { transform : rotate ( - 90 deg ) } }
. score - gauge . score - text { { position : absolute ; top : 50 % ; left : 50 % ; transform : translate ( - 50 % , - 50 % ) ; font - size : 28 px ; font - weight : 700 } }
. report - card { { background : #0e0e1a;border:1px solid #1a1a2e;border-radius:6px;margin-bottom:16px;overflow:hidden}}
. report - header { { padding : 12 px 16 px ; border - bottom : 1 px solid #1a1a2e;display:flex;justify-content:space-between;align-items:center}}
. report - header . source { { color : #4a9eff;font-weight:600;word-break:break-all}}
. report - header . status - badge { { padding : 2 px 10 px ; border - radius : 3 px ; font - size : 11 px ; font - weight : 700 ; color : #fff}}
. status - pass { { background : #4caf50}}
. status - warn { { background : #ff9800}}
. status - fail { { background : #f44336}}
. screenshot { { text - align : center ; padding : 12 px ; background : #080810}}
. screenshot img { { max - width : 100 % ; max - height : 400 px ; border : 1 px solid #1a1a2e;border-radius:4px}}
. glitch - list { { padding : 12 px 16 px } }
. glitch - item { { padding : 8 px 0 ; border - bottom : 1 px solid #111;display:flex;gap:12px;align-items:flex-start}}
. glitch - item : last - child { { border - bottom : none } }
. severity - dot { { width : 8 px ; height : 8 px ; border - radius : 50 % ; margin - top : 5 px ; flex - shrink : 0 } }
. sev - critical { { background : #f44336}}
. sev - major { { background : #ff9800}}
. sev - minor { { background : #2196f3}}
. sev - cosmetic { { background : #666}}
. glitch - detail { { flex : 1 } }
. glitch - type { { color : #ffd700;font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:1px}}
. glitch - desc { { color : #aaa;font-size:12px;margin-top:2px}}
. glitch - meta { { color : #555;font-size:10px;margin-top:2px}}
. no - glitches { { color : #4caf50;text-align:center;padding:20px;font-style:italic}}
footer { { text - align : center ; padding : 16 px ; color : #444;font-size:10px;border-top:1px solid #1a1a2e;margin-top:24px}}
< / style >
< / head >
< body >
< div class = " container " >
< header >
< h1 > { html_module . escape ( title ) } < / h1 >
< div class = " verdict " > { overall_verdict } < / div >
< div class = " stats " >
< div class = " stat " > < div class = " val " > { len ( reports ) } < / div > < div class = " lbl " > Screenshots < / div > < / div >
< div class = " stat " > < div class = " val " > { total_glitches } < / div > < div class = " lbl " > Glitches < / div > < / div >
< div class = " stat " > < div class = " val " > { total_criticals } < / div > < div class = " lbl " > Critical < / div > < / div >
< div class = " stat " > < div class = " val " > { avg_score } < / div > < div class = " lbl " > Avg Score < / div > < / div >
< / div >
< / header >
""" )
# 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 """
< div class = " score-gauge " >
< svg width = " 120 " height = " 120 " viewBox = " 0 0 120 120 " >
< circle cx = " 60 " cy = " 60 " r = " 50 " fill = " none " stroke = " #1a1a2e " stroke - width = " 8 " / >
< circle cx = " 60 " cy = " 60 " r = " 50 " fill = " none " stroke = " {score_color} " stroke - width = " 8 "
stroke - dasharray = " {circumference} " stroke - dashoffset = " {dash_offset} " stroke - linecap = " round " / >
< / svg >
< div class = " score-text " style = " color: {score_color} " > { avg_score } < / div >
< / div >
""" )
# 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 ' <img src= " data: { mime } ;base64, { b64 } " alt= " Screenshot " > '
except Exception :
img_tag = ' <div style= " color:#666;padding:40px " >Screenshot unavailable</div> '
else :
img_tag = ' <div style= " color:#666;padding:40px " >No screenshot</div> '
parts . append ( f """
< div class = " report-card " >
< div class = " report-header " >
< span class = " source " > { html_module . escape ( source_name ) } ( { report . width } x { report . height } ) < / span >
< span class = " status-badge {status_class} " > { report . status } — { report . score } / 100 < / span >
< / div >
< div class = " screenshot " > { img_tag } < / div >
""" )
if report . glitches :
parts . append ( ' <div class= " glitch-list " > ' )
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 """
< div class = " glitch-item " >
< div class = " severity-dot {sev_class} " > < / div >
< div class = " glitch-detail " >
< div class = " glitch-type " > { html_module . escape ( g . type ) } — { sev . upper ( ) } < / div >
< div class = " glitch-desc " > { html_module . escape ( g . description ) } < / div >
< div class = " glitch-meta " > Region : { html_module . escape ( g . region ) } | Confidence : { g . confidence : .0 % } | Source : { html_module . escape ( g . source ) } < / div >
< / div >
< / div > """ )
parts . append ( ' </div> ' )
else :
parts . append ( ' <div class= " no-glitches " >No glitches detected</div> ' )
parts . append ( ' </div><!-- /report-card --> ' )
# Footer
parts . append ( f """
< footer >
Generated { datetime . now ( ) . strftime ( ' % Y- % m- %d % H: % M: % S ' ) } | matrix_glitch_detect . py | timmy - config #544
< / footer >
< / div >
< / body >
< / html > """ )
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 "
2026-04-12 23:25:53 +00:00
)
2026-04-14 22:17:32 +00:00
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 )
2026-04-12 23:25:53 +00:00
2026-04-14 22:17:32 +00:00
if __name__ == " __main__ " :
import subprocess
main ( )