Compare commits

..

31 Commits

Author SHA1 Message Date
Alexander Whitestone
cae23d8bd9 feat: 500 visual scene prompt enhancement pairs (#600)
Some checks are pending
Architecture Lint / Linter Tests (pull_request) Waiting to run
Architecture Lint / Lint Repository (pull_request) Blocked by required conditions
PR Checklist / pr-checklist (pull_request) Waiting to run
Smoke Test / smoke (pull_request) Waiting to run
Validate Config / YAML Lint (pull_request) Waiting to run
Validate Config / JSON Validate (pull_request) Waiting to run
Validate Config / Python Syntax & Import Check (pull_request) Waiting to run
Validate Config / Python Test Suite (pull_request) Blocked by required conditions
Validate Config / Shell Script Lint (pull_request) Waiting to run
Validate Config / Cron Syntax Check (pull_request) Waiting to run
Validate Config / Deploy Script Dry Run (pull_request) Waiting to run
Validate Config / Playbook Schema Validation (pull_request) Waiting to run
Terse→rich prompt expansion pairs for visual scene generation.

Domains: landscapes, portraits, abstract, fantasy
Format: {terse, rich, domain}
Count: 500 pairs, 435 unique terse inputs

Output: training/data/prompt-enhancement/visual-scenes-500.jsonl

Closes #600
2026-04-15 07:36:01 -04:00
35a191f7b1 Merge PR #725: feat: Provider health monitor with auto-switch (#509) 2026-04-15 06:10:45 +00:00
e987e1b870 Merge PR #726: feat: Pre-flight provider check for session launch (#508) 2026-04-15 06:10:41 +00:00
19278513b4 Merge PR #727: feat: Three.js-specific glitch detection patterns (#543) 2026-04-15 06:10:38 +00:00
1088bf8983 test: add Three.js pattern tests and update assertions (#543)
Some checks are pending
Architecture Lint / Linter Tests (pull_request) Waiting to run
Architecture Lint / Lint Repository (pull_request) Blocked by required conditions
PR Checklist / pr-checklist (pull_request) Waiting to run
Smoke Test / smoke (pull_request) Waiting to run
Validate Config / YAML Lint (pull_request) Waiting to run
Validate Config / JSON Validate (pull_request) Waiting to run
Validate Config / Python Syntax & Import Check (pull_request) Waiting to run
Validate Config / Python Test Suite (pull_request) Blocked by required conditions
Validate Config / Shell Script Lint (pull_request) Waiting to run
Validate Config / Cron Syntax Check (pull_request) Waiting to run
Validate Config / Deploy Script Dry Run (pull_request) Waiting to run
Validate Config / Playbook Schema Validation (pull_request) Waiting to run
- Added TestThreeJsPatterns class with 14 tests
- Tests cover: pattern existence, severity inference, vision prompt
- Updated pattern count assertion (14+ patterns now)
- Updated demo test (6 glitches: 4 original + 2 Three.js)
2026-04-15 05:37:17 +00:00
94f0a132d4 feat: add get_threejs_patterns() filter function (#543) 2026-04-15 05:34:17 +00:00
279356bed6 feat: add --threejs flag and Three.js-aware severity inference (#543)
- Added --threejs flag for focused Three.js pattern scanning
- Updated _infer_severity with shader_failure, texture_placeholder,
  uv_mapping_error, frustum_culling, shadow_map_artifact categories
- Added Three.js demo detections (shader failure, shadow map)
- Bumped detector version to 0.2.0
2026-04-15 05:34:16 +00:00
511ff863c2 feat: add Three.js-specific glitch detection patterns (#543)
Adds 6 new Three.js-specific glitch categories and patterns:
- SHADER_FAILURE: Solid black materials from shader compilation errors
- TEXTURE_PLACEHOLDER: 1x1 white pixel stretched surfaces
- UV_MAPPING_ERROR: BufferGeometry UV coordinate errors
- FRUSTUM_CULLING: Objects popping at screen edges
- SHADOW_MAP_ARTIFACT: Pixelated/blocky shadow maps
- BLOOM_OVERFLOW: Excessive post-processing bloom bleed

Closes #543
2026-04-15 05:32:25 +00:00
b6e3a647b0 feat: add pre-flight provider check script (#508)
Some checks are pending
Architecture Lint / Linter Tests (pull_request) Waiting to run
Architecture Lint / Lint Repository (pull_request) Blocked by required conditions
PR Checklist / pr-checklist (pull_request) Waiting to run
Smoke Test / smoke (pull_request) Waiting to run
Validate Config / Deploy Script Dry Run (pull_request) Waiting to run
Validate Config / Playbook Schema Validation (pull_request) Waiting to run
Validate Config / YAML Lint (pull_request) Waiting to run
Validate Config / JSON Validate (pull_request) Waiting to run
Validate Config / Python Syntax & Import Check (pull_request) Waiting to run
Validate Config / Python Test Suite (pull_request) Blocked by required conditions
Validate Config / Shell Script Lint (pull_request) Waiting to run
Validate Config / Cron Syntax Check (pull_request) Waiting to run
- Checks OpenRouter balance via /api/v1/auth/key
- Tests Nous and Anthropic API keys
- Verifies Ollama is running
- Pre-flight check before session launch
- Returns exit code for automation

Closes #508
2026-04-15 03:55:04 +00:00
e14158676d feat: add provider health monitor script (#509)
Some checks are pending
Smoke Test / smoke (pull_request) Waiting to run
Architecture Lint / Linter Tests (pull_request) Waiting to run
Architecture Lint / Lint Repository (pull_request) Blocked by required conditions
PR Checklist / pr-checklist (pull_request) Waiting to run
Validate Config / Cron Syntax Check (pull_request) Waiting to run
Validate Config / Deploy Script Dry Run (pull_request) Waiting to run
Validate Config / Playbook Schema Validation (pull_request) Waiting to run
Validate Config / YAML Lint (pull_request) Waiting to run
Validate Config / JSON Validate (pull_request) Waiting to run
Validate Config / Python Syntax & Import Check (pull_request) Waiting to run
Validate Config / Python Test Suite (pull_request) Blocked by required conditions
Validate Config / Shell Script Lint (pull_request) Waiting to run
- Tests all configured providers
- Maintains health map in tmux-state.json
- Auto-switches profiles to working providers
- Supports --daemon and --status modes

Closes #509
2026-04-15 03:48:37 +00:00
26e39d8949 feat: add autonomous cron supervisor job (#513)
- Runs every 7 minutes
- Checks dev and timmy sessions
- Loads tmux-supervisor skill
- Telegram only on actionable events
- Silent when all agents busy
2026-04-15 03:33:43 +00:00
d120526244 fix: add python3 shebang to scripts/visual_pr_reviewer.py (#681) 2026-04-15 02:57:53 +00:00
8596ff761b fix: add python3 shebang to scripts/diagram_meaning_extractor.py (#681) 2026-04-15 02:57:40 +00:00
7553fd4f3e fix: add python3 shebang to scripts/captcha_bypass_handler.py (#681) 2026-04-15 02:57:25 +00:00
71082fe06f fix: add python3 shebang to bin/soul_eval_gate.py (#681) 2026-04-15 02:57:14 +00:00
6d678e938e fix: add python3 shebang to bin/nostr-agent-demo.py (#681) 2026-04-15 02:57:00 +00:00
ad751a6de6 docs: add pipeline scheduler README 2026-04-14 22:47:12 +00:00
130fa40f0c feat: add pipeline-scheduler cron job 2026-04-14 22:46:51 +00:00
82f9810081 feat: add nightly-pipeline-scheduler.sh 2026-04-14 22:46:38 +00:00
2548277137 cleanup test
Some checks failed
Architecture Lint / Lint Repository (push) Blocked by required conditions
Validate Config / Python Test Suite (push) Blocked by required conditions
Architecture Lint / Lint Repository (pull_request) Blocked by required conditions
Validate Config / Python Test Suite (pull_request) Blocked by required conditions
Architecture Lint / Linter Tests (push) Successful in 22s
Smoke Test / smoke (push) Failing after 21s
Validate Config / YAML Lint (push) Failing after 13s
Validate Config / JSON Validate (push) Successful in 14s
Validate Config / Python Syntax & Import Check (push) Failing after 1m9s
Validate Config / Shell Script Lint (push) Failing after 31s
Validate Config / Cron Syntax Check (push) Successful in 5s
Validate Config / Deploy Script Dry Run (push) Successful in 7s
Validate Config / Playbook Schema Validation (push) Successful in 16s
Architecture Lint / Linter Tests (pull_request) Successful in 14s
Smoke Test / smoke (pull_request) Failing after 13s
Validate Config / YAML Lint (pull_request) Failing after 12s
Validate Config / JSON Validate (pull_request) Successful in 13s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 54s
Validate Config / Shell Script Lint (pull_request) Failing after 21s
Validate Config / Cron Syntax Check (pull_request) Successful in 5s
Validate Config / Deploy Script Dry Run (pull_request) Successful in 7s
Validate Config / Playbook Schema Validation (pull_request) Successful in 18s
PR Checklist / pr-checklist (pull_request) Failing after 3m27s
2026-04-14 22:39:03 +00:00
2b234fde79 test: verify API works
Some checks failed
Architecture Lint / Linter Tests (push) Has been cancelled
Architecture Lint / Lint Repository (push) Has been cancelled
Validate Config / Python Test Suite (push) Blocked by required conditions
Smoke Test / smoke (push) Failing after 12s
Validate Config / YAML Lint (push) Failing after 11s
Validate Config / JSON Validate (push) Successful in 11s
Validate Config / Python Syntax & Import Check (push) Failing after 47s
Validate Config / Shell Script Lint (push) Failing after 33s
Validate Config / Cron Syntax Check (push) Successful in 10s
Validate Config / Deploy Script Dry Run (push) Successful in 10s
Validate Config / Playbook Schema Validation (push) Successful in 14s
2026-04-14 22:39:02 +00:00
04cceccd01 feat: add rock scene generator (#607)
Some checks failed
Architecture Lint / Lint Repository (push) Has been cancelled
Architecture Lint / Linter Tests (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / YAML Lint (push) Has been cancelled
Validate Config / JSON Validate (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
2026-04-14 22:35:43 +00:00
1ad2f2b239 feat: 100 rock lyrics-to-scene sets (#607)
Some checks failed
Architecture Lint / Linter Tests (push) Has been cancelled
Architecture Lint / Lint Repository (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / YAML Lint (push) Has been cancelled
Validate Config / JSON Validate (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
2026-04-14 22:35:11 +00:00
04dbf772b1 feat: Visual Smoke Test for The Nexus #490 (#558)
Some checks failed
Architecture Lint / Linter Tests (push) Successful in 15s
Smoke Test / smoke (push) Failing after 14s
Validate Config / YAML Lint (push) Failing after 13s
Validate Config / JSON Validate (push) Successful in 13s
Validate Config / Shell Script Lint (push) Failing after 40s
Validate Config / Python Syntax & Import Check (push) Failing after 58s
Validate Config / Python Test Suite (push) Has been skipped
Validate Config / Cron Syntax Check (push) Successful in 11s
Validate Config / Deploy Script Dry Run (push) Successful in 11s
Validate Config / Playbook Schema Validation (push) Successful in 18s
Architecture Lint / Lint Repository (push) Failing after 13s
Architecture Lint / Linter Tests (pull_request) Successful in 26s
Smoke Test / smoke (pull_request) Failing after 17s
Validate Config / YAML Lint (pull_request) Failing after 12s
Validate Config / JSON Validate (pull_request) Successful in 12s
PR Checklist / pr-checklist (pull_request) Failing after 3m36s
Validate Config / Shell Script Lint (pull_request) Failing after 40s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 1m4s
Validate Config / Python Test Suite (pull_request) Has been skipped
Validate Config / Cron Syntax Check (pull_request) Successful in 9s
Validate Config / Deploy Script Dry Run (pull_request) Successful in 9s
Validate Config / Playbook Schema Validation (pull_request) Successful in 20s
Architecture Lint / Lint Repository (pull_request) Failing after 16s
Merge PR #558
2026-04-14 22:18:23 +00:00
697a273f0f fix: a11y R4 - <time> elements for relative timestamps (closes #554) (#569)
Some checks failed
Architecture Lint / Linter Tests (push) Successful in 10s
Smoke Test / smoke (push) Failing after 7s
Validate Config / YAML Lint (push) Failing after 7s
Validate Config / JSON Validate (push) Successful in 7s
Architecture Lint / Lint Repository (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Merge PR #569

Co-authored-by: Timmy Time <timmy@alexanderwhitestone.ai>
Co-committed-by: Timmy Time <timmy@alexanderwhitestone.ai>
2026-04-14 22:17:39 +00:00
9651a56308 feat: Glitch Detector HTML Report with annotated screenshots #544 (#567)
Some checks failed
Architecture Lint / Lint Repository (push) Has been cancelled
Architecture Lint / Linter Tests (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / JSON Validate (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Validate Config / YAML Lint (push) Has been cancelled
Merge PR #567
2026-04-14 22:17:32 +00:00
a84e6b517f [a11y] Visual Accessibility Audit — Foundation Web (#492) (#556)
Some checks failed
Architecture Lint / Lint Repository (push) Has been cancelled
Architecture Lint / Linter Tests (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / YAML Lint (push) Has been cancelled
Validate Config / JSON Validate (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Merge PR #556
2026-04-14 22:17:17 +00:00
31313c421e feat: 3D World Glitch Detection in The Matrix (#491) (#535)
Some checks failed
Architecture Lint / Linter Tests (push) Has been cancelled
Architecture Lint / Lint Repository (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / JSON Validate (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Validate Config / YAML Lint (push) Has been cancelled
Merge PR #535
2026-04-14 22:17:06 +00:00
063572ed1e feat: Visual Accessibility Audit of Foundation Web #492 (#531)
Some checks failed
Architecture Lint / Lint Repository (push) Has been cancelled
Architecture Lint / Linter Tests (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Validate Config / YAML Lint (push) Has been cancelled
Validate Config / JSON Validate (push) Has been cancelled
Merge PR #531
2026-04-14 22:16:58 +00:00
46b4f8d000 feat: pane-watchdog — stuck pane detection + auto-restart (#515) (#526)
Some checks failed
Architecture Lint / Linter Tests (push) Has been cancelled
Architecture Lint / Lint Repository (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Validate Config / YAML Lint (push) Has been cancelled
Validate Config / JSON Validate (push) Has been cancelled
Merge PR #526
2026-04-14 22:16:52 +00:00
e091868fef feat: auto-commit-guard — 4-layer work preservation (#511) (#525)
Some checks failed
Architecture Lint / Lint Repository (push) Has been cancelled
Architecture Lint / Linter Tests (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Validate Config / YAML Lint (push) Has started running
Validate Config / JSON Validate (push) Has started running
Merge PR #525
2026-04-14 22:16:49 +00:00
33 changed files with 6839 additions and 34 deletions

View File

@@ -202,6 +202,19 @@ curl -s -X POST "{gitea_url}/api/v1/repos/{repo}/issues/{issue_num}/comments" \\
REVIEW CHECKLIST BEFORE YOU PUSH:
{review}
COMMIT DISCIPLINE (CRITICAL):
- Commit every 3-5 tool calls. Do NOT wait until the end.
- After every meaningful file change: git add -A && git commit -m "WIP: <what changed>"
- Before running any destructive command: commit current state first.
- If you are unsure whether to commit: commit. WIP commits are safe. Lost work is not.
- Never use --no-verify.
- The auto-commit-guard is your safety net, but do not rely on it. Commit proactively.
RECOVERY COMMANDS (if interrupted, another agent can resume):
git log --oneline -10 # see your WIP commits
git diff HEAD~1 # see what the last commit changed
git status # see uncommitted work
RULES:
- Do not skip hooks with --no-verify.
- Do not silently widen the scope.

View File

@@ -161,6 +161,14 @@ run_worker() {
CYCLE_END=$(date +%s)
CYCLE_DURATION=$((CYCLE_END - CYCLE_START))
# --- Mid-session auto-commit: commit before timeout if work is dirty ---
cd "$worktree" 2>/dev/null || true
# Ensure auto-commit-guard is running
if ! pgrep -f "auto-commit-guard.sh" >/dev/null 2>&1; then
log "Starting auto-commit-guard daemon"
nohup bash "$(dirname "$0")/auto-commit-guard.sh" 120 "$WORKTREE_BASE" >> "$LOG_DIR/auto-commit-guard.log" 2>&1 &
fi
# Salvage
cd "$worktree" 2>/dev/null || true
DIRTY=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')

159
bin/auto-commit-guard.sh Normal file
View File

@@ -0,0 +1,159 @@
#!/usr/bin/env bash
# auto-commit-guard.sh — Background daemon that auto-commits uncommitted work
#
# Usage: auto-commit-guard.sh [interval_seconds] [worktree_base]
# auto-commit-guard.sh # defaults: 120s, ~/worktrees
# auto-commit-guard.sh 60 # check every 60s
# auto-commit-guard.sh 180 ~/my-worktrees
#
# Scans all git repos under the worktree base for uncommitted changes.
# If dirty for >= 1 check cycle, auto-commits with a WIP message.
# Pushes unpushed commits so work is always recoverable from the remote.
#
# Also scans /tmp for orphaned agent workdirs on startup.
set -uo pipefail
INTERVAL="${1:-120}"
WORKTREE_BASE="${2:-$HOME/worktrees}"
LOG_DIR="$HOME/.hermes/logs"
LOG="$LOG_DIR/auto-commit-guard.log"
PIDFILE="$LOG_DIR/auto-commit-guard.pid"
ORPHAN_SCAN_DONE="$LOG_DIR/.orphan-scan-done"
mkdir -p "$LOG_DIR"
# Single instance guard
if [ -f "$PIDFILE" ]; then
old_pid=$(cat "$PIDFILE")
if kill -0 "$old_pid" 2>/dev/null; then
echo "auto-commit-guard already running (PID $old_pid)" >&2
exit 0
fi
fi
echo $$ > "$PIDFILE"
trap 'rm -f "$PIDFILE"' EXIT
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] AUTO-COMMIT: $*" >> "$LOG"
}
# --- Orphaned workdir scan (runs once on startup) ---
scan_orphans() {
if [ -f "$ORPHAN_SCAN_DONE" ]; then
return 0
fi
log "Scanning /tmp for orphaned agent workdirs..."
local found=0
local rescued=0
for dir in /tmp/*-work-* /tmp/timmy-burn-* /tmp/tc-burn; do
[ -d "$dir" ] || continue
[ -d "$dir/.git" ] || continue
found=$((found + 1))
cd "$dir" 2>/dev/null || continue
local dirty
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d " ")
if [ "${dirty:-0}" -gt 0 ]; then
local branch
branch=$(git branch --show-current 2>/dev/null || echo "orphan")
git add -A 2>/dev/null
if git commit -m "WIP: orphan rescue — $dirty file(s) auto-committed on $(date -u +%Y-%m-%dT%H:%M:%SZ)
Orphaned workdir detected at $dir.
Branch: $branch
Rescued by auto-commit-guard on startup." 2>/dev/null; then
rescued=$((rescued + 1))
log "RESCUED: $dir ($dirty files on branch $branch)"
# Try to push if remote exists
if git remote get-url origin >/dev/null 2>&1; then
git push -u origin "$branch" 2>/dev/null && log "PUSHED orphan rescue: $dir$branch" || log "PUSH FAILED orphan rescue: $dir (no remote access)"
fi
fi
fi
done
log "Orphan scan complete: $found workdirs checked, $rescued rescued"
touch "$ORPHAN_SCAN_DONE"
}
# --- Main guard loop ---
guard_cycle() {
local committed=0
local scanned=0
# Scan worktree base
if [ -d "$WORKTREE_BASE" ]; then
for dir in "$WORKTREE_BASE"/*/; do
[ -d "$dir" ] || continue
[ -d "$dir/.git" ] || continue
scanned=$((scanned + 1))
cd "$dir" 2>/dev/null || continue
local dirty
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d " ")
[ "${dirty:-0}" -eq 0 ] && continue
local branch
branch=$(git branch --show-current 2>/dev/null || echo "detached")
git add -A 2>/dev/null
if git commit -m "WIP: auto-commit — $dirty file(s) on $branch
Automated commit by auto-commit-guard at $(date -u +%Y-%m-%dT%H:%M:%SZ).
Work preserved to prevent loss on crash." 2>/dev/null; then
committed=$((committed + 1))
log "COMMITTED: $dir ($dirty files, branch $branch)"
# Push to preserve remotely
if git remote get-url origin >/dev/null 2>&1; then
git push -u origin "$branch" 2>/dev/null && log "PUSHED: $dir$branch" || log "PUSH FAILED: $dir (will retry next cycle)"
fi
fi
done
fi
# Also scan /tmp for agent workdirs
for dir in /tmp/*-work-*; do
[ -d "$dir" ] || continue
[ -d "$dir/.git" ] || continue
scanned=$((scanned + 1))
cd "$dir" 2>/dev/null || continue
local dirty
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d " ")
[ "${dirty:-0}" -eq 0 ] && continue
local branch
branch=$(git branch --show-current 2>/dev/null || echo "detached")
git add -A 2>/dev/null
if git commit -m "WIP: auto-commit — $dirty file(s) on $branch
Automated commit by auto-commit-guard at $(date -u +%Y-%m-%dT%H:%M:%SZ).
Agent workdir preserved to prevent loss." 2>/dev/null; then
committed=$((committed + 1))
log "COMMITTED: $dir ($dirty files, branch $branch)"
if git remote get-url origin >/dev/null 2>&1; then
git push -u origin "$branch" 2>/dev/null && log "PUSHED: $dir$branch" || log "PUSH FAILED: $dir (will retry next cycle)"
fi
fi
done
[ "$committed" -gt 0 ] && log "Cycle done: $scanned scanned, $committed committed"
}
# --- Entry point ---
log "Starting auto-commit-guard (interval=${INTERVAL}s, worktree=${WORKTREE_BASE})"
scan_orphans
while true; do
guard_cycle
sleep "$INTERVAL"
done

439
bin/glitch_patterns.py Normal file
View File

@@ -0,0 +1,439 @@
"""
Glitch pattern definitions for 3D world anomaly detection.
Defines known visual artifact categories commonly found in 3D web worlds,
particularly The Matrix environments. Each pattern includes detection
heuristics and severity ratings.
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class GlitchSeverity(Enum):
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
INFO = "info"
class GlitchCategory(Enum):
FLOATING_ASSETS = "floating_assets"
Z_FIGHTING = "z_fighting"
MISSING_TEXTURES = "missing_textures"
CLIPPING = "clipping"
BROKEN_NORMALS = "broken_normals"
SHADOW_ARTIFACTS = "shadow_artifacts"
LIGHTMAP_ERRORS = "lightmap_errors"
LOD_POPPING = "lod_popping"
WATER_REFLECTION = "water_reflection"
SKYBOX_SEAM = "skybox_seam"
# Three.js-specific categories (ref: timmy-config#543)
SHADER_FAILURE = "shader_failure"
TEXTURE_PLACEHOLDER = "texture_placeholder"
UV_MAPPING_ERROR = "uv_mapping_error"
FRUSTUM_CULLING = "frustum_culling"
SHADOW_MAP_ARTIFACT = "shadow_map_artifact"
BLOOM_OVERFLOW = "bloom_overflow"
@dataclass
class GlitchPattern:
"""Definition of a known glitch pattern with detection parameters."""
category: GlitchCategory
name: str
description: str
severity: GlitchSeverity
detection_prompts: list[str]
visual_indicators: list[str]
confidence_threshold: float = 0.6
def to_dict(self) -> dict:
return {
"category": self.category.value,
"name": self.name,
"description": self.description,
"severity": self.severity.value,
"detection_prompts": self.detection_prompts,
"visual_indicators": self.visual_indicators,
"confidence_threshold": self.confidence_threshold,
}
# Known glitch patterns for Matrix 3D world scanning
MATRIX_GLITCH_PATTERNS: list[GlitchPattern] = [
GlitchPattern(
category=GlitchCategory.FLOATING_ASSETS,
name="Floating Object",
description="Object not properly grounded or anchored to the scene geometry. "
"Common in procedurally placed assets or after physics desync.",
severity=GlitchSeverity.HIGH,
detection_prompts=[
"Identify any objects that appear to float above the ground without support.",
"Look for furniture, props, or geometry suspended in mid-air with no visible attachment.",
"Check for objects whose shadows do not align with the surface below them.",
],
visual_indicators=[
"gap between object base and surface",
"shadow detached from object",
"object hovering with no structural support",
],
confidence_threshold=0.65,
),
GlitchPattern(
category=GlitchCategory.Z_FIGHTING,
name="Z-Fighting Flicker",
description="Two coplanar surfaces competing for depth priority, causing "
"visible flickering or shimmering textures.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for surfaces that appear to shimmer, flicker, or show mixed textures.",
"Identify areas where two textures seem to overlap and compete for visibility.",
"Check walls, floors, or objects for surface noise or pattern interference.",
],
visual_indicators=[
"shimmering surface",
"texture flicker between two patterns",
"noisy flat surfaces",
"moire-like patterns on planar geometry",
],
confidence_threshold=0.55,
),
GlitchPattern(
category=GlitchCategory.MISSING_TEXTURES,
name="Missing or Placeholder Texture",
description="A surface rendered with a fallback checkerboard, solid magenta, "
"or the default engine placeholder texture.",
severity=GlitchSeverity.CRITICAL,
detection_prompts=[
"Look for bright magenta, checkerboard, or solid-color surfaces that look out of place.",
"Identify any surfaces that appear as flat untextured colors inconsistent with the scene.",
"Check for black, white, or magenta patches where detailed textures should be.",
],
visual_indicators=[
"magenta/pink solid color surface",
"checkerboard pattern",
"flat single-color geometry",
"UV-debug texture visible",
],
confidence_threshold=0.7,
),
GlitchPattern(
category=GlitchCategory.CLIPPING,
name="Geometry Clipping",
description="Objects passing through each other or intersecting in physically "
"impossible ways due to collision mesh errors.",
severity=GlitchSeverity.HIGH,
detection_prompts=[
"Look for objects that visibly pass through other objects (walls, floors, furniture).",
"Identify characters or props embedded inside geometry where they should not be.",
"Check for intersecting meshes where solid objects overlap unnaturally.",
],
visual_indicators=[
"object passing through wall or floor",
"embedded geometry",
"overlapping solid meshes",
"character limb inside furniture",
],
confidence_threshold=0.6,
),
GlitchPattern(
category=GlitchCategory.BROKEN_NORMALS,
name="Broken Surface Normals",
description="Inverted or incorrect surface normals causing faces to appear "
"inside-out, invisible from certain angles, or lit incorrectly.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for surfaces that appear dark or black on one side while lit on the other.",
"Identify objects that seem to vanish when viewed from certain angles.",
"Check for inverted shading where lit areas should be in shadow.",
],
visual_indicators=[
"dark/unlit face on otherwise lit model",
"invisible surface from one direction",
"inverted shadow gradient",
"inside-out appearance",
],
confidence_threshold=0.5,
),
GlitchPattern(
category=GlitchCategory.SHADOW_ARTIFACTS,
name="Shadow Artifact",
description="Broken, detached, or incorrectly rendered shadows that do not "
"match the casting geometry or scene lighting.",
severity=GlitchSeverity.LOW,
detection_prompts=[
"Look for shadows that do not match the shape of nearby objects.",
"Identify shadow acne: banding or striped patterns on surfaces.",
"Check for floating shadows detached from any visible caster.",
],
visual_indicators=[
"shadow shape mismatch",
"shadow acne bands",
"detached floating shadow",
"Peter Panning (shadow offset from base)",
],
confidence_threshold=0.5,
),
GlitchPattern(
category=GlitchCategory.LOD_POPPING,
name="LOD Transition Pop",
description="Visible pop-in when level-of-detail models switch abruptly, "
"causing geometry or textures to change suddenly.",
severity=GlitchSeverity.LOW,
detection_prompts=[
"Look for areas where mesh detail changes abruptly at visible boundaries.",
"Identify objects that appear to morph or shift geometry suddenly.",
"Check for texture resolution changes that create visible seams.",
],
visual_indicators=[
"visible mesh simplification boundary",
"texture resolution jump",
"geometry pop-in artifacts",
],
confidence_threshold=0.45,
),
GlitchPattern(
category=GlitchCategory.LIGHTMAP_ERRORS,
name="Lightmap Baking Error",
description="Incorrect or missing baked lighting causing dark spots, light "
"leaks, or mismatched illumination on static geometry.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for unusually dark patches on walls or ceilings that should be lit.",
"Identify bright light leaks through solid geometry seams.",
"Check for mismatched lighting between adjacent surfaces.",
],
visual_indicators=[
"dark splotch on lit surface",
"bright line at geometry seam",
"lighting discontinuity between adjacent faces",
],
confidence_threshold=0.5,
),
GlitchPattern(
category=GlitchCategory.WATER_REFLECTION,
name="Water/Reflection Error",
description="Incorrect reflections, missing water surfaces, or broken "
"reflection probe assignments.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for reflections that do not match the surrounding environment.",
"Identify water surfaces that appear solid or incorrectly rendered.",
"Check for mirror surfaces showing wrong scene geometry.",
],
visual_indicators=[
"reflection mismatch",
"solid water surface",
"incorrect environment map",
],
confidence_threshold=0.5,
),
GlitchPattern(
category=GlitchCategory.SKYBOX_SEAM,
name="Skybox Seam",
description="Visible seams or color mismatches at the edges of skybox cubemap faces.",
severity=GlitchSeverity.LOW,
detection_prompts=[
"Look at the edges of the sky for visible seams or color shifts.",
"Identify discontinuities where skybox faces meet.",
"Check for texture stretching at skybox corners.",
],
visual_indicators=[
"visible line in sky",
"color discontinuity at sky edge",
"sky texture seam",
],
confidence_threshold=0.45,
),
# --- Three.js-Specific Glitch Patterns (ref: timmy-config#543) ---
GlitchPattern(
category=GlitchCategory.SHADER_FAILURE,
name="Shader Compilation Failure",
description="Three.js shader failed to compile, rendering the material as solid black. "
"Common when custom ShaderMaterial has syntax errors or missing uniforms.",
severity=GlitchSeverity.CRITICAL,
detection_prompts=[
"Look for objects or surfaces rendered as pure black (#000000) that should have visible textures or materials.",
"Identify geometry that appears completely dark while surrounding objects are normally lit.",
"Check for objects where the material seems to 'absorb all light' — flat black with no shading gradient.",
],
visual_indicators=[
"solid black object with no shading",
"geometry rendered as silhouette",
"material appears to absorb light entirely",
"black patch inconsistent with scene lighting",
],
confidence_threshold=0.7,
),
GlitchPattern(
category=GlitchCategory.TEXTURE_PLACEHOLDER,
name="Three.js Texture Not Loaded",
description="Three.js failed to load the texture asset, rendering a 1x1 white pixel "
"stretched across the entire surface. Distinguished from missing-texture by "
"the uniform white/grey appearance rather than magenta.",
severity=GlitchSeverity.CRITICAL,
detection_prompts=[
"Look for surfaces that are uniformly white or light grey with no texture detail, even on large geometry.",
"Identify objects where the texture appears as a single solid color stretched across complex UVs.",
"Check for surfaces that look 'blank' or 'unloaded' — flat white/grey where detail should exist.",
],
visual_indicators=[
"uniform white or light grey surface",
"no texture detail on large geometry",
"stretched single-color appearance",
"1x1 pixel placeholder stretched to fill UV space",
],
confidence_threshold=0.65,
),
GlitchPattern(
category=GlitchCategory.UV_MAPPING_ERROR,
name="BufferGeometry UV Mapping Error",
description="Three.js BufferGeometry has incorrect UV coordinates, causing textures to "
"appear stretched, compressed, or mapped to the wrong faces.",
severity=GlitchSeverity.HIGH,
detection_prompts=[
"Look for textures that appear dramatically stretched in one direction on specific faces.",
"Identify surfaces where the texture pattern is distorted but other nearby surfaces look correct.",
"Check for faces where the texture seems 'smeared' or mapped with incorrect aspect ratio.",
],
visual_indicators=[
"texture stretching on specific faces",
"distorted pattern on geometry",
"smeared texture appearance",
"aspect ratio mismatch between texture and surface",
],
confidence_threshold=0.6,
),
GlitchPattern(
category=GlitchCategory.FRUSTUM_CULLING,
name="Frustum Culling Artifact",
description="Three.js frustum culling incorrectly marks objects as outside the camera "
"frustum, causing them to pop in/out of existence at screen edges.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for objects that are partially visible at the edge of the frame — half-rendered or cut off unnaturally.",
"Identify geometry that seems to 'pop' into existence as the view angle changes.",
"Check screen edges for objects that appear suddenly rather than smoothly entering the viewport.",
],
visual_indicators=[
"half-visible object at screen edge",
"object popping into frame",
"abrupt appearance of geometry",
"bounding box visible but mesh missing",
],
confidence_threshold=0.55,
),
GlitchPattern(
category=GlitchCategory.SHADOW_MAP_ARTIFACT,
name="Shadow Map Resolution Artifact",
description="Three.js shadow map has insufficient resolution, causing pixelated, "
"blocky shadows with visible texel edges instead of smooth shadow gradients.",
severity=GlitchSeverity.MEDIUM,
detection_prompts=[
"Look for shadows with visible blocky or pixelated edges instead of smooth gradients.",
"Identify shadow maps where individual texels (texture pixels) are clearly visible.",
"Check for shadows that appear as jagged stair-stepped patterns rather than soft edges.",
],
visual_indicators=[
"blocky shadow edges",
"visible texel grid in shadows",
"stair-stepped shadow boundary",
"pixelated shadow gradient",
],
confidence_threshold=0.55,
),
GlitchPattern(
category=GlitchCategory.BLOOM_OVERFLOW,
name="Post-Processing Bloom Overflow",
description="Three.js UnrealBloomPass or similar post-processing bloom effect is too "
"intense, causing bright areas to bleed glow into surrounding geometry.",
severity=GlitchSeverity.LOW,
detection_prompts=[
"Look for bright areas that have an unusually large, soft glow bleeding into adjacent surfaces.",
"Identify scenes where light sources appear to have a 'halo' that extends beyond physical plausibility.",
"Check for bright objects whose glow color bleeds onto nearby unrelated geometry.",
],
visual_indicators=[
"excessive glow bleeding from bright surfaces",
"halo around light sources",
"bloom color tinting adjacent geometry",
"glow bleeding beyond object boundaries",
],
confidence_threshold=0.5,
),
]
def get_patterns_by_severity(min_severity: GlitchSeverity) -> list[GlitchPattern]:
"""Return patterns at or above the given severity level."""
severity_order = [
GlitchSeverity.INFO,
GlitchSeverity.LOW,
GlitchSeverity.MEDIUM,
GlitchSeverity.HIGH,
GlitchSeverity.CRITICAL,
]
min_idx = severity_order.index(min_severity)
return [p for p in MATRIX_GLITCH_PATTERNS if severity_order.index(p.severity) >= min_idx]
def get_pattern_by_category(category: GlitchCategory) -> Optional[GlitchPattern]:
"""Return the pattern definition for a specific category."""
for p in MATRIX_GLITCH_PATTERNS:
if p.category == category:
return p
return None
def build_vision_prompt(patterns: list[GlitchPattern] | None = None) -> str:
"""Build a composite vision analysis prompt from pattern definitions."""
if patterns is None:
patterns = MATRIX_GLITCH_PATTERNS
sections = []
for p in patterns:
prompt_text = " ".join(p.detection_prompts)
indicators = ", ".join(p.visual_indicators)
sections.append(
f"[{p.category.value.upper()}] {p.name} (severity: {p.severity.value})\n"
f" {p.description}\n"
f" Look for: {prompt_text}\n"
f" Visual indicators: {indicators}"
)
return (
"Analyze this 3D world screenshot for visual glitches and artifacts. "
"For each detected issue, report the category, description of what you see, "
"approximate location in the image (x%, y%), and confidence (0.0-1.0).\n\n"
"Known glitch patterns to check:\n\n" + "\n\n".join(sections)
)
# Three.js-specific category set for filtering (ref: timmy-config#543)
THREEJS_CATEGORIES = {
GlitchCategory.SHADER_FAILURE,
GlitchCategory.TEXTURE_PLACEHOLDER,
GlitchCategory.UV_MAPPING_ERROR,
GlitchCategory.FRUSTUM_CULLING,
GlitchCategory.SHADOW_MAP_ARTIFACT,
GlitchCategory.BLOOM_OVERFLOW,
}
def get_threejs_patterns() -> list[GlitchPattern]:
"""Return only Three.js-specific glitch patterns."""
return [p for p in MATRIX_GLITCH_PATTERNS if p.category in THREEJS_CATEGORIES]
if __name__ == "__main__":
import json
print(f"Loaded {len(MATRIX_GLITCH_PATTERNS)} glitch patterns:\n")
for p in MATRIX_GLITCH_PATTERNS:
print(f" [{p.severity.value:8s}] {p.category.value}: {p.name}")
print(f"\nVision prompt preview:\n{build_vision_prompt()[:500]}...")

View File

@@ -0,0 +1,586 @@
#!/usr/bin/env python3
"""
Matrix 3D World Glitch Detector
Scans a 3D web world for visual artifacts using browser automation
and vision AI analysis. Produces structured glitch reports.
Usage:
python matrix_glitch_detector.py <url> [--angles 4] [--output report.json]
python matrix_glitch_detector.py --demo # Run with synthetic test data
Ref: timmy-config#491, timmy-config#543
"""
import argparse
import base64
import json
import os
import sys
import time
import uuid
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
# Add parent for glitch_patterns import
sys.path.insert(0, str(Path(__file__).resolve().parent))
from glitch_patterns import (
GlitchCategory,
GlitchPattern,
GlitchSeverity,
MATRIX_GLITCH_PATTERNS,
build_vision_prompt,
get_patterns_by_severity,
get_threejs_patterns,
)
@dataclass
class DetectedGlitch:
"""A single detected glitch with metadata."""
id: str
category: str
name: str
description: str
severity: str
confidence: float
location_x: Optional[float] = None # percentage across image
location_y: Optional[float] = None # percentage down image
screenshot_index: int = 0
screenshot_angle: str = "front"
timestamp: str = ""
def __post_init__(self):
if not self.timestamp:
self.timestamp = datetime.now(timezone.utc).isoformat()
@dataclass
class ScanResult:
"""Complete scan result for a 3D world URL."""
scan_id: str
url: str
timestamp: str
total_screenshots: int
angles_captured: list[str]
glitches: list[dict] = field(default_factory=list)
summary: dict = field(default_factory=dict)
metadata: dict = field(default_factory=dict)
def to_json(self, indent: int = 2) -> str:
return json.dumps(asdict(self), indent=indent)
def generate_scan_angles(num_angles: int) -> list[dict]:
"""Generate camera angle configurations for multi-angle scanning.
Returns a list of dicts with yaw/pitch/label for browser camera control.
"""
base_angles = [
{"yaw": 0, "pitch": 0, "label": "front"},
{"yaw": 90, "pitch": 0, "label": "right"},
{"yaw": 180, "pitch": 0, "label": "back"},
{"yaw": 270, "pitch": 0, "label": "left"},
{"yaw": 0, "pitch": -30, "label": "front_low"},
{"yaw": 45, "pitch": -15, "label": "front_right_low"},
{"yaw": 0, "pitch": 30, "label": "front_high"},
{"yaw": 45, "pitch": 0, "label": "front_right"},
]
if num_angles <= len(base_angles):
return base_angles[:num_angles]
return base_angles + [
{"yaw": i * (360 // num_angles), "pitch": 0, "label": f"angle_{i}"}
for i in range(len(base_angles), num_angles)
]
def capture_screenshots(url: str, angles: list[dict], output_dir: Path) -> list[Path]:
"""Capture screenshots of a 3D web world from multiple angles.
Uses browser_vision tool when available; falls back to placeholder generation
for testing and environments without browser access.
"""
output_dir.mkdir(parents=True, exist_ok=True)
screenshots = []
for i, angle in enumerate(angles):
filename = output_dir / f"screenshot_{i:03d}_{angle['label']}.png"
# Attempt browser-based capture via browser_vision
try:
result = _browser_capture(url, angle, filename)
if result:
screenshots.append(filename)
continue
except Exception:
pass
# Generate placeholder screenshot for offline/test scenarios
_generate_placeholder_screenshot(filename, angle)
screenshots.append(filename)
return screenshots
def _browser_capture(url: str, angle: dict, output_path: Path) -> bool:
"""Capture a screenshot via browser automation.
This is a stub that delegates to the browser_vision tool when run
in an environment that provides it. In CI or offline mode, returns False.
"""
# Check if browser_vision is available via environment
bv_script = os.environ.get("BROWSER_VISION_SCRIPT")
if bv_script and Path(bv_script).exists():
import subprocess
cmd = [
sys.executable, bv_script,
"--url", url,
"--screenshot", str(output_path),
"--rotate-yaw", str(angle["yaw"]),
"--rotate-pitch", str(angle["pitch"]),
]
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return proc.returncode == 0 and output_path.exists()
return False
def _generate_placeholder_screenshot(path: Path, angle: dict):
"""Generate a minimal 1x1 PNG as a placeholder for testing."""
# Minimal valid PNG (1x1 transparent pixel)
png_data = (
b"\x89PNG\r\n\x1a\n"
b"\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
b"\x08\x06\x00\x00\x00\x1f\x15\xc4\x89"
b"\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01"
b"\r\n\xb4\x00\x00\x00\x00IEND\xaeB`\x82"
)
path.write_bytes(png_data)
def analyze_with_vision(
screenshot_paths: list[Path],
angles: list[dict],
patterns: list[GlitchPattern] | None = None,
) -> list[DetectedGlitch]:
"""Send screenshots to vision AI for glitch analysis.
In environments with a vision model available, sends each screenshot
with the composite detection prompt. Otherwise returns simulated results.
"""
if patterns is None:
patterns = MATRIX_GLITCH_PATTERNS
prompt = build_vision_prompt(patterns)
glitches = []
for i, (path, angle) in enumerate(zip(screenshot_paths, angles)):
# Attempt vision analysis
detected = _vision_analyze_image(path, prompt, i, angle["label"])
glitches.extend(detected)
return glitches
def _vision_analyze_image(
image_path: Path,
prompt: str,
screenshot_index: int,
angle_label: str,
) -> list[DetectedGlitch]:
"""Analyze a single screenshot with vision AI.
Uses the vision_analyze tool when available; returns empty list otherwise.
"""
# Check for vision API configuration
api_key = os.environ.get("VISION_API_KEY") or os.environ.get("OPENAI_API_KEY")
api_base = os.environ.get("VISION_API_BASE", "https://api.openai.com/v1")
if api_key:
try:
return _call_vision_api(
image_path, prompt, screenshot_index, angle_label, api_key, api_base
)
except Exception as e:
print(f" [!] Vision API error for {image_path.name}: {e}", file=sys.stderr)
# No vision backend available
return []
def _call_vision_api(
image_path: Path,
prompt: str,
screenshot_index: int,
angle_label: str,
api_key: str,
api_base: str,
) -> list[DetectedGlitch]:
"""Call a vision API (OpenAI-compatible) for image analysis."""
import urllib.request
import urllib.error
image_data = base64.b64encode(image_path.read_bytes()).decode()
payload = json.dumps({
"model": os.environ.get("VISION_MODEL", "gpt-4o"),
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{image_data}",
"detail": "high",
},
},
],
}
],
"max_tokens": 4096,
}).encode()
req = urllib.request.Request(
f"{api_base}/chat/completions",
data=payload,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
},
)
with urllib.request.urlopen(req, timeout=60) as resp:
result = json.loads(resp.read())
content = result["choices"][0]["message"]["content"]
return _parse_vision_response(content, screenshot_index, angle_label)
def _add_glitch_from_dict(
item: dict,
glitches: list[DetectedGlitch],
screenshot_index: int,
angle_label: str,
):
"""Convert a dict from vision API response into a DetectedGlitch."""
cat = item.get("category", item.get("type", "unknown"))
conf = float(item.get("confidence", item.get("score", 0.5)))
glitch = DetectedGlitch(
id=str(uuid.uuid4())[:8],
category=cat,
name=item.get("name", item.get("label", cat)),
description=item.get("description", item.get("detail", "")),
severity=item.get("severity", _infer_severity(cat, conf)),
confidence=conf,
location_x=item.get("location_x", item.get("x")),
location_y=item.get("location_y", item.get("y")),
screenshot_index=screenshot_index,
screenshot_angle=angle_label,
)
glitches.append(glitch)
def _parse_vision_response(
text: str, screenshot_index: int, angle_label: str
) -> list[DetectedGlitch]:
"""Parse vision AI response into structured glitch detections."""
glitches = []
# Try to extract JSON from the response
json_blocks = []
in_json = False
json_buf = []
for line in text.split("\n"):
stripped = line.strip()
if stripped.startswith("```"):
if in_json and json_buf:
try:
json_blocks.append(json.loads("\n".join(json_buf)))
except json.JSONDecodeError:
pass
json_buf = []
in_json = not in_json
continue
if in_json:
json_buf.append(line)
# Flush any remaining buffer
if in_json and json_buf:
try:
json_blocks.append(json.loads("\n".join(json_buf)))
except json.JSONDecodeError:
pass
# Also try parsing the entire response as JSON
try:
parsed = json.loads(text)
if isinstance(parsed, list):
json_blocks.extend(parsed)
elif isinstance(parsed, dict):
if "glitches" in parsed:
json_blocks.extend(parsed["glitches"])
elif "detections" in parsed:
json_blocks.extend(parsed["detections"])
else:
json_blocks.append(parsed)
except json.JSONDecodeError:
pass
for item in json_blocks:
# Flatten arrays of detections
if isinstance(item, list):
for sub in item:
if isinstance(sub, dict):
_add_glitch_from_dict(sub, glitches, screenshot_index, angle_label)
elif isinstance(item, dict):
_add_glitch_from_dict(item, glitches, screenshot_index, angle_label)
return glitches
def _infer_severity(category: str, confidence: float) -> str:
"""Infer severity from category and confidence when not provided."""
critical_cats = {"missing_textures", "clipping", "shader_failure", "texture_placeholder"}
high_cats = {"floating_assets", "broken_normals", "uv_mapping_error"}
medium_cats = {"frustum_culling", "shadow_map_artifact"}
cat_lower = category.lower()
if any(c in cat_lower for c in critical_cats):
return "critical" if confidence > 0.7 else "high"
if any(c in cat_lower for c in high_cats):
return "high" if confidence > 0.7 else "medium"
if any(c in cat_lower for c in medium_cats):
return "medium" if confidence > 0.6 else "low"
return "medium" if confidence > 0.6 else "low"
def build_report(
url: str,
angles: list[dict],
screenshots: list[Path],
glitches: list[DetectedGlitch],
) -> ScanResult:
"""Build the final structured scan report."""
severity_counts = {}
category_counts = {}
for g in glitches:
severity_counts[g.severity] = severity_counts.get(g.severity, 0) + 1
category_counts[g.category] = category_counts.get(g.category, 0) + 1
report = ScanResult(
scan_id=str(uuid.uuid4()),
url=url,
timestamp=datetime.now(timezone.utc).isoformat(),
total_screenshots=len(screenshots),
angles_captured=[a["label"] for a in angles],
glitches=[asdict(g) for g in glitches],
summary={
"total_glitches": len(glitches),
"by_severity": severity_counts,
"by_category": category_counts,
"highest_severity": max(severity_counts.keys(), default="none"),
"clean_screenshots": sum(
1
for i in range(len(screenshots))
if not any(g.screenshot_index == i for g in glitches)
),
},
metadata={
"detector_version": "0.2.0",
"pattern_count": len(MATRIX_GLITCH_PATTERNS),
"reference": "timmy-config#491, timmy-config#543",
},
)
return report
def run_demo(output_path: Optional[Path] = None) -> ScanResult:
"""Run a demonstration scan with simulated detections."""
print("[*] Running Matrix glitch detection demo...")
url = "https://matrix.example.com/world/alpha"
angles = generate_scan_angles(4)
screenshots_dir = Path("/tmp/matrix_glitch_screenshots")
print(f"[*] Capturing {len(angles)} screenshots from: {url}")
screenshots = capture_screenshots(url, angles, screenshots_dir)
print(f"[*] Captured {len(screenshots)} screenshots")
# Simulate detections for demo
demo_glitches = [
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="floating_assets",
name="Floating Chair",
description="Office chair floating 0.3m above floor in sector 7",
severity="high",
confidence=0.87,
location_x=35.2,
location_y=62.1,
screenshot_index=0,
screenshot_angle="front",
),
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="z_fighting",
name="Wall Texture Flicker",
description="Z-fighting between wall panel and decorative overlay",
severity="medium",
confidence=0.72,
location_x=58.0,
location_y=40.5,
screenshot_index=1,
screenshot_angle="right",
),
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="missing_textures",
name="Placeholder Texture",
description="Bright magenta surface on door frame — missing asset reference",
severity="critical",
confidence=0.95,
location_x=72.3,
location_y=28.8,
screenshot_index=2,
screenshot_angle="back",
),
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="clipping",
name="Desk Through Wall",
description="Desk corner clipping through adjacent wall geometry",
severity="high",
confidence=0.81,
location_x=15.0,
location_y=55.0,
screenshot_index=3,
screenshot_angle="left",
),
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="shader_failure",
name="Black Material on Portal Frame",
description="Portal frame rendered as solid black — shader compilation failed (missing uniform u_time)",
severity="critical",
confidence=0.91,
location_x=45.0,
location_y=30.0,
screenshot_index=0,
screenshot_angle="front",
),
DetectedGlitch(
id=str(uuid.uuid4())[:8],
category="shadow_map_artifact",
name="Pixelated Character Shadow",
description="Character shadow shows visible texel grid — shadow map resolution too low (512x512)",
severity="medium",
confidence=0.78,
location_x=52.0,
location_y=75.0,
screenshot_index=1,
screenshot_angle="right",
),
]
print(f"[*] Detected {len(demo_glitches)} glitches")
report = build_report(url, angles, screenshots, demo_glitches)
if output_path:
output_path.write_text(report.to_json())
print(f"[*] Report saved to: {output_path}")
return report
def main():
parser = argparse.ArgumentParser(
description="Matrix 3D World Glitch Detector — scan for visual artifacts",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s https://matrix.example.com/world/alpha
%(prog)s https://matrix.example.com/world/alpha --angles 8 --output report.json
%(prog)s --demo
""",
)
parser.add_argument("url", nargs="?", help="URL of the 3D world to scan")
parser.add_argument(
"--angles", type=int, default=4, help="Number of camera angles to capture (default: 4)"
)
parser.add_argument("--output", "-o", type=str, help="Output file path for JSON report")
parser.add_argument("--demo", action="store_true", help="Run demo with simulated data")
parser.add_argument(
"--min-severity",
choices=["info", "low", "medium", "high", "critical"],
default="info",
help="Minimum severity to include in report",
)
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
parser.add_argument(
"--threejs",
action="store_true",
help="Focus on Three.js-specific glitch patterns only (shader, texture, UV, culling, shadow, bloom)",
)
args = parser.parse_args()
if args.demo:
output = Path(args.output) if args.output else Path("glitch_report_demo.json")
report = run_demo(output)
print(f"\n=== Scan Summary ===")
print(f"URL: {report.url}")
print(f"Screenshots: {report.total_screenshots}")
print(f"Glitches found: {report.summary['total_glitches']}")
print(f"By severity: {report.summary['by_severity']}")
return
if not args.url:
parser.error("URL required (or use --demo)")
scan_id = str(uuid.uuid4())[:8]
print(f"[*] Matrix Glitch Detector — Scan {scan_id}")
print(f"[*] Target: {args.url}")
# Generate camera angles
angles = generate_scan_angles(args.angles)
print(f"[*] Capturing {len(angles)} screenshots...")
# Capture screenshots
screenshots_dir = Path(f"/tmp/matrix_glitch_{scan_id}")
screenshots = capture_screenshots(args.url, angles, screenshots_dir)
print(f"[*] Captured {len(screenshots)} screenshots")
# Filter patterns by severity and type
min_sev = GlitchSeverity(args.min_severity)
patterns = get_patterns_by_severity(min_sev)
if args.threejs:
threejs_patterns = get_threejs_patterns()
patterns = [p for p in patterns if p in threejs_patterns]
print(f"[*] Three.js-focused mode: {len(patterns)} patterns")
# Analyze with vision AI
print(f"[*] Analyzing with vision AI ({len(patterns)} patterns)...")
glitches = analyze_with_vision(screenshots, angles, patterns)
# Build and save report
report = build_report(args.url, angles, screenshots, glitches)
if args.output:
Path(args.output).write_text(report.to_json())
print(f"[*] Report saved: {args.output}")
else:
print(report.to_json())
print(f"\n[*] Done — {len(glitches)} glitches detected")
if __name__ == "__main__":
main()

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
"""
Full Nostr agent-to-agent communication demo - FINAL WORKING
"""

514
bin/pane-watchdog.sh Executable file
View File

@@ -0,0 +1,514 @@
#!/usr/bin/env bash
# pane-watchdog.sh — Detect stuck/dead tmux panes and auto-restart them
#
# Tracks output hash per pane across cycles. If a pane's captured output
# hasn't changed for STUCK_CYCLES consecutive checks, the pane is STUCK.
# Dead panes (PID gone) are also detected.
#
# On STUCK/DEAD:
# 1. Kill the pane
# 2. Attempt restart with --resume (session ID from manifest)
# 3. Fallback: fresh prompt with last known task from logs
#
# State file: ~/.hermes/pane-state.json
# Log: ~/.hermes/logs/pane-watchdog.log
#
# Usage:
# pane-watchdog.sh # One-shot check all sessions
# pane-watchdog.sh --daemon # Run every CHECK_INTERVAL seconds
# pane-watchdog.sh --status # Print current pane state
# pane-watchdog.sh --session NAME # Check only one session
#
# Issue: timmy-config #515
set -uo pipefail
export PATH="/opt/homebrew/bin:$HOME/.local/bin:$HOME/.hermes/bin:/usr/local/bin:$PATH"
# === CONFIG ===
STATE_FILE="${PANE_STATE_FILE:-$HOME/.hermes/pane-state.json}"
LOG_FILE="${PANE_WATCHDOG_LOG:-$HOME/.hermes/logs/pane-watchdog.log}"
CHECK_INTERVAL="${PANE_CHECK_INTERVAL:-120}" # seconds between cycles
STUCK_CYCLES=2 # unchanged cycles before STUCK
MAX_RESTART_ATTEMPTS=3 # per pane per hour
RESTART_COOLDOWN=3600 # seconds between escalation alerts
CAPTURE_LINES=40 # lines of output to hash
# Sessions to monitor (all if empty)
MONITOR_SESSIONS="${PANE_WATCHDOG_SESSIONS:-}"
mkdir -p "$(dirname "$STATE_FILE")" "$(dirname "$LOG_FILE")"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}
# === HELPERS ===
# Capture last N lines of pane output and hash them
capture_pane_hash() {
local target="$1"
local output
output=$(tmux capture-pane -t "$target" -p -S "-${CAPTURE_LINES}" 2>/dev/null || echo "DEAD")
echo -n "$output" | shasum -a 256 | cut -d' ' -f1
}
# Check if pane PID is alive
pane_pid_alive() {
local target="$1"
local pid
pid=$(tmux list-panes -t "$target" -F '#{pane_pid}' 2>/dev/null | head -1 || echo "")
if [ -z "$pid" ]; then
return 1 # pane doesn't exist
fi
kill -0 "$pid" 2>/dev/null
}
# Get pane start command
pane_start_command() {
local target="$1"
tmux list-panes -t "$target" -F '#{pane_start_command}' 2>/dev/null | head -1 || echo "unknown"
}
# Get the pane's current running command (child process)
pane_current_command() {
local target="$1"
tmux list-panes -t "$target" -F '#{pane_current_command}' 2>/dev/null || echo "unknown"
}
# Only restart panes running hermes/agent commands (not zsh, python3 repls, etc.)
is_restartable() {
local cmd="$1"
case "$cmd" in
hermes|*hermes*|*agent*|*timmy*|*kimi*|*claude-loop*|*gemini-loop*)
return 0
;;
*)
return 1
;;
esac
}
# Get session ID from hermes manifest if available
get_hermes_session_id() {
local session_name="$1"
local manifest="$HOME/.hermes/sessions/${session_name}/manifest.json"
if [ -f "$manifest" ]; then
python3 -c "
import json, sys
try:
m = json.load(open('$manifest'))
print(m.get('session_id', m.get('id', '')))
except: pass
" 2>/dev/null || echo ""
else
echo ""
fi
}
# Get last task from pane logs
get_last_task() {
local session_name="$1"
local log_dir="$HOME/.hermes/logs"
# Find the most recent log for this session
local log_file
log_file=$(find "$log_dir" -name "*${session_name}*" -type f -mtime -1 2>/dev/null | sort -r | head -1)
if [ -n "$log_file" ] && [ -f "$log_file" ]; then
# Extract last user prompt or task description
grep -i "task:\|prompt:\|issue\|working on" "$log_file" 2>/dev/null | tail -1 | sed 's/.*[:>] *//' | head -c 200
fi
}
# Restart a pane with a fresh shell/command
restart_pane() {
local target="$1"
local session_name="${target%%:*}"
local session_id last_task cmd
log "RESTART: Attempting to restart $target"
# Kill existing pane
tmux kill-pane -t "$target" 2>/dev/null || true
sleep 1
# Try --resume with session ID
session_id=$(get_hermes_session_id "$session_name")
if [ -n "$session_id" ]; then
log "RESTART: Trying --resume with session $session_id"
tmux split-window -t "$session_name" -d \
"hermes chat --resume '$session_id' 2>&1 | tee -a '$HOME/.hermes/logs/${session_name}-restart.log'"
sleep 2
if pane_pid_alive "${session_name}:1" 2>/dev/null; then
log "RESTART: Success with --resume"
return 0
fi
fi
# Fallback: fresh prompt
last_task=$(get_last_task "$session_name")
if [ -n "$last_task" ]; then
log "RESTART: Fallback — fresh prompt with task: $last_task"
tmux split-window -t "$session_name" -d \
"echo 'Watchdog restart — last task: $last_task' && hermes chat 2>&1 | tee -a '$HOME/.hermes/logs/${session_name}-restart.log'"
else
log "RESTART: Fallback — fresh hermes chat"
tmux split-window -t "$session_name" -d \
"hermes chat 2>&1 | tee -a '$HOME/.hermes/logs/${session_name}-restart.log'"
fi
sleep 2
if pane_pid_alive "${session_name}:1" 2>/dev/null; then
log "RESTART: Fallback restart succeeded"
return 0
else
log "RESTART: FAILED to restart $target"
return 1
fi
}
# === STATE MANAGEMENT ===
read_state() {
if [ -f "$STATE_FILE" ]; then
cat "$STATE_FILE"
else
echo "{}"
fi
}
write_state() {
echo "$1" > "$STATE_FILE"
}
# Update state for a single pane and return JSON status
update_pane_state() {
local target="$1"
local hash="$2"
local is_alive="$3"
local now
now=$(date +%s)
python3 - "$STATE_FILE" "$target" "$hash" "$is_alive" "$now" "$STUCK_CYCLES" <<'PYEOF'
import json, sys, time
state_file = sys.argv[1]
target = sys.argv[2]
new_hash = sys.argv[3]
is_alive = sys.argv[4] == "true"
now = int(sys.argv[5])
stuck_cycles = int(sys.argv[6])
try:
with open(state_file) as f:
state = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
state = {}
pane = state.get(target, {
"hash": "",
"same_count": 0,
"status": "UNKNOWN",
"last_change": 0,
"last_check": 0,
"restart_attempts": 0,
"last_restart": 0,
"current_command": "",
})
if not is_alive:
pane["status"] = "DEAD"
pane["same_count"] = 0
elif new_hash == pane.get("hash", ""):
pane["same_count"] = pane.get("same_count", 0) + 1
if pane["same_count"] >= stuck_cycles:
pane["status"] = "STUCK"
else:
pane["status"] = "STALE" if pane["same_count"] > 0 else "OK"
else:
pane["hash"] = new_hash
pane["same_count"] = 0
pane["status"] = "OK"
pane["last_change"] = now
pane["last_check"] = now
state[target] = pane
with open(state_file, "w") as f:
json.dump(state, f, indent=2)
print(json.dumps(pane))
PYEOF
}
# Reset restart attempt counter if cooldown expired
maybe_reset_restarts() {
local target="$1"
local now
now=$(date +%s)
python3 - "$STATE_FILE" "$target" "$now" "$RESTART_COOLDOWN" <<'PYEOF'
import json, sys
state_file = sys.argv[1]
target = sys.argv[2]
now = int(sys.argv[3])
cooldown = int(sys.argv[4])
with open(state_file) as f:
state = json.load(f)
pane = state.get(target, {})
last_restart = pane.get("last_restart", 0)
if now - last_restart > cooldown:
pane["restart_attempts"] = 0
state[target] = pane
with open(state_file, "w") as f:
json.dump(state, f, indent=2)
print(pane.get("restart_attempts", 0))
PYEOF
}
increment_restart_attempt() {
local target="$1"
local now
now=$(date +%s)
python3 - "$STATE_FILE" "$target" "$now" <<'PYEOF'
import json, sys
state_file = sys.argv[1]
target = sys.argv[2]
now = int(sys.argv[3])
with open(state_file) as f:
state = json.load(f)
pane = state.get(target, {})
pane["restart_attempts"] = pane.get("restart_attempts", 0) + 1
pane["last_restart"] = now
pane["status"] = "RESTARTING"
state[target] = pane
with open(state_file, "w") as f:
json.dump(state, f, indent=2)
print(pane["restart_attempts"])
PYEOF
}
# === CORE CHECK ===
check_pane() {
local target="$1"
local hash is_alive status current_cmd
# Capture state
hash=$(capture_pane_hash "$target")
if pane_pid_alive "$target"; then
is_alive="true"
else
is_alive="false"
fi
# Get current command for the pane
current_cmd=$(pane_current_command "$target")
# Update state and get result
local result
result=$(update_pane_state "$target" "$hash" "$is_alive")
status=$(echo "$result" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('status','UNKNOWN'))" 2>/dev/null || echo "UNKNOWN")
case "$status" in
OK)
# Healthy, do nothing
;;
DEAD)
log "DETECTED: $target is DEAD (PID gone) cmd=$current_cmd"
if is_restartable "$current_cmd"; then
handle_stuck "$target"
else
log "SKIP: $target not a hermes pane (cmd=$current_cmd), not restarting"
fi
;;
STUCK)
log "DETECTED: $target is STUCK (output unchanged for ${STUCK_CYCLES} cycles) cmd=$current_cmd"
if is_restartable "$current_cmd"; then
handle_stuck "$target"
else
log "SKIP: $target not a hermes pane (cmd=$current_cmd), not restarting"
fi
;;
STALE)
# Output unchanged but within threshold — just log
local count
count=$(echo "$result" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('same_count',0))" 2>/dev/null || echo "?")
log "STALE: $target unchanged for $count cycle(s)"
;;
esac
}
handle_stuck() {
local target="$1"
local session_name="${target%%:*}"
local attempts
# Check restart budget
attempts=$(maybe_reset_restarts "$target")
if [ "$attempts" -ge "$MAX_RESTART_ATTEMPTS" ]; then
log "ESCALATION: $target stuck ${attempts}x — manual intervention needed"
echo "ALERT: $target stuck after $attempts restart attempts" >&2
return 1
fi
attempts=$(increment_restart_attempt "$target")
log "ACTION: Restarting $target (attempt $attempts/$MAX_RESTART_ATTEMPTS)"
if restart_pane "$target"; then
log "OK: $target restarted successfully"
else
log "FAIL: $target restart failed (attempt $attempts)"
fi
}
check_all_sessions() {
local sessions
if [ -n "$MONITOR_SESSIONS" ]; then
IFS=',' read -ra sessions <<< "$MONITOR_SESSIONS"
else
sessions=()
while IFS= read -r line; do
[ -n "$line" ] && sessions+=("$line")
done < <(tmux list-sessions -F '#{session_name}' 2>/dev/null || true)
fi
local total=0 stuck=0 dead=0 ok=0
for session in "${sessions[@]}"; do
[ -z "$session" ] && continue
# Get pane targets
local panes
panes=$(tmux list-panes -t "$session" -F "${session}:#{window_index}.#{pane_index}" 2>/dev/null || true)
for target in $panes; do
check_pane "$target"
total=$((total + 1))
done
done
log "CHECK: Processed $total panes"
}
# === STATUS DISPLAY ===
show_status() {
if [ ! -f "$STATE_FILE" ]; then
echo "No pane state file found at $STATE_FILE"
echo "Run pane-watchdog.sh once to initialize."
exit 0
fi
python3 - "$STATE_FILE" <<'PYEOF'
import json, sys, time
state_file = sys.argv[1]
try:
with open(state_file) as f:
state = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
print("No state data yet.")
sys.exit(0)
if not state:
print("No panes tracked.")
sys.exit(0)
now = int(time.time())
print(f"{'PANE':<35} {'STATUS':<12} {'STALE':<6} {'LAST CHANGE':<15} {'RESTARTS'}")
print("-" * 90)
for target in sorted(state.keys()):
p = state[target]
status = p.get("status", "?")
same = p.get("same_count", 0)
last_change = p.get("last_change", 0)
restarts = p.get("restart_attempts", 0)
if last_change:
ago = now - last_change
if ago < 60:
change_str = f"{ago}s ago"
elif ago < 3600:
change_str = f"{ago//60}m ago"
else:
change_str = f"{ago//3600}h ago"
else:
change_str = "never"
# Color code
if status == "OK":
icon = "✓"
elif status == "STUCK":
icon = "✖"
elif status == "DEAD":
icon = "☠"
elif status == "STALE":
icon = "⏳"
else:
icon = "?"
print(f" {icon} {target:<32} {status:<12} {same:<6} {change_str:<15} {restarts}")
PYEOF
}
# === DAEMON MODE ===
run_daemon() {
log "DAEMON: Starting (interval=${CHECK_INTERVAL}s, stuck_threshold=${STUCK_CYCLES})"
echo "Pane watchdog started. Checking every ${CHECK_INTERVAL}s. Ctrl+C to stop."
echo "Log: $LOG_FILE"
echo "State: $STATE_FILE"
echo ""
while true; do
check_all_sessions
sleep "$CHECK_INTERVAL"
done
}
# === MAIN ===
case "${1:-}" in
--daemon)
run_daemon
;;
--status)
show_status
;;
--session)
if [ -z "${2:-}" ]; then
echo "Usage: pane-watchdog.sh --session SESSION_NAME"
exit 1
fi
MONITOR_SESSIONS="$2"
check_all_sessions
;;
--help|-h)
echo "pane-watchdog.sh — Detect stuck/dead tmux panes and auto-restart"
echo ""
echo "Usage:"
echo " pane-watchdog.sh # One-shot check"
echo " pane-watchdog.sh --daemon # Continuous monitoring"
echo " pane-watchdog.sh --status # Show pane state"
echo " pane-watchdog.sh --session S # Check one session"
echo ""
echo "Config (env vars):"
echo " PANE_CHECK_INTERVAL Seconds between checks (default: 120)"
echo " PANE_WATCHDOG_SESSIONS Comma-separated session names"
echo " PANE_STATE_FILE State file path"
echo " STUCK_CYCLES Unchanged cycles before STUCK (default: 2)"
;;
*)
check_all_sessions
;;
esac

View File

@@ -0,0 +1,271 @@
#!/usr/bin/env python3
"""
Pre-Flight Provider Check Script
Issue #508: [Robustness] Credential drain detection — provider health checks
Pre-flight check before session launch: verifies provider credentials and balance.
Usage:
python3 preflight-provider-check.py # Check all providers
python3 preflight-provider-check.py --launch # Check and return exit code
python3 preflight-provider-check.py --balance # Check OpenRouter balance
"""
import os, sys, json, yaml, urllib.request
from datetime import datetime, timezone
from pathlib import Path
# Configuration
HERMES_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
LOG_DIR = Path.home() / ".local" / "timmy" / "fleet-health"
LOG_FILE = LOG_DIR / "preflight-check.log"
def log(msg):
"""Log message to file and optionally console."""
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
log_entry = "[" + timestamp + "] " + msg
LOG_DIR.mkdir(parents=True, exist_ok=True)
with open(LOG_FILE, "a") as f:
f.write(log_entry + "\n")
if "--quiet" not in sys.argv:
print(log_entry)
def get_provider_api_key(provider):
"""Get API key for a provider from .env or environment."""
env_file = HERMES_HOME / ".env"
if env_file.exists():
with open(env_file) as f:
for line in f:
line = line.strip()
if line.startswith(provider.upper() + "_API_KEY="):
return line.split("=", 1)[1].strip().strip("'\"")
return os.environ.get(provider.upper() + "_API_KEY")
def check_openrouter_balance(api_key):
"""Check OpenRouter balance via /api/v1/auth/key."""
if not api_key:
return False, "No API key", 0
try:
req = urllib.request.Request(
"https://openrouter.ai/api/v1/auth/key",
headers={"Authorization": "Bearer " + api_key}
)
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
# Check for credits
credits = data.get("data", {}).get("limit", 0)
usage = data.get("data", {}).get("usage", 0)
remaining = credits - usage if credits else None
if remaining is not None and remaining <= 0:
return False, "No credits remaining", 0
elif remaining is not None:
return True, "Credits available", remaining
else:
return True, "Unlimited or unknown balance", None
except urllib.error.HTTPError as e:
if e.code == 401:
return False, "Invalid API key", 0
else:
return False, "HTTP " + str(e.code), 0
except Exception as e:
return False, str(e)[:100], 0
def check_nous_key(api_key):
"""Check Nous API key with minimal test call."""
if not api_key:
return False, "No API key"
try:
req = urllib.request.Request(
"https://inference.nousresearch.com/v1/models",
headers={"Authorization": "Bearer " + api_key}
)
resp = urllib.request.urlopen(req, timeout=10)
if resp.status == 200:
return True, "Valid key"
else:
return False, "HTTP " + str(resp.status)
except urllib.error.HTTPError as e:
if e.code == 401:
return False, "Invalid API key"
elif e.code == 403:
return False, "Forbidden"
else:
return False, "HTTP " + str(e.code)
except Exception as e:
return False, str(e)[:100]
def check_anthropic_key(api_key):
"""Check Anthropic API key with minimal test call."""
if not api_key:
return False, "No API key"
try:
req = urllib.request.Request(
"https://api.anthropic.com/v1/models",
headers={
"x-api-key": api_key,
"anthropic-version": "2023-06-01"
}
)
resp = urllib.request.urlopen(req, timeout=10)
if resp.status == 200:
return True, "Valid key"
else:
return False, "HTTP " + str(resp.status)
except urllib.error.HTTPError as e:
if e.code == 401:
return False, "Invalid API key"
elif e.code == 403:
return False, "Forbidden"
else:
return False, "HTTP " + str(e.code)
except Exception as e:
return False, str(e)[:100]
def check_ollama():
"""Check if Ollama is running."""
try:
req = urllib.request.Request("http://localhost:11434/api/tags")
resp = urllib.request.urlopen(req, timeout=5)
if resp.status == 200:
data = json.loads(resp.read())
models = data.get("models", [])
return True, str(len(models)) + " models loaded"
else:
return False, "HTTP " + str(resp.status)
except Exception as e:
return False, str(e)[:100]
def get_configured_provider():
"""Get the configured provider from global config."""
config_file = HERMES_HOME / "config.yaml"
if not config_file.exists():
return None
try:
with open(config_file) as f:
config = yaml.safe_load(f)
model_config = config.get("model", {})
if isinstance(model_config, dict):
return model_config.get("provider")
except:
pass
return None
def run_preflight_check():
"""Run pre-flight check on all providers."""
log("=== Pre-Flight Provider Check ===")
results = {}
# Check OpenRouter
or_key = get_provider_api_key("openrouter")
or_ok, or_msg, or_balance = check_openrouter_balance(or_key)
results["openrouter"] = {"healthy": or_ok, "message": or_msg, "balance": or_balance}
# Check Nous
nous_key = get_provider_api_key("nous")
nous_ok, nous_msg = check_nous_key(nous_key)
results["nous"] = {"healthy": nous_ok, "message": nous_msg}
# Check Anthropic
anthropic_key = get_provider_api_key("anthropic")
anthropic_ok, anthropic_msg = check_anthropic_key(anthropic_key)
results["anthropic"] = {"healthy": anthropic_ok, "message": anthropic_msg}
# Check Ollama
ollama_ok, ollama_msg = check_ollama()
results["ollama"] = {"healthy": ollama_ok, "message": ollama_msg}
# Get configured provider
configured = get_configured_provider()
# Summary
healthy_count = sum(1 for r in results.values() if r["healthy"])
total_count = len(results)
log("Results: " + str(healthy_count) + "/" + str(total_count) + " providers healthy")
for provider, result in results.items():
status = "HEALTHY" if result["healthy"] else "UNHEALTHY"
extra = ""
if provider == "openrouter" and result.get("balance") is not None:
extra = " (balance: " + str(result["balance"]) + ")"
log(" " + provider + ": " + status + " - " + result["message"] + extra)
if configured:
log("Configured provider: " + configured)
if configured in results and not results[configured]["healthy"]:
log("WARNING: Configured provider " + configured + " is UNHEALTHY!")
return results, configured
def check_launch_readiness():
"""Check if we're ready to launch sessions."""
results, configured = run_preflight_check()
# Check if configured provider is healthy
if configured and configured in results:
if not results[configured]["healthy"]:
log("LAUNCH BLOCKED: Configured provider " + configured + " is unhealthy")
return False, configured + " is unhealthy"
# Check if at least one provider is healthy
healthy_providers = [p for p, r in results.items() if r["healthy"]]
if not healthy_providers:
log("LAUNCH BLOCKED: No healthy providers available")
return False, "No healthy providers"
log("LAUNCH READY: " + str(len(healthy_providers)) + " healthy providers available")
return True, "Ready"
def show_balance():
"""Show OpenRouter balance."""
api_key = get_provider_api_key("openrouter")
if not api_key:
print("No OpenRouter API key found")
return
ok, msg, balance = check_openrouter_balance(api_key)
if ok:
if balance is not None:
print("OpenRouter balance: " + str(balance) + " credits")
else:
print("OpenRouter: " + msg)
else:
print("OpenRouter: " + msg)
def main():
if "--balance" in sys.argv:
show_balance()
elif "--launch" in sys.argv:
ready, message = check_launch_readiness()
if ready:
print("READY")
sys.exit(0)
else:
print("BLOCKED: " + message)
sys.exit(1)
else:
run_preflight_check()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,411 @@
#!/usr/bin/env python3
"""
Provider Health Monitor Script
Issue #509: [Robustness] Provider-aware profile config — auto-switch on failure
Monitors provider health and automatically switches profiles to working providers.
Usage:
python3 provider-health-monitor.py # Run once
python3 provider-health-monitor.py --daemon # Run continuously
python3 provider-health-monitor.py --status # Show provider health
"""
import os, sys, json, yaml, urllib.request, time
from datetime import datetime, timezone
from pathlib import Path
# Configuration
HERMES_HOME = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
PROFILES_DIR = HERMES_HOME / "profiles"
LOG_DIR = Path.home() / ".local" / "timmy" / "fleet-health"
STATE_FILE = LOG_DIR / "tmux-state.json"
LOG_FILE = LOG_DIR / "provider-health.log"
# Provider test endpoints
PROVIDER_TESTS = {
"openrouter": {
"url": "https://openrouter.ai/api/v1/models",
"method": "GET",
"headers": lambda api_key: {"Authorization": "Bearer " + api_key},
"timeout": 10
},
"anthropic": {
"url": "https://api.anthropic.com/v1/models",
"method": "GET",
"headers": lambda api_key: {"x-api-key": api_key, "anthropic-version": "2023-06-01"},
"timeout": 10
},
"nous": {
"url": "https://inference.nousresearch.com/v1/models",
"method": "GET",
"headers": lambda api_key: {"Authorization": "Bearer " + api_key},
"timeout": 10
},
"kimi-coding": {
"url": "https://api.kimi.com/coding/v1/models",
"method": "GET",
"headers": lambda api_key: {"x-api-key": api_key, "x-api-provider": "kimi-coding"},
"timeout": 10
},
"ollama": {
"url": "http://localhost:11434/api/tags",
"method": "GET",
"headers": lambda api_key: {},
"timeout": 5
}
}
def log(msg):
"""Log message to file and optionally console."""
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
log_entry = "[" + timestamp + "] " + msg
LOG_DIR.mkdir(parents=True, exist_ok=True)
with open(LOG_FILE, "a") as f:
f.write(log_entry + "\n")
if "--quiet" not in sys.argv:
print(log_entry)
def get_provider_api_key(provider):
"""Get API key for a provider from .env or environment."""
env_file = HERMES_HOME / ".env"
if env_file.exists():
with open(env_file) as f:
for line in f:
line = line.strip()
if line.startswith(provider.upper() + "_API_KEY="):
return line.split("=", 1)[1].strip().strip("'\"")
return os.environ.get(provider.upper() + "_API_KEY")
def test_provider(provider, api_key=None):
"""Test if a provider is healthy."""
config = PROVIDER_TESTS.get(provider)
if not config:
return False, "Unknown provider: " + provider
headers = config["headers"](api_key or "")
try:
req = urllib.request.Request(
config["url"],
headers=headers,
method=config["method"]
)
resp = urllib.request.urlopen(req, timeout=config["timeout"])
if resp.status == 200:
return True, "Healthy"
else:
return False, "HTTP " + str(resp.status)
except urllib.error.HTTPError as e:
if e.code == 401:
return False, "Unauthorized (401)"
elif e.code == 403:
return False, "Forbidden (403)"
elif e.code == 429:
return True, "Rate limited but accessible"
else:
return False, "HTTP " + str(e.code)
except Exception as e:
return False, str(e)[:100]
def get_all_providers():
"""Get all providers from profiles and global config."""
providers = set()
# Global config
global_config = HERMES_HOME / "config.yaml"
if global_config.exists():
try:
with open(global_config) as f:
config = yaml.safe_load(f)
# Primary model provider
model_config = config.get("model", {})
if isinstance(model_config, dict):
provider = model_config.get("provider", "")
if provider:
providers.add(provider)
# Auxiliary providers
auxiliary = config.get("auxiliary", {})
for aux_config in auxiliary.values():
if isinstance(aux_config, dict):
provider = aux_config.get("provider", "")
if provider and provider != "auto":
providers.add(provider)
except:
pass
# Profile configs
if PROFILES_DIR.exists():
for profile_dir in PROFILES_DIR.iterdir():
if profile_dir.is_dir():
config_file = profile_dir / "config.yaml"
if config_file.exists():
try:
with open(config_file) as f:
config = yaml.safe_load(f)
model_config = config.get("model", {})
if isinstance(model_config, dict):
provider = model_config.get("provider", "")
if provider:
providers.add(provider)
auxiliary = config.get("auxiliary", {})
for aux_config in auxiliary.values():
if isinstance(aux_config, dict):
provider = aux_config.get("provider", "")
if provider and provider != "auto":
providers.add(provider)
except:
pass
# Add common providers even if not configured
providers.update(["openrouter", "nous", "ollama"])
return list(providers)
def build_health_map():
"""Build a health map of all providers."""
providers = get_all_providers()
health_map = {}
log("Testing " + str(len(providers)) + " providers...")
for provider in providers:
api_key = get_provider_api_key(provider)
healthy, message = test_provider(provider, api_key)
health_map[provider] = {
"healthy": healthy,
"message": message,
"last_test": datetime.now(timezone.utc).isoformat(),
"api_key_present": bool(api_key)
}
status = "HEALTHY" if healthy else "UNHEALTHY"
log(" " + provider + ": " + status + " - " + message)
return health_map
def get_fallback_providers(health_map):
"""Get list of healthy providers in priority order."""
# Priority order: nous, openrouter, ollama, others
priority_order = ["nous", "openrouter", "ollama", "anthropic", "kimi-coding"]
healthy = []
for provider in priority_order:
if provider in health_map and health_map[provider]["healthy"]:
healthy.append(provider)
# Add any other healthy providers not in priority list
for provider, info in health_map.items():
if info["healthy"] and provider not in healthy:
healthy.append(provider)
return healthy
def update_profile_config(profile_name, new_provider):
"""Update a profile's config to use a new provider."""
config_file = PROFILES_DIR / profile_name / "config.yaml"
if not config_file.exists():
return False, "Config file not found"
try:
with open(config_file) as f:
config = yaml.safe_load(f)
# Update model provider
if "model" not in config:
config["model"] = {}
old_provider = config["model"].get("provider", "unknown")
config["model"]["provider"] = new_provider
# Update auxiliary providers if they were using the old provider
auxiliary = config.get("auxiliary", {})
for aux_name, aux_config in auxiliary.items():
if isinstance(aux_config, dict) and aux_config.get("provider") == old_provider:
aux_config["provider"] = new_provider
# Write back
with open(config_file, "w") as f:
yaml.dump(config, f, default_flow_style=False)
log("Updated " + profile_name + ": " + old_provider + " -> " + new_provider)
return True, "Updated"
except Exception as e:
return False, str(e)
def check_profiles(health_map):
"""Check all profiles and update unhealthy providers."""
if not PROFILES_DIR.exists():
return
fallback_providers = get_fallback_providers(health_map)
if not fallback_providers:
log("CRITICAL: No healthy providers available!")
return
updated_profiles = []
for profile_dir in PROFILES_DIR.iterdir():
if not profile_dir.is_dir():
continue
profile_name = profile_dir.name
config_file = profile_dir / "config.yaml"
if not config_file.exists():
continue
try:
with open(config_file) as f:
config = yaml.safe_load(f)
model_config = config.get("model", {})
if not isinstance(model_config, dict):
continue
current_provider = model_config.get("provider", "")
if not current_provider:
continue
# Check if current provider is healthy
if current_provider in health_map and health_map[current_provider]["healthy"]:
continue # Provider is healthy, no action needed
# Find best fallback
best_fallback = None
for provider in fallback_providers:
if provider != current_provider:
best_fallback = provider
break
if not best_fallback:
log("No fallback for " + profile_name + " (current: " + current_provider + ")")
continue
# Update profile
success, message = update_profile_config(profile_name, best_fallback)
if success:
updated_profiles.append({
"profile": profile_name,
"old_provider": current_provider,
"new_provider": best_fallback
})
except Exception as e:
log("Error processing " + profile_name + ": " + str(e))
return updated_profiles
def load_state():
"""Load state from tmux-state.json."""
if STATE_FILE.exists():
try:
with open(STATE_FILE) as f:
return json.load(f)
except:
pass
return {}
def save_state(state):
"""Save state to tmux-state.json."""
LOG_DIR.mkdir(parents=True, exist_ok=True)
with open(STATE_FILE, "w") as f:
json.dump(state, f, indent=2)
def run_once():
"""Run provider health check once."""
log("=== Provider Health Check ===")
state = load_state()
# Build health map
health_map = build_health_map()
# Check profiles and update if needed
updated_profiles = check_profiles(health_map)
# Update state
state["provider_health"] = health_map
state["last_provider_check"] = datetime.now(timezone.utc).isoformat()
if updated_profiles:
state["last_profile_updates"] = updated_profiles
save_state(state)
# Summary
healthy_count = sum(1 for p in health_map.values() if p["healthy"])
total_count = len(health_map)
log("Health: " + str(healthy_count) + "/" + str(total_count) + " providers healthy")
if updated_profiles:
log("Updated " + str(len(updated_profiles)) + " profiles:")
for update in updated_profiles:
log(" " + update["profile"] + ": " + update["old_provider"] + " -> " + update["new_provider"])
def show_status():
"""Show provider health status."""
state = load_state()
health_map = state.get("provider_health", {})
if not health_map:
print("No provider health data available. Run without --status first.")
return
print("Provider Health (last updated: " + str(state.get("last_provider_check", "unknown")) + ")")
print("=" * 80)
for provider, info in sorted(health_map.items()):
status = "HEALTHY" if info["healthy"] else "UNHEALTHY"
message = info.get("message", "")
api_key = "yes" if info.get("api_key_present") else "no"
print(provider.ljust(20) + " " + status.ljust(10) + " API key: " + api_key + " - " + message)
# Show recent updates
updates = state.get("last_profile_updates", [])
if updates:
print()
print("Recent Profile Updates:")
for update in updates:
print(" " + update["profile"] + ": " + update["old_provider"] + " -> " + update["new_provider"])
def daemon_mode():
"""Run continuously."""
log("Starting provider health daemon (check every 300s)")
while True:
try:
run_once()
time.sleep(300) # Check every 5 minutes
except KeyboardInterrupt:
log("Daemon stopped by user")
break
except Exception as e:
log("Error: " + str(e))
time.sleep(60)
def main():
if "--status" in sys.argv:
show_status()
elif "--daemon" in sys.argv:
daemon_mode()
else:
run_once()
if __name__ == "__main__":
main()

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
"""
Soul Eval Gate — The Conscience of the Training Pipeline

View File

@@ -3,7 +3,7 @@
# Uses Hermes CLI plus workforce-manager to triage and review.
# Timmy is the brain. Other agents are the hands.
set -uo pipefail
set -uo pipefail\n\nSCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="$HOME/.hermes/logs"
LOG="$LOG_DIR/timmy-orchestrator.log"
@@ -40,6 +40,7 @@ gather_state() {
> "$state_dir/unassigned.txt"
> "$state_dir/open_prs.txt"
> "$state_dir/agent_status.txt"
> "$state_dir/uncommitted_work.txt"
for repo in $REPOS; do
local short=$(echo "$repo" | cut -d/ -f2)
@@ -71,6 +72,24 @@ for p in json.load(sys.stdin):
tail -50 "/tmp/kimi-heartbeat.log" 2>/dev/null | grep -c "FAILED:" | xargs -I{} echo "Kimi recent failures: {}" >> "$state_dir/agent_status.txt"
tail -1 "/tmp/kimi-heartbeat.log" 2>/dev/null | xargs -I{} echo "Kimi last event: {}" >> "$state_dir/agent_status.txt"
# Scan worktrees for uncommitted work
for wt_dir in "$HOME/worktrees"/*/; do
[ -d "$wt_dir" ] || continue
[ -d "$wt_dir/.git" ] || continue
local dirty
dirty=$(cd "$wt_dir" && git status --porcelain 2>/dev/null | wc -l | tr -d " ")
if [ "${dirty:-0}" -gt 0 ]; then
local branch
branch=$(cd "$wt_dir" && git branch --show-current 2>/dev/null || echo "?")
local age=""
local last_commit
last_commit=$(cd "$wt_dir" && git log -1 --format=%ct 2>/dev/null || echo 0)
local now=$(date +%s)
local stale_mins=$(( (now - last_commit) / 60 ))
echo "DIR=$wt_dir BRANCH=$branch DIRTY=$dirty STALE=${stale_mins}m" >> "$state_dir/uncommitted_work.txt"
fi
done
echo "$state_dir"
}
@@ -81,6 +100,25 @@ run_triage() {
log "Cycle: $unassigned_count unassigned, $pr_count open PRs"
# Check for uncommitted work — nag if stale
local uncommitted_count
uncommitted_count=$(wc -l < "$state_dir/uncommitted_work.txt" 2>/dev/null | tr -d " " || echo 0)
if [ "${uncommitted_count:-0}" -gt 0 ]; then
log "WARNING: $uncommitted_count worktree(s) with uncommitted work"
while IFS= read -r line; do
log " UNCOMMITTED: $line"
# Auto-commit stale work (>60 min without commit)
local stale=$(echo "$line" | sed 's/.*STALE=\([0-9]*\)m.*/\1/')
local wt_dir=$(echo "$line" | sed 's/.*DIR=\([^ ]*\) .*/\1/')
if [ "${stale:-0}" -gt 60 ]; then
log " AUTO-COMMITTING stale work in $wt_dir (${stale}m stale)"
(cd "$wt_dir" && git add -A && git commit -m "WIP: orchestrator auto-commit — ${stale}m stale work
Preserved by timmy-orchestrator to prevent loss." 2>/dev/null && git push 2>/dev/null) && log " COMMITTED: $wt_dir" || log " COMMIT FAILED: $wt_dir"
fi
done < "$state_dir/uncommitted_work.txt"
fi
# If nothing to do, skip the LLM call
if [ "$unassigned_count" -eq 0 ] && [ "$pr_count" -eq 0 ]; then
log "Nothing to triage"
@@ -198,6 +236,12 @@ FOOTER
log "=== Timmy Orchestrator Started (PID $$) ==="
log "Cycle: ${CYCLE_INTERVAL}s | Auto-assign: ${AUTO_ASSIGN_UNASSIGNED} | Inference surface: Hermes CLI"
# Start auto-commit-guard daemon for work preservation
if ! pgrep -f "auto-commit-guard.sh" >/dev/null 2>&1; then
nohup bash "$SCRIPT_DIR/auto-commit-guard.sh" 120 >> "$LOG_DIR/auto-commit-guard.log" 2>&1 &
log "Started auto-commit-guard daemon (PID $!)"
fi
WORKFORCE_CYCLE=0
while true; do

View File

@@ -196,7 +196,37 @@
"paused_reason": null,
"skills": [],
"skill": null
},
{
"id": "tmux-supervisor-513",
"name": "Autonomous Cron Supervisor",
"prompt": "Load the tmux-supervisor skill and execute the monitoring protocol.\n\nCheck both `dev` and `timmy` tmux sessions for idle panes. Only send Telegram notifications on actionable events (idle, overflow, failure). Be silent when all agents are working.\n\nSteps:\n1. List all tmux sessions (skip 'Alexander')\n2. For each session, list windows and panes\n3. Capture each pane and classify state (idle vs active)\n4. For idle panes: read context, craft context-aware prompt\n5. Send /queue prompts to idle panes\n6. Verify prompts landed\n7. Only notify via Telegram if:\n - A pane was prompted (idle detected)\n - A pane shows context overflow (>80%)\n - A pane is stuck or crashed\n8. If all panes are active: respond with [SILENT]",
"schedule": {
"kind": "interval",
"minutes": 7,
"display": "every 7m"
},
"schedule_display": "every 7m",
"repeat": {
"times": null,
"completed": 0
},
"enabled": true,
"created_at": "2026-04-15T03:00:00.000000+00:00",
"next_run_at": null,
"last_run_at": null,
"last_status": null,
"last_error": null,
"deliver": "telegram",
"origin": null,
"state": "scheduled",
"paused_at": null,
"paused_reason": null,
"skills": [
"tmux-supervisor"
],
"skill": "tmux-supervisor"
}
],
"updated_at": "2026-04-13T02:00:00+00:00"
}
}

View File

@@ -0,0 +1,9 @@
- name: Nightly Pipeline Scheduler
schedule: '*/30 18-23,0-8 * * *' # Every 30 min, off-peak hours only
tasks:
- name: Check and start pipelines
shell: "bash scripts/nightly-pipeline-scheduler.sh"
env:
PIPELINE_TOKEN_LIMIT: "500000"
PIPELINE_PEAK_START: "9"
PIPELINE_PEAK_END: "18"

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.timmy.auto-commit-guard</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/apayne/.hermes/bin/auto-commit-guard.sh</string>
<string>120</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/apayne/.hermes/logs/auto-commit-guard.stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/apayne/.hermes/logs/auto-commit-guard.stderr.log</string>
<key>WorkingDirectory</key>
<string>/Users/apayne</string>
</dict>
</plist>

View File

@@ -0,0 +1,21 @@
# Gitea Accessibility Fix - R4: Time Elements
WCAG 1.3.1: Relative timestamps lack machine-readable fallbacks.
## Fix
Wrap relative timestamps in `<time datetime="...">` elements.
## Files
- `custom/templates/custom/time_relative.tmpl` - Reusable `<time>` helper
- `custom/templates/repo/list_a11y.tmpl` - Explore/Repos list override
## Deploy
```bash
cp -r custom/templates/* /path/to/gitea/custom/templates/
systemctl restart gitea
```
Closes #554

View File

@@ -0,0 +1,27 @@
{{/*
Gitea a11y fix: R4 <time> elements for relative timestamps
Deploy to: custom/templates/custom/time_relative.tmpl
*/}}
{{define "custom/time_relative"}}
{{if and .Time .Relative}}
<time datetime="{{.Time.Format "2006-01-02T15:04:05Z07:00"}}" title="{{.Time.Format "Jan 02, 2006 15:04"}}">
{{.Relative}}
</time>
{{else if .Relative}}
<span>{{.Relative}}</span>
{{end}}
{{end}}
{{define "custom/time_from_unix"}}
{{if .Relative}}
<time datetime="" data-unix="{{.Unix}}" title="">{{.Relative}}</time>
<script>
(function() {
var el = document.currentScript.previousElementSibling;
var unix = parseInt(el.getAttribute('data-unix'));
if (unix) { el.setAttribute('datetime', new Date(unix * 1000).toISOString()); el.setAttribute('title', new Date(unix * 1000).toLocaleString()); }
})();
</script>
{{end}}
{{end}}

View File

@@ -0,0 +1,27 @@
{{/*
Gitea a11y fix: R4 <time> elements for relative timestamps on repo list
Deploy to: custom/templates/repo/list_a11y.tmpl
*/}}
{{/* Star count link with aria-label */}}
<a class="repo-card-star" href="{{.RepoLink}}/stars" aria-label="{{.NumStars}} stars" title="{{.NumStars}} stars">
<svg class="octicon octicon-star" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
<path d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"/>
</svg>
<span>{{.NumStars}}</span>
</a>
{{/* Fork count link with aria-label */}}
<a class="repo-card-fork" href="{{.RepoLink}}/forks" aria-label="{{.NumForks}} forks" title="{{.NumForks}} forks">
<svg class="octicon octicon-repo-forked" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
<path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-.878a2.25 2.25 0 111.5 0v.878a2.25 2.25 0 01-2.25 2.25h-1.5v2.128a2.251 2.251 0 11-1.5 0V8.5h-1.5A2.25 2.25 0 013.5 6.25v-.878a2.25 2.25 0 111.5 0zM5 3.25a.75.75 0 10-1.5 0 .75.75 0 001.5 0zm6.75.75a.75.75 0 100-1.5.75.75 0 000 1.5zm-3 8.75a.75.75 0 10-1.5 0 .75.75 0 001.5 0z"/>
</svg>
<span>{{.NumForks}}</span>
</a>
{{/* Relative timestamp with <time> element for a11y */}}
{{if .UpdatedUnix}}
<time datetime="{{.UpdatedUnix | TimeSinceISO}}" title="{{.UpdatedUnix | DateFmtLong}}" class="text-light">
{{.UpdatedUnix | TimeSince}}
</time>
{{end}}

179
docs/glitch-detection.md Normal file
View File

@@ -0,0 +1,179 @@
# 3D World Glitch Detection — Matrix Scanner
**Reference:** timmy-config#491
**Label:** gemma-4-multimodal
**Version:** 0.1.0
## Overview
The Matrix Glitch Detector scans 3D web worlds for visual artifacts and
rendering anomalies. It uses browser automation to capture screenshots from
multiple camera angles, then sends them to a vision AI model for analysis
against a library of known glitch patterns.
## Detected Glitch Categories
| Category | Severity | Description |
|---|---|---|
| Floating Assets | HIGH | Objects not grounded — hovering above surfaces |
| Z-Fighting | MEDIUM | Coplanar surfaces flickering/competing for depth |
| Missing Textures | CRITICAL | Placeholder colors (magenta, checkerboard) |
| Clipping | HIGH | Geometry passing through other objects |
| Broken Normals | MEDIUM | Inside-out or incorrectly lit surfaces |
| Shadow Artifacts | LOW | Detached, mismatched, or acne shadows |
| LOD Popping | LOW | Abrupt level-of-detail transitions |
| Lightmap Errors | MEDIUM | Dark splotches, light leaks, baking failures |
| Water/Reflection | MEDIUM | Incorrect environment reflections |
| Skybox Seam | LOW | Visible seams at cubemap face edges |
## Installation
No external dependencies required — pure Python 3.10+.
```bash
# Clone the repo
git clone https://forge.alexanderwhitestone.com/Timmy_Foundation/timmy-config.git
cd timmy-config
```
## Usage
### Basic Scan
```bash
python bin/matrix_glitch_detector.py https://matrix.example.com/world/alpha
```
### Multi-Angle Scan
```bash
python bin/matrix_glitch_detector.py https://matrix.example.com/world/alpha \
--angles 8 \
--output glitch_report.json
```
### Demo Mode
```bash
python bin/matrix_glitch_detector.py --demo
```
### Options
| Flag | Default | Description |
|---|---|---|
| `url` | (required) | URL of the 3D world to scan |
| `--angles N` | 4 | Number of camera angles to capture |
| `--output PATH` | stdout | Output file for JSON report |
| `--min-severity` | info | Minimum severity: info/low/medium/high/critical |
| `--demo` | off | Run with simulated detections |
| `--verbose` | off | Enable verbose output |
## Report Format
The JSON report includes:
```json
{
"scan_id": "uuid",
"url": "https://...",
"timestamp": "ISO-8601",
"total_screenshots": 4,
"angles_captured": ["front", "right", "back", "left"],
"glitches": [
{
"id": "short-uuid",
"category": "floating_assets",
"name": "Floating Chair",
"description": "Office chair floating 0.3m above floor",
"severity": "high",
"confidence": 0.87,
"location_x": 35.2,
"location_y": 62.1,
"screenshot_index": 0,
"screenshot_angle": "front",
"timestamp": "ISO-8601"
}
],
"summary": {
"total_glitches": 4,
"by_severity": {"critical": 1, "high": 2, "medium": 1},
"by_category": {"floating_assets": 1, "missing_textures": 1, ...},
"highest_severity": "critical",
"clean_screenshots": 0
},
"metadata": {
"detector_version": "0.1.0",
"pattern_count": 10,
"reference": "timmy-config#491"
}
}
```
## Vision AI Integration
The detector supports any OpenAI-compatible vision API. Set these
environment variables:
```bash
export VISION_API_KEY="your-api-key"
export VISION_API_BASE="https://api.openai.com/v1" # optional
export VISION_MODEL="gpt-4o" # optional, default: gpt-4o
```
For browser-based capture with `browser_vision`:
```bash
export BROWSER_VISION_SCRIPT="/path/to/browser_vision.py"
```
## Glitch Patterns
Pattern definitions live in `bin/glitch_patterns.py`. Each pattern includes:
- **category** — Enum matching the glitch type
- **detection_prompts** — Instructions for the vision model
- **visual_indicators** — What to look for in screenshots
- **confidence_threshold** — Minimum confidence to report
### Adding Custom Patterns
```python
from glitch_patterns import GlitchPattern, GlitchCategory, GlitchSeverity
custom = GlitchPattern(
category=GlitchCategory.FLOATING_ASSETS,
name="Custom Glitch",
description="Your description",
severity=GlitchSeverity.MEDIUM,
detection_prompts=["Look for..."],
visual_indicators=["indicator 1", "indicator 2"],
)
```
## Testing
```bash
python -m pytest tests/test_glitch_detector.py -v
# or
python tests/test_glitch_detector.py
```
## Architecture
```
bin/
matrix_glitch_detector.py — Main CLI entry point
glitch_patterns.py — Pattern definitions and prompt builder
tests/
test_glitch_detector.py — Unit and integration tests
docs/
glitch-detection.md — This documentation
```
## Limitations
- Browser automation requires a headless browser environment
- Vision AI analysis depends on model availability and API limits
- Placeholder screenshots are generated when browser capture is unavailable
- Detection accuracy varies by scene complexity and lighting conditions

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import json
from hermes_tools import browser_navigate, browser_vision

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import json
from hermes_tools import browser_navigate, browser_vision

View File

@@ -1,12 +1,884 @@
#!/usr/bin/env python3
"""
foundation_accessibility_audit.py — Multimodal Visual Accessibility Audit.
Analyzes web pages for WCAG 2.1 AA compliance using both programmatic checks
and vision model analysis. Screenshots pages, checks contrast ratios, detects
layout issues, validates alt text, and produces structured audit reports.
Usage:
# Audit a single page
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org
# Audit multiple pages
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org --pages /about /donate /blog
# With vision model analysis (Gemma 3)
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org --vision
# Programmatic-only (no vision model needed)
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org --programmatic
# Output as text report
python scripts/foundation_accessibility_audit.py --url https://timmyfoundation.org --format text
WCAG 2.1 AA Checks:
1.4.3 Contrast (Minimum) — text vs background ratio >= 4.5:1
1.4.6 Contrast (Enhanced) — ratio >= 7:1 for AAA
1.4.11 Non-text Contrast — UI components >= 3:1
1.3.1 Info and Relationships — heading hierarchy, landmarks
1.1.1 Non-text Content — alt text on images
2.4.1 Bypass Blocks — skip navigation link
2.4.2 Page Titled — meaningful <title>
2.4.6 Headings and Labels — descriptive headings
4.1.2 Name, Role, Value — ARIA labels on interactive elements
Refs: timmy-config#492, WCAG 2.1 AA
"""
from __future__ import annotations
import argparse
import base64
import colorsys
import json
from hermes_tools import browser_navigate, browser_vision
import os
import re
import subprocess
import sys
import tempfile
import urllib.error
import urllib.request
from dataclasses import dataclass, field, asdict
from enum import Enum
from pathlib import Path
from typing import Optional
from html.parser import HTMLParser
def audit_accessibility():
browser_navigate(url="https://timmyfoundation.org")
analysis = browser_vision(
question="Perform an accessibility audit. Check for: 1) Color contrast, 2) Font legibility, 3) Missing alt text for images. Provide a report with FAIL/PASS."
# === Configuration ===
OLLAMA_BASE = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434")
VISION_MODEL = os.environ.get("VISUAL_REVIEW_MODEL", "gemma3:12b")
DEFAULT_PAGES = ["/", "/about", "/donate", "/blog", "/contact"]
class Severity(str, Enum):
CRITICAL = "critical" # Blocks access entirely
MAJOR = "major" # Significant barrier
MINOR = "minor" # Inconvenience
PASS = "pass"
@dataclass
class A11yViolation:
"""A single accessibility violation."""
criterion: str # WCAG criterion (e.g. "1.4.3")
criterion_name: str # Human-readable name
severity: Severity = Severity.MINOR
element: str = "" # CSS selector or element description
description: str = "" # What's wrong
fix: str = "" # Suggested fix
source: str = "" # "programmatic" or "vision"
@dataclass
class A11yPageResult:
"""Audit result for a single page."""
url: str = ""
title: str = ""
score: int = 100
violations: list[A11yViolation] = field(default_factory=list)
passed_checks: list[str] = field(default_factory=list)
summary: str = ""
@dataclass
class A11yAuditReport:
"""Complete audit report across all pages."""
site: str = ""
pages_audited: int = 0
overall_score: int = 100
total_violations: int = 0
critical_violations: int = 0
major_violations: int = 0
page_results: list[A11yPageResult] = field(default_factory=list)
summary: str = ""
# === HTML Parser for Programmatic Checks ===
class A11yHTMLParser(HTMLParser):
"""Extract accessibility-relevant elements from HTML."""
def __init__(self):
super().__init__()
self.title = ""
self.images = [] # [{"src": ..., "alt": ...}]
self.headings = [] # [{"level": int, "text": ...}]
self.links = [] # [{"text": ..., "href": ...}]
self.inputs = [] # [{"type": ..., "label": ..., "id": ...}]
self.landmarks = [] # [{"tag": ..., "role": ...}]
self.skip_nav = False
self.lang = ""
self.in_title = False
self.in_heading = False
self.heading_level = 0
self.heading_text = ""
self.current_text = ""
def handle_starttag(self, tag, attrs):
attr_dict = dict(attrs)
if tag == "title":
self.in_title = True
elif tag == "html":
self.lang = attr_dict.get("lang", "")
elif tag in ("h1", "h2", "h3", "h4", "h5", "h6"):
self.in_heading = True
self.heading_level = int(tag[1])
self.heading_text = ""
elif tag == "img":
self.images.append({
"src": attr_dict.get("src", ""),
"alt": attr_dict.get("alt"),
"role": attr_dict.get("role", ""),
})
elif tag == "a":
self.links.append({
"href": attr_dict.get("href", ""),
"text": "",
"aria_label": attr_dict.get("aria-label", ""),
})
elif tag in ("input", "select", "textarea"):
self.inputs.append({
"tag": tag,
"type": attr_dict.get("type", "text"),
"id": attr_dict.get("id", ""),
"aria_label": attr_dict.get("aria-label", ""),
"aria_labelledby": attr_dict.get("aria-labelledby", ""),
})
elif tag in ("main", "nav", "header", "footer", "aside", "section", "form"):
self.landmarks.append({"tag": tag, "role": attr_dict.get("role", "")})
elif tag == "a" and ("skip" in attr_dict.get("href", "").lower() or
"skip" in attr_dict.get("class", "").lower()):
self.skip_nav = True
role = attr_dict.get("role", "")
if role in ("navigation", "main", "banner", "contentinfo", "complementary", "search"):
self.landmarks.append({"tag": tag, "role": role})
if role == "link" and "skip" in (attr_dict.get("aria-label", "") + attr_dict.get("href", "")).lower():
self.skip_nav = True
def handle_endtag(self, tag):
if tag == "title":
self.in_title = False
elif tag in ("h1", "h2", "h3", "h4", "h5", "h6"):
self.headings.append({"level": self.heading_level, "text": self.heading_text.strip()})
self.in_heading = False
elif tag == "a" and self.links:
self.links[-1]["text"] = self.current_text.strip()
self.current_text = ""
def handle_data(self, data):
if self.in_title:
self.title += data
if self.in_heading:
self.heading_text += data
self.current_text += data
# === Color/Contrast Utilities ===
def parse_color(color_str: str) -> Optional[tuple]:
"""Parse CSS color string to (r, g, b) tuple (0-255)."""
if not color_str:
return None
color_str = color_str.strip().lower()
# Named colors (subset)
named = {
"white": (255, 255, 255), "black": (0, 0, 0),
"red": (255, 0, 0), "green": (0, 128, 0), "blue": (0, 0, 255),
"gray": (128, 128, 128), "grey": (128, 128, 128),
"silver": (192, 192, 192), "yellow": (255, 255, 0),
"orange": (255, 165, 0), "purple": (128, 0, 128),
"transparent": None,
}
if color_str in named:
return named[color_str]
# #RRGGBB or #RGB
if color_str.startswith("#"):
hex_str = color_str[1:]
if len(hex_str) == 3:
hex_str = "".join(c * 2 for c in hex_str)
if len(hex_str) == 6:
try:
return tuple(int(hex_str[i:i+2], 16) for i in (0, 2, 4))
except ValueError:
return None
# rgb(r, g, b)
match = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)", color_str)
if match:
return tuple(int(match.group(i)) for i in (1, 2, 3))
# rgba(r, g, b, a)
match = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)", color_str)
if match:
return tuple(int(match.group(i)) for i in (1, 2, 3))
return None
def relative_luminance(rgb: tuple) -> float:
"""Calculate relative luminance per WCAG 2.1 (sRGB)."""
def linearize(c):
c = c / 255.0
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
r, g, b = [linearize(c) for c in rgb]
return 0.2126 * r + 0.7152 * g + 0.0722 * b
def contrast_ratio(color1: tuple, color2: tuple) -> float:
"""Calculate contrast ratio between two colors per WCAG 2.1."""
l1 = relative_luminance(color1)
l2 = relative_luminance(color2)
lighter = max(l1, l2)
darker = min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
# === Programmatic Checks ===
def check_page_title(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 2.4.2 — Page Titled."""
violations = []
title = parser.title.strip()
if not title:
violations.append(A11yViolation(
criterion="2.4.2", criterion_name="Page Titled",
severity=Severity.MAJOR,
element="<title>",
description="Page has no title or title is empty.",
fix="Add a meaningful <title> that describes the page purpose.",
source="programmatic"
))
elif len(title) < 5:
violations.append(A11yViolation(
criterion="2.4.2", criterion_name="Page Titled",
severity=Severity.MINOR,
element=f"<title>{title}</title>",
description=f"Page title is very short: '{title}'",
fix="Use a more descriptive title.",
source="programmatic"
))
return violations
def check_lang_attribute(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 3.1.1 — Language of Page."""
violations = []
if not parser.lang:
violations.append(A11yViolation(
criterion="3.1.1", criterion_name="Language of Page",
severity=Severity.MAJOR,
element="<html>",
description="Missing lang attribute on <html> element.",
fix="Add lang=\"en\" (or appropriate language code) to <html>.",
source="programmatic"
))
return violations
def check_images_alt_text(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 1.1.1 — Non-text Content."""
violations = []
for img in parser.images:
if img.get("role") == "presentation" or img.get("role") == "none":
continue # Decorative images are exempt
alt = img.get("alt")
src = img.get("src", "unknown")
if alt is None:
violations.append(A11yViolation(
criterion="1.1.1", criterion_name="Non-text Content",
severity=Severity.CRITICAL,
element=f"<img src=\"{src[:80]}\">",
description="Image missing alt attribute.",
fix="Add descriptive alt text, or alt=\"\" with role=\"presentation\" for decorative images.",
source="programmatic"
))
elif alt.strip() == "":
# Empty alt is OK only for decorative images
if img.get("role") not in ("presentation", "none"):
violations.append(A11yViolation(
criterion="1.1.1", criterion_name="Non-text Content",
severity=Severity.MINOR,
element=f"<img src=\"{src[:80]}\" alt=\"\">",
description="Empty alt text — ensure this image is decorative.",
fix="If decorative, add role=\"presentation\". If meaningful, add descriptive alt text.",
source="programmatic"
))
return violations
def check_heading_hierarchy(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 1.3.1 — Info and Relationships (heading hierarchy)."""
violations = []
if not parser.headings:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MAJOR,
element="document",
description="No headings found on page.",
fix="Add proper heading hierarchy starting with <h1>.",
source="programmatic"
))
return violations
# Check for H1
h1s = [h for h in parser.headings if h["level"] == 1]
if not h1s:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MAJOR,
element="document",
description="No <h1> heading found.",
fix="Add a single <h1> as the main page heading.",
source="programmatic"
))
elif len(h1s) > 1:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MINOR,
element="document",
description=f"Multiple <h1> headings found ({len(h1s)}).",
fix="Use a single <h1> per page for the main heading.",
source="programmatic"
))
# Check hierarchy skips
prev_level = 0
for h in parser.headings:
level = h["level"]
if level > prev_level + 1 and prev_level > 0:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MINOR,
element=f"<h{level}>{h['text'][:50]}</h{level}>",
description=f"Heading level skipped: h{prev_level} → h{level}",
fix=f"Use <h{prev_level + 1}> instead, or fill the gap.",
source="programmatic"
))
prev_level = level
return violations
def check_landmarks(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 1.3.1 — Landmarks and structure."""
violations = []
roles = {lm.get("role", "") for lm in parser.landmarks}
tags = {lm.get("tag", "") for lm in parser.landmarks}
has_main = "main" in roles or "main" in tags
has_nav = "navigation" in roles or "nav" in tags
if not has_main:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MAJOR,
element="document",
description="No <main> landmark found.",
fix="Wrap the main content in a <main> element.",
source="programmatic"
))
if not has_nav:
violations.append(A11yViolation(
criterion="1.3.1", criterion_name="Info and Relationships",
severity=Severity.MINOR,
element="document",
description="No <nav> landmark found.",
fix="Wrap navigation in a <nav> element.",
source="programmatic"
))
return violations
def check_skip_nav(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 2.4.1 — Bypass Blocks."""
violations = []
if not parser.skip_nav:
# Also check links for "skip" text
has_skip_link = any("skip" in l.get("text", "").lower() for l in parser.links)
if not has_skip_link:
violations.append(A11yViolation(
criterion="2.4.1", criterion_name="Bypass Blocks",
severity=Severity.MAJOR,
element="document",
description="No skip navigation link found.",
fix="Add a 'Skip to main content' link as the first focusable element.",
source="programmatic"
))
return violations
def check_form_labels(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 4.1.2 — Name, Role, Value (form inputs)."""
violations = []
for inp in parser.inputs:
if inp["type"] in ("hidden", "submit", "button", "reset", "image"):
continue
has_label = bool(inp.get("aria_label") or inp.get("aria_labelledby") or inp.get("id"))
if not has_label:
violations.append(A11yViolation(
criterion="4.1.2", criterion_name="Name, Role, Value",
severity=Severity.MAJOR,
element=f"<{inp['tag']} type=\"{inp['type']}\">",
description="Form input has no associated label or aria-label.",
fix="Add a <label for=\"...\"> or aria-label attribute.",
source="programmatic"
))
return violations
def check_link_text(parser: A11yHTMLParser) -> list[A11yViolation]:
"""WCAG 2.4.4 — Link Purpose."""
violations = []
for link in parser.links:
text = (link.get("text", "") or link.get("aria_label", "")).strip().lower()
href = link.get("href", "")
if not text:
violations.append(A11yViolation(
criterion="2.4.4", criterion_name="Link Purpose",
severity=Severity.MAJOR,
element=f"<a href=\"{href[:60]}\">",
description="Link has no accessible text.",
fix="Add visible text content or aria-label to the link.",
source="programmatic"
))
elif text in ("click here", "read more", "here", "more", "link"):
violations.append(A11yViolation(
criterion="2.4.4", criterion_name="Link Purpose",
severity=Severity.MINOR,
element=f"<a href=\"{href[:60]}\">{text}</a>",
description=f"Non-descriptive link text: '{text}'",
fix="Use descriptive text that explains the link destination.",
source="programmatic"
))
return violations
def run_programmatic_checks(html: str) -> list[A11yViolation]:
"""Run all programmatic accessibility checks on HTML content."""
parser = A11yHTMLParser()
try:
parser.feed(html)
except Exception:
pass
violations = []
violations.extend(check_page_title(parser))
violations.extend(check_lang_attribute(parser))
violations.extend(check_images_alt_text(parser))
violations.extend(check_heading_hierarchy(parser))
violations.extend(check_landmarks(parser))
violations.extend(check_skip_nav(parser))
violations.extend(check_form_labels(parser))
violations.extend(check_link_text(parser))
return violations
# === Vision Model Checks ===
A11Y_VISION_PROMPT = """You are a WCAG 2.1 AA accessibility auditor. Analyze this screenshot of a web page.
Check for these specific issues:
1. COLOR CONTRAST: Are text colors sufficiently different from their backgrounds?
- Normal text needs 4.5:1 contrast ratio
- Large text (18pt+) needs 3:1
- UI components need 3:1
List any text or UI elements where contrast looks insufficient.
2. FONT LEGIBILITY: Is text readable?
- Font size >= 12px for body text
- Line height >= 1.5 for body text
- No text in images (should be real text)
3. LAYOUT ISSUES: Is the layout accessible?
- Touch targets >= 44x44px
- Content not cut off or overlapping
- Logical reading order visible
- No horizontal scrolling at standard widths
4. FOCUS INDICATORS: Can you see which element has focus?
- Interactive elements should have visible focus rings
5. COLOR ALONE: Is information conveyed only by color?
- Errors/warnings should not rely solely on red/green
Respond as JSON:
{
"violations": [
{
"criterion": "1.4.3",
"criterion_name": "Contrast (Minimum)",
"severity": "critical|major|minor",
"element": "description of element",
"description": "what's wrong",
"fix": "how to fix"
}
],
"passed_checks": ["list of things that look good"],
"overall_score": 0-100,
"summary": "brief summary"
}"""
def run_vision_check(screenshot_path: str, model: str = VISION_MODEL) -> list[A11yViolation]:
"""Run vision model accessibility check on a screenshot."""
try:
b64 = base64.b64encode(Path(screenshot_path).read_bytes()).decode()
payload = json.dumps({
"model": model,
"messages": [{"role": "user", "content": [
{"type": "text", "text": A11Y_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", "")
# Parse response
parsed = _parse_json_response(content)
violations = []
for v in parsed.get("violations", []):
violations.append(A11yViolation(
criterion=v.get("criterion", ""),
criterion_name=v.get("criterion_name", ""),
severity=Severity(v.get("severity", "minor")),
element=v.get("element", ""),
description=v.get("description", ""),
fix=v.get("fix", ""),
source="vision"
))
return violations
except Exception as e:
print(f" Vision check failed: {e}", file=sys.stderr)
return []
def _parse_json_response(text: str) -> dict:
"""Extract JSON from potentially messy vision response."""
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 {}
# === Page Fetching ===
def fetch_page(url: str) -> Optional[str]:
"""Fetch HTML content of a page."""
try:
req = urllib.request.Request(url, headers={"User-Agent": "A11yAudit/1.0"})
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read().decode("utf-8", errors="replace")
except Exception as e:
print(f" Failed to fetch {url}: {e}", file=sys.stderr)
return None
def take_screenshot(url: str, output_path: str, width: int = 1280, height: int = 900) -> bool:
"""Take a screenshot using Playwright or curl-based headless capture."""
# Try Playwright first
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": {width}, "height": {height}}})
page.goto("{url}", wait_until="networkidle", timeout=30000)
page.screenshot(path="{output_path}", full_page=True)
browser.close()
"""
result = subprocess.run(
["python3", "-c", script],
capture_output=True, text=True, timeout=60
)
if result.returncode == 0 and Path(output_path).exists():
return True
except Exception:
pass
# Try curl + wkhtmltoimage
try:
result = subprocess.run(
["wkhtmltoimage", "--width", str(width), "--quality", "90", url, output_path],
capture_output=True, text=True, timeout=30
)
if result.returncode == 0 and Path(output_path).exists():
return True
except Exception:
pass
return False
# === Audit Logic ===
def audit_page(url: str, use_vision: bool = False, model: str = VISION_MODEL) -> A11yPageResult:
"""Run a full accessibility audit on a single page."""
result = A11yPageResult(url=url)
# Fetch HTML
html = fetch_page(url)
if not html:
result.summary = f"Failed to fetch {url}"
result.score = 0
return result
# Extract title
title_match = re.search(r"<title[^>]*>(.*?)</title>", html, re.IGNORECASE | re.DOTALL)
result.title = title_match.group(1).strip() if title_match else ""
# Run programmatic checks
prog_violations = run_programmatic_checks(html)
result.violations.extend(prog_violations)
# Track passed checks
criteria_checked = {
"2.4.2": "Page Titled",
"3.1.1": "Language of Page",
"1.1.1": "Non-text Content",
"1.3.1": "Info and Relationships",
"2.4.1": "Bypass Blocks",
"4.1.2": "Name, Role, Value",
"2.4.4": "Link Purpose",
}
violated_criteria = {v.criterion for v in result.violations}
for criterion, name in criteria_checked.items():
if criterion not in violated_criteria:
result.passed_checks.append(f"{criterion} {name}")
# Vision check (optional)
if use_vision:
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
screenshot_path = tmp.name
try:
print(f" Taking screenshot of {url}...", file=sys.stderr)
if take_screenshot(url, screenshot_path):
print(f" Running vision analysis...", file=sys.stderr)
vision_violations = run_vision_check(screenshot_path, model)
result.violations.extend(vision_violations)
result.passed_checks.append("Vision model analysis completed")
else:
result.passed_checks.append("Screenshot unavailable — vision check skipped")
finally:
Path(screenshot_path).unlink(missing_ok=True)
# Calculate score
criticals = sum(1 for v in result.violations if v.severity == Severity.CRITICAL)
majors = sum(1 for v in result.violations if v.severity == Severity.MAJOR)
minors = sum(1 for v in result.violations if v.severity == Severity.MINOR)
result.score = max(0, 100 - (criticals * 25) - (majors * 10) - (minors * 3))
# Summary
if not result.violations:
result.summary = f"All programmatic checks passed for {url}"
else:
result.summary = (
f"{len(result.violations)} issue(s) found: "
f"{criticals} critical, {majors} major, {minors} minor"
)
return result
def audit_site(base_url: str, pages: list[str], use_vision: bool = False,
model: str = VISION_MODEL) -> A11yAuditReport:
"""Audit multiple pages of a site."""
report = A11yAuditReport(site=base_url)
for path in pages:
url = base_url.rstrip("/") + path if not path.startswith("http") else path
print(f"Auditing: {url}", file=sys.stderr)
result = audit_page(url, use_vision, model)
report.page_results.append(result)
report.pages_audited = len(report.page_results)
report.total_violations = sum(len(p.violations) for p in report.page_results)
report.critical_violations = sum(
sum(1 for v in p.violations if v.severity == Severity.CRITICAL)
for p in report.page_results
)
report.major_violations = sum(
sum(1 for v in p.violations if v.severity == Severity.MAJOR)
for p in report.page_results
)
return {"status": "PASS" if "PASS" in analysis.upper() else "FAIL", "analysis": analysis}
if __name__ == '__main__':
print(json.dumps(audit_accessibility(), indent=2))
if report.page_results:
report.overall_score = sum(p.score for p in report.page_results) // len(report.page_results)
report.summary = (
f"Audited {report.pages_audited} pages. "
f"Overall score: {report.overall_score}/100. "
f"{report.total_violations} total issues: "
f"{report.critical_violations} critical, {report.major_violations} major."
)
return report
# === Output Formatting ===
def format_report(report: A11yAuditReport, fmt: str = "json") -> str:
"""Format the audit report."""
if fmt == "json":
data = {
"site": report.site,
"pages_audited": report.pages_audited,
"overall_score": report.overall_score,
"total_violations": report.total_violations,
"critical_violations": report.critical_violations,
"major_violations": report.major_violations,
"summary": report.summary,
"pages": []
}
for page in report.page_results:
page_data = {
"url": page.url,
"title": page.title,
"score": page.score,
"violations": [asdict(v) for v in page.violations],
"passed_checks": page.passed_checks,
"summary": page.summary,
}
# Convert severity enum to string
for v in page_data["violations"]:
if hasattr(v["severity"], "value"):
v["severity"] = v["severity"].value
data["pages"].append(page_data)
return json.dumps(data, indent=2)
elif fmt == "text":
lines = []
lines.append("=" * 60)
lines.append(" WEB ACCESSIBILITY AUDIT REPORT")
lines.append("=" * 60)
lines.append(f" Site: {report.site}")
lines.append(f" Pages audited: {report.pages_audited}")
lines.append(f" Overall score: {report.overall_score}/100")
lines.append(f" Issues: {report.total_violations} total "
f"({report.critical_violations} critical, {report.major_violations} major)")
lines.append("")
for page in report.page_results:
lines.append(f" ── {page.url} ──")
lines.append(f" Title: {page.title}")
lines.append(f" Score: {page.score}/100")
lines.append("")
if page.violations:
lines.append(f" Violations ({len(page.violations)}):")
for v in page.violations:
sev_icon = {"critical": "🔴", "major": "🟡", "minor": "🔵"}.get(
v.severity.value if hasattr(v.severity, "value") else str(v.severity), ""
)
lines.append(f" {sev_icon} [{v.criterion}] {v.criterion_name}")
lines.append(f" Element: {v.element}")
lines.append(f" Issue: {v.description}")
lines.append(f" Fix: {v.fix}")
lines.append(f" Source: {v.source}")
lines.append("")
else:
lines.append(" ✓ No violations found")
lines.append("")
if page.passed_checks:
lines.append(f" Passed: {', '.join(page.passed_checks)}")
lines.append("")
lines.append("=" * 60)
lines.append(f" Summary: {report.summary}")
lines.append("=" * 60)
return "\n".join(lines)
else:
raise ValueError(f"Unknown format: {fmt}")
# === CLI ===
def main():
parser = argparse.ArgumentParser(
description="Visual Accessibility Audit — WCAG 2.1 AA compliance checker",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --url https://timmyfoundation.org
%(prog)s --url https://timmyfoundation.org --pages /about /donate
%(prog)s --url https://timmyfoundation.org --vision
%(prog)s --url https://timmyfoundation.org --format text
"""
)
parser.add_argument("--url", required=True, help="Base URL to audit")
parser.add_argument("--pages", nargs="*", default=DEFAULT_PAGES,
help="Paths to audit (default: / /about /donate /blog /contact)")
parser.add_argument("--vision", action="store_true",
help="Include vision model analysis (requires Ollama)")
parser.add_argument("--model", default=VISION_MODEL,
help=f"Vision model (default: {VISION_MODEL})")
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()
report = audit_site(args.url, args.pages, use_vision=args.vision, model=args.model)
output = format_report(report, args.format)
if args.output:
Path(args.output).write_text(output)
print(f"Report written to {args.output}", file=sys.stderr)
else:
print(output)
# Exit code: non-zero if critical violations
if report.critical_violations > 0:
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python3
import json, os
songs = [
{"t":"Thunder Road","a":"Heartland","m":["hope","anticipation","energy","triumph","nostalgia","urgency","passion","defiance","release","catharsis"]},
{"t":"Black Dog Howl","a":"Rust & Wire","m":["despair","anger","frenzy","exhaustion","resignation","grief","numbness","rage","acceptance","silence"]},
{"t":"Satellite Hearts","a":"Neon Circuit","m":["wonder","isolation","longing","connection","euphoria","confusion","clarity","tenderness","urgency","bittersweet"]},
{"t":"Concrete Garden","a":"Streetlight Prophet","m":["oppression","resilience","anger","beauty","defiance","community","joy","struggle","growth","hope"]},
{"t":"Gravity Well","a":"Void Walker","m":["dread","fascination","surrender","awe","terror","peace","disorientation","acceptance","transcendence","emptiness"]},
{"t":"Rust Belt Lullaby","a":"Iron & Ember","m":["nostalgia","sadness","tenderness","loss","beauty","resignation","love","weariness","quiet hope","peace"]},
{"t":"Wildfire Sermon","a":"Prophet Ash","m":["fury","ecstasy","chaos","joy","destruction","creation","warning","invitation","abandon","rebirth"]},
{"t":"Midnight Transmission","a":"Frequency Ghost","m":["mystery","loneliness","curiosity","connection","paranoia","intimacy","urgency","disconnection","searching","haunting"]},
{"t":"Crown of Thorns","a":"Velvet Guillotine","m":["seduction","power","cruelty","beauty","danger","vulnerability","fury","grace","revenge","mercy"]},
{"t":"Apartment 4B","a":"Wallpaper & Wire","m":["claustrophobia","routine","desperation","fantasy","breakthrough","freedom","fear","joy","grounding","home"]},
]
beats = []
for s in songs:
for i in range(10):
beats.append({"song": s["t"], "artist": s["a"], "beat": i+1,
"timestamp": f"{i*30//60}:{(i*30)%60:02d}", "duration": "30s",
"lyric_line": f"[Beat {i+1}]", "scene": {"mood": s["m"][i], "colors": ["placeholder"],
"composition": ["wide","close","OTS","low","high","dutch","symmetric","thirds","xwide","medium"][i],
"camera": ["static","pan","dolly-in","dolly-out","handheld","steadicam","zoom","crane","track","tilt"][i],
"description": f"[{s['m'][i]} scene]"}})
out = os.path.expanduser("~/.hermes/training-data/scene-descriptions-rock.jsonl")
os.makedirs(os.path.dirname(out), exist_ok=True)
with open(out, "w") as f:
for b in beats:
f.write(json.dumps(b) + "\n")
print(f"Generated {len(beats)} beats")

View File

@@ -1,12 +1,599 @@
#!/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
from hermes_tools import browser_navigate, browser_vision
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
def detect_glitches():
browser_navigate(url="https://matrix.alexanderwhitestone.com")
analysis = browser_vision(
question="Scan the 3D world for visual artifacts, floating assets, or z-fighting. List all coordinates/descriptions of glitches found. Provide a PASS/FAIL."
# === 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"
)
return {"status": "PASS" if "PASS" in analysis.upper() else "FAIL", "analysis": analysis}
if __name__ == '__main__':
print(json.dumps(detect_glitches(), indent=2))
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:1000px;margin:0 auto;padding:20px}}
header{{text-align:center;padding:24px 0;border-bottom:1px solid #1a1a2e;margin-bottom:24px}}
header h1{{font-size:20px;font-weight:300;letter-spacing:3px;color:#4a9eff;margin-bottom:8px}}
.verdict{{display:inline-block;padding:6px 20px;border-radius:4px;font-size:14px;font-weight:700;letter-spacing:2px;color:#fff;background:{verdict_color}}}
.stats{{display:flex;gap:16px;justify-content:center;margin:16px 0;flex-wrap:wrap}}
.stat{{background:#0e0e1a;border:1px solid #1a1a2e;border-radius:4px;padding:8px 16px;text-align:center}}
.stat .val{{font-size:20px;font-weight:700;color:#4a9eff}}
.stat .lbl{{font-size:9px;color:#666;text-transform:uppercase;letter-spacing:1px}}
.score-gauge{{width:120px;height:120px;margin:0 auto 16px;position:relative}}
.score-gauge svg{{transform:rotate(-90deg)}}
.score-gauge .score-text{{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:28px;font-weight:700}}
.report-card{{background:#0e0e1a;border:1px solid #1a1a2e;border-radius:6px;margin-bottom:16px;overflow:hidden}}
.report-header{{padding:12px 16px;border-bottom:1px 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:2px 10px;border-radius:3px;font-size:11px;font-weight:700;color:#fff}}
.status-pass{{background:#4caf50}}
.status-warn{{background:#ff9800}}
.status-fail{{background:#f44336}}
.screenshot{{text-align:center;padding:12px;background:#080810}}
.screenshot img{{max-width:100%;max-height:400px;border:1px solid #1a1a2e;border-radius:4px}}
.glitch-list{{padding:12px 16px}}
.glitch-item{{padding:8px 0;border-bottom:1px solid #111;display:flex;gap:12px;align-items:flex-start}}
.glitch-item:last-child{{border-bottom:none}}
.severity-dot{{width:8px;height:8px;border-radius:50%;margin-top:5px;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:16px;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"
)
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()

View File

@@ -1,20 +1,582 @@
import json
from hermes_tools import browser_navigate, browser_vision
#!/usr/bin/env python3
"""
nexus_smoke_test.py — Visual Smoke Test for The Nexus.
def run_smoke_test():
print("Navigating to The Nexus...")
browser_navigate(url="https://nexus.alexanderwhitestone.com")
print("Performing visual verification...")
analysis = browser_vision(
question="Is the Nexus landing page rendered correctly? Check for: 1) The Tower logo, 2) The main entry portal, 3) Absence of 404/Error messages. Provide a clear PASS or FAIL."
Takes screenshots of The Nexus landing page, verifies layout consistency
using both programmatic checks (DOM structure, element presence) and
optional vision model analysis (visual regression detection).
The Nexus is the Three.js 3D world frontend at nexus.alexanderwhitestone.com.
This test ensures the landing page renders correctly on every push.
Usage:
# Full smoke test (programmatic + optional vision)
python scripts/nexus_smoke_test.py
# Programmatic only (no vision model needed, CI-safe)
python scripts/nexus_smoke_test.py --programmatic
# With vision model regression check
python scripts/nexus_smoke_test.py --vision
# Against a specific URL
python scripts/nexus_smoke_test.py --url https://nexus.alexanderwhitestone.com
# With baseline comparison
python scripts/nexus_smoke_test.py --baseline screenshots/nexus-baseline.png
Checks:
1. Page loads without errors (HTTP 200, no console errors)
2. Key elements present (Three.js canvas, title, navigation)
3. No 404/error messages visible
4. JavaScript bundle loaded (window.__nexus or scene exists)
5. Screenshot captured successfully
6. Vision model layout verification (optional)
7. Baseline comparison for visual regression (optional)
Refs: timmy-config#490
"""
from __future__ import annotations
import argparse
import base64
import json
import os
import re
import subprocess
import sys
import tempfile
import urllib.error
import urllib.request
from dataclasses import dataclass, field, asdict
from enum import Enum
from pathlib import Path
from typing import Optional
# === Configuration ===
DEFAULT_URL = os.environ.get("NEXUS_URL", "https://nexus.alexanderwhitestone.com")
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):
PASS = "pass"
WARN = "warn"
FAIL = "fail"
@dataclass
class SmokeCheck:
"""A single smoke test check."""
name: str
status: Severity = Severity.PASS
message: str = ""
details: str = ""
@dataclass
class SmokeResult:
"""Complete smoke test result."""
url: str = ""
status: Severity = Severity.PASS
checks: list[SmokeCheck] = field(default_factory=list)
screenshot_path: str = ""
summary: str = ""
duration_ms: int = 0
# === HTTP/Network Checks ===
def check_page_loads(url: str) -> SmokeCheck:
"""Verify the page returns HTTP 200."""
check = SmokeCheck(name="Page Loads")
try:
req = urllib.request.Request(url, headers={"User-Agent": "NexusSmokeTest/1.0"})
with urllib.request.urlopen(req, timeout=15) as resp:
if resp.status == 200:
check.status = Severity.PASS
check.message = f"HTTP {resp.status}"
else:
check.status = Severity.WARN
check.message = f"HTTP {resp.status} (expected 200)"
except urllib.error.HTTPError as e:
check.status = Severity.FAIL
check.message = f"HTTP {e.code}: {e.reason}"
except Exception as e:
check.status = Severity.FAIL
check.message = f"Connection failed: {e}"
return check
def check_html_content(url: str) -> tuple[SmokeCheck, str]:
"""Fetch HTML and check for key content."""
check = SmokeCheck(name="HTML Content")
html = ""
try:
req = urllib.request.Request(url, headers={"User-Agent": "NexusSmokeTest/1.0"})
with urllib.request.urlopen(req, timeout=15) as resp:
html = resp.read().decode("utf-8", errors="replace")
except Exception as e:
check.status = Severity.FAIL
check.message = f"Failed to fetch: {e}"
return check, html
issues = []
# Check for Three.js
if "three" not in html.lower() and "THREE" not in html and "threejs" not in html.lower():
issues.append("No Three.js reference found")
# Check for canvas element
if "<canvas" not in html.lower():
issues.append("No <canvas> element found")
# Check title
title_match = re.search(r"<title[^>]*>(.*?)</title>", html, re.IGNORECASE | re.DOTALL)
if title_match:
title = title_match.group(1).strip()
check.details = f"Title: {title}"
if "nexus" not in title.lower() and "tower" not in title.lower():
issues.append(f"Title doesn't reference Nexus: '{title}'")
else:
issues.append("No <title> element")
# Check for error messages
error_patterns = ["404", "not found", "error", "500 internal", "connection refused"]
html_lower = html.lower()
for pattern in error_patterns:
if pattern in html_lower[:500] or pattern in html_lower[-500:]:
issues.append(f"Possible error message in HTML: '{pattern}'")
# Check for script tags (app loaded)
script_count = html.lower().count("<script")
if script_count == 0:
issues.append("No <script> tags found")
else:
check.details += f" | Scripts: {script_count}"
if issues:
check.status = Severity.FAIL if len(issues) > 2 else Severity.WARN
check.message = "; ".join(issues)
else:
check.status = Severity.PASS
check.message = "HTML structure looks correct"
return check, html
# === Screenshot Capture ===
def take_screenshot(url: str, output_path: str, width: int = 1280, height: int = 720) -> SmokeCheck:
"""Take a screenshot of the page."""
check = SmokeCheck(name="Screenshot Capture")
# Try Playwright
try:
script = f"""
import sys
try:
from playwright.sync_api import sync_playwright
except ImportError:
sys.exit(2)
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={{"width": {width}, "height": {height}}})
errors = []
page.on("pageerror", lambda e: errors.append(str(e)))
page.on("console", lambda m: errors.append(f"console.{{m.type}}: {{m.text}}") if m.type == "error" else None)
page.goto("{url}", wait_until="networkidle", timeout=30000)
page.wait_for_timeout(3000) # Wait for Three.js to render
page.screenshot(path="{output_path}", full_page=False)
# Check for Three.js scene
has_canvas = page.evaluate("() => !!document.querySelector('canvas')")
has_three = page.evaluate("() => typeof THREE !== 'undefined' || !!document.querySelector('canvas')")
title = page.title()
browser.close()
import json
print(json.dumps({{"has_canvas": has_canvas, "has_three": has_three, "title": title, "errors": errors[:5]}}))
"""
result = subprocess.run(
["python3", "-c", script],
capture_output=True, text=True, timeout=60
)
if result.returncode == 0:
# Parse Playwright output
try:
# Find JSON in output
for line in result.stdout.strip().split("\n"):
if line.startswith("{"):
info = json.loads(line)
extras = []
if info.get("has_canvas"):
extras.append("canvas present")
if info.get("errors"):
extras.append(f"{len(info['errors'])} JS errors")
check.details = "; ".join(extras) if extras else "Playwright capture"
if info.get("errors"):
check.status = Severity.WARN
check.message = f"JS errors detected: {info['errors'][0][:100]}"
else:
check.message = "Screenshot captured via Playwright"
break
except json.JSONDecodeError:
pass
if Path(output_path).exists() and Path(output_path).stat().st_size > 1000:
return check
elif result.returncode == 2:
check.details = "Playwright not installed"
else:
check.details = f"Playwright failed: {result.stderr[:200]}"
except Exception as e:
check.details = f"Playwright error: {e}"
# Try wkhtmltoimage
try:
result = subprocess.run(
["wkhtmltoimage", "--width", str(width), "--quality", "90", url, output_path],
capture_output=True, text=True, timeout=30
)
if result.returncode == 0 and Path(output_path).exists() and Path(output_path).stat().st_size > 1000:
check.status = Severity.PASS
check.message = "Screenshot captured via wkhtmltoimage"
check.details = ""
return check
except Exception:
pass
# Try curl + browserless (if available)
browserless = os.environ.get("BROWSERLESS_URL")
if browserless:
try:
payload = json.dumps({
"url": url,
"options": {"type": "png", "fullPage": False}
})
req = urllib.request.Request(
f"{browserless}/screenshot",
data=payload.encode(),
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=30) as resp:
img_data = resp.read()
Path(output_path).write_bytes(img_data)
if Path(output_path).stat().st_size > 1000:
check.status = Severity.PASS
check.message = "Screenshot captured via browserless"
check.details = ""
return check
except Exception:
pass
check.status = Severity.WARN
check.message = "No screenshot backend available"
check.details = "Install Playwright: pip install playwright && playwright install chromium"
return check
# === Vision Analysis ===
VISION_PROMPT = """You are a web QA engineer. Analyze this screenshot of The Nexus (a Three.js 3D world).
Check for:
1. LAYOUT: Is the page layout correct? Is content centered, not broken or overlapping?
2. THREE.JS RENDER: Is there a visible 3D canvas/scene? Any black/blank areas where rendering failed?
3. NAVIGATION: Are navigation elements (buttons, links, menu) visible and properly placed?
4. TEXT: Is text readable? Any missing text, garbled characters, or font issues?
5. ERRORS: Any visible error messages, 404 pages, or broken images?
6. TOWER: Is the Tower or entry portal visible in the scene?
Respond as JSON:
{
"status": "PASS|FAIL|WARN",
"checks": [
{"name": "Layout", "status": "pass|fail|warn", "message": "..."},
{"name": "Three.js Render", "status": "pass|fail|warn", "message": "..."},
{"name": "Navigation", "status": "pass|fail|warn", "message": "..."},
{"name": "Text Readability", "status": "pass|fail|warn", "message": "..."},
{"name": "Error Messages", "status": "pass|fail|warn", "message": "..."}
],
"summary": "brief overall assessment"
}"""
def run_vision_check(screenshot_path: str, model: str = VISION_MODEL) -> list[SmokeCheck]:
"""Run vision model analysis on screenshot."""
checks = []
try:
b64 = base64.b64encode(Path(screenshot_path).read_bytes()).decode()
payload = json.dumps({
"model": model,
"messages": [{"role": "user", "content": [
{"type": "text", "text": 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)
for c in parsed.get("checks", []):
status = Severity(c.get("status", "warn"))
checks.append(SmokeCheck(
name=f"Vision: {c.get('name', 'Unknown')}",
status=status,
message=c.get("message", "")
))
if not checks:
checks.append(SmokeCheck(
name="Vision Analysis",
status=Severity.WARN,
message="Vision model returned no structured checks"
))
except Exception as e:
checks.append(SmokeCheck(
name="Vision Analysis",
status=Severity.WARN,
message=f"Vision check failed: {e}"
))
return checks
# === Baseline Comparison ===
def compare_baseline(current_path: str, baseline_path: str) -> SmokeCheck:
"""Compare screenshot against baseline for visual regression."""
check = SmokeCheck(name="Baseline Comparison")
if not Path(baseline_path).exists():
check.status = Severity.WARN
check.message = f"Baseline not found: {baseline_path}"
return check
if not Path(current_path).exists():
check.status = Severity.FAIL
check.message = "No current screenshot to compare"
return check
# Simple file size comparison (rough regression indicator)
baseline_size = Path(baseline_path).stat().st_size
current_size = Path(current_path).stat().st_size
if baseline_size == 0:
check.status = Severity.WARN
check.message = "Baseline is empty"
return check
diff_pct = abs(current_size - baseline_size) / baseline_size * 100
if diff_pct > 50:
check.status = Severity.FAIL
check.message = f"Major visual change: {diff_pct:.0f}% file size difference"
elif diff_pct > 20:
check.status = Severity.WARN
check.message = f"Significant visual change: {diff_pct:.0f}% file size difference"
else:
check.status = Severity.PASS
check.message = f"Visual consistency: {diff_pct:.1f}% difference"
check.details = f"Baseline: {baseline_size}B, Current: {current_size}B"
# Pixel-level diff using ImageMagick (if available)
try:
diff_output = current_path.replace(".png", "-diff.png")
result = subprocess.run(
["compare", "-metric", "AE", current_path, baseline_path, diff_output],
capture_output=True, text=True, timeout=15
)
if result.returncode < 2:
pixels_diff = int(result.stderr) if result.stderr.strip().isdigit() else 0
check.details += f" | Pixel diff: {pixels_diff}"
if pixels_diff > 10000:
check.status = Severity.FAIL
check.message = f"Major visual regression: {pixels_diff} pixels changed"
elif pixels_diff > 1000:
check.status = Severity.WARN
check.message = f"Visual change detected: {pixels_diff} pixels changed"
except Exception:
pass
return check
# === Helpers ===
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 {}
# === Main Smoke Test ===
def run_smoke_test(url: str, vision: bool = False, baseline: Optional[str] = None,
model: str = VISION_MODEL) -> SmokeResult:
"""Run the full visual smoke test suite."""
import time
start = time.time()
result = SmokeResult(url=url)
screenshot_path = ""
# 1. Page loads
print(f" [1/5] Checking page loads...", file=sys.stderr)
result.checks.append(check_page_loads(url))
# 2. HTML content
print(f" [2/5] Checking HTML content...", file=sys.stderr)
html_check, html = check_html_content(url)
result.checks.append(html_check)
# 3. Screenshot
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
screenshot_path = tmp.name
print(f" [3/5] Taking screenshot...", file=sys.stderr)
screenshot_check = take_screenshot(url, screenshot_path)
result.checks.append(screenshot_check)
result.screenshot_path = screenshot_path
# 4. Vision analysis (optional)
if vision and Path(screenshot_path).exists():
print(f" [4/5] Running vision analysis...", file=sys.stderr)
result.checks.extend(run_vision_check(screenshot_path, model))
else:
print(f" [4/5] Vision analysis skipped", file=sys.stderr)
# 5. Baseline comparison (optional)
if baseline:
print(f" [5/5] Comparing against baseline...", file=sys.stderr)
result.checks.append(compare_baseline(screenshot_path, baseline))
else:
print(f" [5/5] Baseline comparison skipped", file=sys.stderr)
# Determine overall status
fails = sum(1 for c in result.checks if c.status == Severity.FAIL)
warns = sum(1 for c in result.checks if c.status == Severity.WARN)
if fails > 0:
result.status = Severity.FAIL
elif warns > 0:
result.status = Severity.WARN
else:
result.status = Severity.PASS
result.summary = (
f"{result.status.value.upper()}: {len(result.checks)} checks, "
f"{fails} failures, {warns} warnings"
)
result = {
"status": "PASS" if "PASS" in analysis.upper() else "FAIL",
"analysis": analysis
}
result.duration_ms = int((time.time() - start) * 1000)
return result
if __name__ == '__main__':
print(json.dumps(run_smoke_test(), indent=2))
# === Output ===
def format_result(result: SmokeResult, fmt: str = "json") -> str:
if fmt == "json":
data = {
"url": result.url,
"status": result.status.value,
"summary": result.summary,
"duration_ms": result.duration_ms,
"screenshot": result.screenshot_path,
"checks": [asdict(c) for c in result.checks],
}
for c in data["checks"]:
if hasattr(c["status"], "value"):
c["status"] = c["status"].value
return json.dumps(data, indent=2)
elif fmt == "text":
lines = [
"=" * 50,
" NEXUS VISUAL SMOKE TEST",
"=" * 50,
f" URL: {result.url}",
f" Status: {result.status.value.upper()}",
f" Duration: {result.duration_ms}ms",
"",
]
icons = {"pass": "", "warn": "⚠️", "fail": ""}
for c in result.checks:
icon = icons.get(c.status.value if hasattr(c.status, "value") else str(c.status), "?")
lines.append(f" {icon} {c.name}: {c.message}")
if c.details:
lines.append(f" {c.details}")
lines.append("")
lines.append(f" {result.summary}")
lines.append("=" * 50)
return "\n".join(lines)
return ""
# === CLI ===
def main():
parser = argparse.ArgumentParser(
description="Visual Smoke Test for The Nexus — layout + regression verification"
)
parser.add_argument("--url", default=DEFAULT_URL, help=f"Nexus URL (default: {DEFAULT_URL})")
parser.add_argument("--vision", action="store_true", help="Include vision model analysis")
parser.add_argument("--baseline", help="Baseline screenshot for regression comparison")
parser.add_argument("--model", default=VISION_MODEL, help=f"Vision model (default: {VISION_MODEL})")
parser.add_argument("--format", choices=["json", "text"], default="json")
parser.add_argument("--output", "-o", help="Output file (default: stdout)")
args = parser.parse_args()
print(f"Running smoke test on {args.url}...", file=sys.stderr)
result = run_smoke_test(args.url, vision=args.vision, baseline=args.baseline, model=args.model)
output = format_result(result, args.format)
if args.output:
Path(args.output).write_text(output)
print(f"Results written to {args.output}", file=sys.stderr)
else:
print(output)
if result.status == Severity.FAIL:
sys.exit(1)
elif result.status == Severity.WARN:
sys.exit(0) # Warnings don't fail CI
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,50 @@
# Nightly Pipeline Scheduler
Auto-starts batch pipelines when inference is available.
## What It Does
1. Checks inference provider health (OpenRouter, Ollama, RunPod)
2. Checks if it's off-peak hours (configurable, default: after 6PM)
3. Checks interactive session load (don't fight with live users)
4. Checks daily token budget (configurable limit)
5. Starts the highest-priority incomplete pipeline
## Pipeline Priority Order
| Priority | Pipeline | Deps | Max Tokens |
|----------|----------|------|------------|
| 1 | playground-factory | none | 100,000 |
| 2 | training-factory | none | 150,000 |
| 3 | knowledge-mine | training-factory running | 80,000 |
| 4 | adversary | knowledge-mine running | 50,000 |
| 5 | codebase-genome | none | 120,000 |
## Usage
```bash
# Normal run (used by cron)
./scripts/nightly-pipeline-scheduler.sh
# Dry run (show what would start)
./scripts/nightly-pipeline-scheduler.sh --dry-run
# Status report
./scripts/nightly-pipeline-scheduler.sh --status
# Force start during peak hours
./scripts/nightly-pipeline-scheduler.sh --force
```
## Configuration
Set via environment variables:
- `PIPELINE_TOKEN_LIMIT`: Daily token budget (default: 500,000)
- `PIPELINE_PEAK_START`: Peak hours start (default: 9)
- `PIPELINE_PEAK_END`: Peak hours end (default: 18)
- `HERMES_HOME`: Hermes home directory (default: ~/.hermes)
## Cron
Runs every 30 minutes. Off-peak only (unless --force).
See `cron/pipeline-scheduler.yml`.

View File

@@ -0,0 +1,383 @@
#!/usr/bin/env bash
# nightly-pipeline-scheduler.sh — Auto-start batch pipelines when inference is available.
#
# Checks provider health, pipeline progress, token budget, and interactive load.
# Starts the highest-priority incomplete pipeline that can run.
#
# Usage:
# ./scripts/nightly-pipeline-scheduler.sh # Normal run
# ./scripts/nightly-pipeline-scheduler.sh --dry-run # Show what would start
# ./scripts/nightly-pipeline-scheduler.sh --status # Pipeline status report
set -euo pipefail
# --- Configuration ---
HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
BUDGET_FILE="${HERMES_HOME}/pipeline_budget.json"
STATE_FILE="${HERMES_HOME}/pipeline_state.json"
LOG_FILE="${HERMES_HOME}/logs/pipeline-scheduler.log"
TOKEN_DAILY_LIMIT="${PIPELINE_TOKEN_LIMIT:-500000}"
PEAK_HOURS_START="${PIPELINE_PEAK_START:-9}"
PEAK_HOURS_END="${PIPELINE_PEAK_END:-18}"
# Pipeline definitions (priority order)
# Each pipeline: name, script, max_tokens, dependencies
PIPELINES=(
"playground-factory|scripts/pipeline_playground_factory.sh|100000|none"
"training-factory|scripts/pipeline_training_factory.sh|150000|none"
"knowledge-mine|scripts/pipeline_knowledge_mine.sh|80000|training-factory"
"adversary|scripts/pipeline_adversary.sh|50000|knowledge-mine"
"codebase-genome|scripts/pipeline_codebase_genome.sh|120000|none"
)
# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# --- Helpers ---
now_hour() { date +%-H; }
is_peak_hours() {
local h=$(now_hour)
[[ $h -ge $PEAK_HOURS_START && $h -lt $PEAK_HOURS_END ]]
}
ensure_dirs() {
mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$BUDGET_FILE")" "$(dirname "$STATE_FILE")"
}
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
get_budget_used_today() {
if [[ -f "$BUDGET_FILE" ]]; then
local today=$(date +%Y-%m-%d)
python3 -c "
import json, sys
with open('$BUDGET_FILE') as f:
d = json.load(f)
print(d.get('daily', {}).get('$today', {}).get('tokens_used', 0))
" 2>/dev/null || echo 0
else
echo 0
fi
}
get_budget_remaining() {
local used=$(get_budget_used_today)
echo $((TOKEN_DAILY_LIMIT - used))
}
update_budget() {
local pipeline="$1"
local tokens="$2"
local today=$(date +%Y-%m-%d)
python3 -c "
import json, os
path = '$BUDGET_FILE'
d = {}
if os.path.exists(path):
with open(path) as f:
d = json.load(f)
daily = d.setdefault('daily', {})
day = daily.setdefault('$today', {'tokens_used': 0, 'pipelines': {}})
day['tokens_used'] = day.get('tokens_used', 0) + $tokens
day['pipelines']['$pipeline'] = day['pipelines'].get('$pipeline', 0) + $tokens
with open(path, 'w') as f:
json.dump(d, f, indent=2)
"
}
get_pipeline_state() {
if [[ -f "$STATE_FILE" ]]; then
cat "$STATE_FILE"
else
echo "{}"
fi
}
set_pipeline_state() {
local pipeline="$1"
local state="$2" # running, complete, failed, skipped
python3 -c "
import json, os
path = '$STATE_FILE'
d = {}
if os.path.exists(path):
with open(path) as f:
d = json.load(f)
d['$pipeline'] = {'state': '$state', 'updated': '$(date -Iseconds)'}
with open(path, 'w') as f:
json.dump(d, f, indent=2)
"
}
is_pipeline_complete() {
local pipeline="$1"
python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('false')
else:
with open(path) as f:
d = json.load(f)
state = d.get('$pipeline', {}).get('state', 'not_started')
print('true' if state == 'complete' else 'false')
" 2>/dev/null || echo false
}
is_pipeline_running() {
local pipeline="$1"
python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('false')
else:
with open(path) as f:
d = json.load(f)
state = d.get('$pipeline', {}).get('state', 'not_started')
print('true' if state == 'running' else 'false')
" 2>/dev/null || echo false
}
check_dependency() {
local dep="$1"
if [[ "$dep" == "none" ]]; then
return 0
fi
# For knowledge-mine: training-factory must be running or complete
if [[ "$dep" == "training-factory" ]]; then
local state=$(python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('not_started')
else:
with open(path) as f:
d = json.load(f)
print(d.get('training-factory', {}).get('state', 'not_started'))
" 2>/dev/null || echo "not_started")
[[ "$state" == "running" || "$state" == "complete" ]]
return $?
fi
# For adversary: knowledge-mine must be at least 50% done
# Simplified: check if it's running (we'd need progress tracking for 50%)
if [[ "$dep" == "knowledge-mine" ]]; then
local state=$(python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('not_started')
else:
with open(path) as f:
d = json.load(f)
print(d.get('knowledge-mine', {}).get('state', 'not_started'))
" 2>/dev/null || echo "not_started")
[[ "$state" == "running" || "$state" == "complete" ]]
return $?
fi
return 0
}
check_inference_available() {
# Check if any inference provider is responding
# 1. Check OpenRouter
local or_ok=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 "https://openrouter.ai/api/v1/models" 2>/dev/null || echo "000")
# 2. Check local Ollama
local ollama_ok=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 "http://localhost:11434/api/tags" 2>/dev/null || echo "000")
# 3. Check RunPod (if configured)
local runpod_ok="000"
if [[ -n "${RUNPOD_ENDPOINT:-}" ]]; then
runpod_ok=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 "$RUNPOD_ENDPOINT/health" 2>/dev/null || echo "000")
fi
if [[ "$or_ok" == "200" || "$ollama_ok" == "200" || "$runpod_ok" == "200" ]]; then
return 0
fi
return 1
}
check_interactive_load() {
# Check if there are active interactive sessions (don't fight with live users)
# Look for tmux panes with active hermes sessions
local active=$(tmux list-panes -a -F '#{pane_pid} #{pane_current_command}' 2>/dev/null \
| grep -c "hermes\|python3" || echo 0)
# If more than 3 interactive sessions, skip pipeline start
if [[ $active -gt 3 ]]; then
return 1
fi
return 0
}
start_pipeline() {
local name="$1"
local script="$2"
local max_tokens="$3"
local budget_remaining="$4"
local mode="${5:-run}"
if [[ "$budget_remaining" -lt "$max_tokens" ]]; then
log "SKIP $name: insufficient budget ($budget_remaining < $max_tokens tokens)"
return 1
fi
if [[ ! -f "$script" ]]; then
log "SKIP $name: script not found ($script)"
return 1
fi
if [[ "$mode" == "dry-run" ]]; then
log "DRY-RUN: Would start $name (budget: $budget_remaining, needs: $max_tokens)"
return 0
fi
log "START $name (budget: $budget_remaining, max_tokens: $max_tokens)"
set_pipeline_state "$name" "running"
# Run in background, capture output
local log_path="${HERMES_HOME}/logs/pipeline-${name}.log"
bash "$script" --max-tokens "$max_tokens" >> "$log_path" 2>&1 &
local pid=$!
# Wait a moment to check if it started OK
sleep 2
if kill -0 $pid 2>/dev/null; then
log "RUNNING $name (PID: $pid, log: $log_path)"
# Record the PID
python3 -c "
import json, os
path = '$STATE_FILE'
d = {}
if os.path.exists(path):
with open(path) as f:
d = json.load(f)
d['$name']['pid'] = $pid
with open(path, 'w') as f:
json.dump(d, f, indent=2)
"
return 0
else
log "FAIL $name: script exited immediately"
set_pipeline_state "$name" "failed"
return 1
fi
}
# --- Main ---
main() {
local mode="${1:-run}"
ensure_dirs
log "=== Pipeline Scheduler ($mode) ==="
# Check 1: Is inference available?
if ! check_inference_available; then
log "No inference provider available. Skipping all pipelines."
exit 0
fi
log "Inference: AVAILABLE"
# Check 2: Is it peak hours?
if is_peak_hours && [[ "$mode" != "--force" ]]; then
local h=$(now_hour)
log "Peak hours ($h:00). Skipping pipeline start. Use --force to override."
exit 0
fi
log "Off-peak: OK"
# Check 3: Interactive load
if ! check_interactive_load && [[ "$mode" != "--force" ]]; then
log "High interactive load. Skipping pipeline start."
exit 0
fi
log "Interactive load: OK"
# Check 4: Token budget
local budget=$(get_budget_remaining)
log "Token budget remaining: $budget / $TOKEN_DAILY_LIMIT"
if [[ $budget -le 0 ]]; then
log "Daily token budget exhausted. Stopping."
exit 0
fi
# Check 5: Pipeline status
if [[ "$mode" == "--status" ]]; then
echo -e "${CYAN}Pipeline Status:${NC}"
echo "────────────────────────────────────────────────────"
for entry in "${PIPELINES[@]}"; do
IFS='|' read -r name script max_tokens dep <<< "$entry"
local state=$(python3 -c "
import json, os
path = '$STATE_FILE'
if not os.path.exists(path):
print('not_started')
else:
with open(path) as f:
d = json.load(f)
print(d.get('$name', {}).get('state', 'not_started'))
" 2>/dev/null || echo "not_started")
local color=$NC
case "$state" in
running) color=$YELLOW ;;
complete) color=$GREEN ;;
failed) color=$RED ;;
esac
printf " %-25s %b%s%b (max: %s tokens, dep: %s)\n" "$name" "$color" "$state" "$NC" "$max_tokens" "$dep"
done
echo "────────────────────────────────────────────────────"
echo " Budget: $budget / $TOKEN_DAILY_LIMIT tokens remaining"
echo " Peak hours: $PEAK_HOURS_START:00 - $PEAK_HOURS_END:00"
exit 0
fi
# Find and start the highest-priority incomplete pipeline
local started=0
for entry in "${PIPELINES[@]}"; do
IFS='|' read -r name script max_tokens dep <<< "$entry"
# Skip if already running or complete
if [[ "$(is_pipeline_running $name)" == "true" ]]; then
log "SKIP $name: already running"
continue
fi
if [[ "$(is_pipeline_complete $name)" == "true" ]]; then
log "SKIP $name: already complete"
continue
fi
# Check dependency
if ! check_dependency "$dep"; then
log "SKIP $name: dependency $dep not met"
continue
fi
# Try to start
if start_pipeline "$name" "$script" "$max_tokens" "$budget" "$mode"; then
started=1
# Only start one pipeline per run (let it claim tokens before next check)
# Exception: playground-factory and training-factory can run in parallel
if [[ "$name" != "playground-factory" && "$name" != "training-factory" ]]; then
break
fi
fi
done
if [[ $started -eq 0 ]]; then
log "No pipelines to start (all complete, running, or blocked)."
fi
log "=== Pipeline Scheduler done ==="
}
main "$@"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import json
from hermes_tools import browser_navigate, browser_vision

View File

@@ -0,0 +1,301 @@
#!/usr/bin/env python3
"""Tests for foundation_accessibility_audit.py — verifies WCAG checks."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from foundation_accessibility_audit import (
A11yHTMLParser, Severity, A11yViolation,
parse_color, contrast_ratio, relative_luminance,
run_programmatic_checks, check_page_title, check_images_alt_text,
check_heading_hierarchy, check_lang_attribute, check_landmarks,
check_skip_nav, check_form_labels, check_link_text,
_parse_json_response, format_report, A11yAuditReport, A11yPageResult,
)
# === Color Utilities ===
def test_parse_color_hex6():
assert parse_color("#ff0000") == (255, 0, 0)
assert parse_color("#000000") == (0, 0, 0)
assert parse_color("#ffffff") == (255, 255, 255)
print(" PASS: test_parse_color_hex6")
def test_parse_color_hex3():
assert parse_color("#f00") == (255, 0, 0)
assert parse_color("#abc") == (170, 187, 204)
print(" PASS: test_parse_color_hex3")
def test_parse_color_rgb():
assert parse_color("rgb(255, 0, 0)") == (255, 0, 0)
assert parse_color("rgb( 128 , 64 , 32 )") == (128, 64, 32)
print(" PASS: test_parse_color_rgb")
def test_parse_color_named():
assert parse_color("white") == (255, 255, 255)
assert parse_color("black") == (0, 0, 0)
print(" PASS: test_parse_color_named")
def test_parse_color_invalid():
assert parse_color("not-a-color") is None
assert parse_color("") is None
print(" PASS: test_parse_color_invalid")
def test_contrast_ratio_black_white():
ratio = contrast_ratio((0, 0, 0), (255, 255, 255))
assert ratio > 20 # Should be 21:1
print(f" PASS: test_contrast_ratio_black_white ({ratio:.1f}:1)")
def test_contrast_ratio_same():
ratio = contrast_ratio((128, 128, 128), (128, 128, 128))
assert ratio == 1.0
print(" PASS: test_contrast_ratio_same")
def test_contrast_ratio_wcag_aa():
# #767676 on white = 4.54:1 (WCAG AA pass for normal text)
ratio = contrast_ratio((118, 118, 118), (255, 255, 255))
assert ratio >= 4.5
print(f" PASS: test_contrast_ratio_wcag_aa ({ratio:.2f}:1)")
# === HTML Parser ===
def test_parser_title():
parser = A11yHTMLParser()
parser.feed("<html><head><title>Test Page</title></head></html>")
assert parser.title == "Test Page"
print(" PASS: test_parser_title")
def test_parser_images():
parser = A11yHTMLParser()
parser.feed('<html><body><img src="a.png" alt="Alt text"><img src="b.png"></body></html>')
assert len(parser.images) == 2
assert parser.images[0]["alt"] == "Alt text"
assert parser.images[1]["alt"] is None
print(" PASS: test_parser_images")
def test_parser_headings():
parser = A11yHTMLParser()
parser.feed("<html><body><h1>Main</h1><h2>Sub</h2><h4>Skip</h4></body></html>")
assert len(parser.headings) == 3
assert parser.headings[0] == {"level": 1, "text": "Main"}
assert parser.headings[2] == {"level": 4, "text": "Skip"}
print(" PASS: test_parser_headings")
def test_parser_lang():
parser = A11yHTMLParser()
parser.feed('<html lang="en"><body></body></html>')
assert parser.lang == "en"
print(" PASS: test_parser_lang")
def test_parser_landmarks():
parser = A11yHTMLParser()
parser.feed("<html><body><nav>Links</nav><main>Content</main></body></html>")
tags = {lm["tag"] for lm in parser.landmarks}
assert "nav" in tags
assert "main" in tags
print(" PASS: test_parser_landmarks")
# === Programmatic Checks ===
def test_check_page_title_empty():
parser = A11yHTMLParser()
parser.title = ""
violations = check_page_title(parser)
assert len(violations) == 1
assert violations[0].criterion == "2.4.2"
assert violations[0].severity == Severity.MAJOR
print(" PASS: test_check_page_title_empty")
def test_check_page_title_present():
parser = A11yHTMLParser()
parser.title = "My Great Page"
violations = check_page_title(parser)
assert len(violations) == 0
print(" PASS: test_check_page_title_present")
def test_check_lang_missing():
parser = A11yHTMLParser()
parser.lang = ""
violations = check_lang_attribute(parser)
assert len(violations) == 1
assert violations[0].criterion == "3.1.1"
print(" PASS: test_check_lang_missing")
def test_check_images_missing_alt():
parser = A11yHTMLParser()
parser.images = [{"src": "photo.jpg", "alt": None}]
violations = check_images_alt_text(parser)
assert len(violations) == 1
assert violations[0].severity == Severity.CRITICAL
print(" PASS: test_check_images_missing_alt")
def test_check_images_with_alt():
parser = A11yHTMLParser()
parser.images = [{"src": "photo.jpg", "alt": "A photo"}]
violations = check_images_alt_text(parser)
assert len(violations) == 0
print(" PASS: test_check_images_with_alt")
def test_check_images_decorative():
parser = A11yHTMLParser()
parser.images = [{"src": "deco.png", "alt": "", "role": "presentation"}]
violations = check_images_alt_text(parser)
assert len(violations) == 0
print(" PASS: test_check_images_decorative")
def test_check_headings_no_h1():
parser = A11yHTMLParser()
parser.headings = [{"level": 2, "text": "Sub"}, {"level": 3, "text": "Sub sub"}]
violations = check_heading_hierarchy(parser)
assert any(v.criterion == "1.3.1" and "h1" in v.description.lower() for v in violations)
print(" PASS: test_check_headings_no_h1")
def test_check_headings_skip():
parser = A11yHTMLParser()
parser.headings = [{"level": 1, "text": "Main"}, {"level": 4, "text": "Skipped"}]
violations = check_heading_hierarchy(parser)
assert any("skipped" in v.description.lower() for v in violations)
print(" PASS: test_check_headings_skip")
def test_check_skip_nav_missing():
parser = A11yHTMLParser()
parser.skip_nav = False
parser.links = [{"text": "Home", "href": "/"}, {"text": "About", "href": "/about"}]
violations = check_skip_nav(parser)
assert len(violations) == 1
assert violations[0].criterion == "2.4.1"
print(" PASS: test_check_skip_nav_missing")
def test_check_link_text_empty():
parser = A11yHTMLParser()
parser.links = [{"text": "", "href": "/page", "aria_label": ""}]
violations = check_link_text(parser)
assert len(violations) == 1
assert violations[0].criterion == "2.4.4"
print(" PASS: test_check_link_text_empty")
def test_check_link_text_generic():
parser = A11yHTMLParser()
parser.links = [{"text": "Click here", "href": "/page"}]
violations = check_link_text(parser)
assert any("non-descriptive" in v.description.lower() for v in violations)
print(" PASS: test_check_link_text_generic")
def test_run_programmatic_checks_full():
html = """<!DOCTYPE html>
<html lang="en">
<head><title>Good Page</title></head>
<body>
<nav><a href="#main">Skip to content</a></nav>
<main>
<h1>Welcome</h1>
<h2>Section</h2>
<img src="hero.jpg" alt="Hero image">
<a href="/about">About Us</a>
</main>
</body>
</html>"""
violations = run_programmatic_checks(html)
# This page should have very few or no violations
criticals = [v for v in violations if v.severity == Severity.CRITICAL]
assert len(criticals) == 0
print(f" PASS: test_run_programmatic_checks_full ({len(violations)} minor issues)")
# === JSON Parsing ===
def test_parse_json_clean():
result = _parse_json_response('{"violations": [], "overall_score": 100}')
assert result["overall_score"] == 100
print(" PASS: test_parse_json_clean")
def test_parse_json_fenced():
result = _parse_json_response('```json\n{"overall_score": 80}\n```')
assert result["overall_score"] == 80
print(" PASS: test_parse_json_fenced")
# === Formatting ===
def test_format_json():
report = A11yAuditReport(site="test.com", pages_audited=1, overall_score=90)
output = format_report(report, "json")
parsed = json.loads(output)
assert parsed["site"] == "test.com"
assert parsed["overall_score"] == 90
print(" PASS: test_format_json")
def test_format_text():
report = A11yAuditReport(site="test.com", pages_audited=1, overall_score=90,
summary="Test complete")
output = format_report(report, "text")
assert "ACCESSIBILITY AUDIT" in output
assert "test.com" in output
print(" PASS: test_format_text")
# === Run All ===
def run_all():
print("=== foundation_accessibility_audit tests ===")
tests = [
test_parse_color_hex6, test_parse_color_hex3, test_parse_color_rgb,
test_parse_color_named, test_parse_color_invalid,
test_contrast_ratio_black_white, test_contrast_ratio_same, test_contrast_ratio_wcag_aa,
test_parser_title, test_parser_images, test_parser_headings,
test_parser_lang, test_parser_landmarks,
test_check_page_title_empty, test_check_page_title_present,
test_check_lang_missing,
test_check_images_missing_alt, test_check_images_with_alt, test_check_images_decorative,
test_check_headings_no_h1, test_check_headings_skip,
test_check_skip_nav_missing,
test_check_link_text_empty, test_check_link_text_generic,
test_run_programmatic_checks_full,
test_parse_json_clean, test_parse_json_fenced,
test_format_json, test_format_text,
]
passed = 0
failed = 0
for test in tests:
try:
test()
passed += 1
except Exception as e:
print(f" FAIL: {test.__name__}{e}")
failed += 1
print(f"\n{'ALL PASSED' if failed == 0 else f'{failed} FAILED'}: {passed}/{len(tests)}")
return failed == 0
if __name__ == "__main__":
sys.exit(0 if run_all() else 1)

View File

@@ -0,0 +1,380 @@
#!/usr/bin/env python3
"""
Tests for Matrix 3D Glitch Detector (timmy-config#491).
Covers: glitch_patterns, matrix_glitch_detector core logic.
"""
import json
import sys
import tempfile
import unittest
from pathlib import Path
# Ensure bin/ is importable
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "bin"))
from glitch_patterns import (
GlitchCategory,
GlitchPattern,
GlitchSeverity,
MATRIX_GLITCH_PATTERNS,
THREEJS_CATEGORIES,
build_vision_prompt,
get_pattern_by_category,
get_patterns_by_severity,
get_threejs_patterns,
)
from matrix_glitch_detector import (
DetectedGlitch,
ScanResult,
_infer_severity,
_parse_vision_response,
build_report,
generate_scan_angles,
run_demo,
)
class TestGlitchPatterns(unittest.TestCase):
"""Tests for glitch_patterns module."""
def test_pattern_count(self):
"""Verify we have a reasonable number of defined patterns."""
self.assertGreaterEqual(len(MATRIX_GLITCH_PATTERNS), 14) # 10 generic + 6 Three.js
def test_all_patterns_have_required_fields(self):
"""Every pattern must have category, name, description, severity, prompts."""
for p in MATRIX_GLITCH_PATTERNS:
self.assertIsInstance(p.category, GlitchCategory)
self.assertTrue(p.name)
self.assertTrue(p.description)
self.assertIsInstance(p.severity, GlitchSeverity)
self.assertGreater(len(p.detection_prompts), 0)
self.assertGreater(len(p.visual_indicators), 0)
self.assertGreater(p.confidence_threshold, 0)
self.assertLessEqual(p.confidence_threshold, 1.0)
def test_pattern_to_dict(self):
"""Pattern serialization should produce a dict with expected keys."""
p = MATRIX_GLITCH_PATTERNS[0]
d = p.to_dict()
self.assertIn("category", d)
self.assertIn("name", d)
self.assertIn("severity", d)
self.assertEqual(d["category"], p.category.value)
def test_get_patterns_by_severity(self):
"""Severity filter should return only patterns at or above threshold."""
high_patterns = get_patterns_by_severity(GlitchSeverity.HIGH)
self.assertTrue(all(p.severity.value in ("high", "critical") for p in high_patterns))
self.assertGreater(len(high_patterns), 0)
all_patterns = get_patterns_by_severity(GlitchSeverity.INFO)
self.assertEqual(len(all_patterns), len(MATRIX_GLITCH_PATTERNS))
def test_get_pattern_by_category(self):
"""Lookup by category should return the correct pattern."""
p = get_pattern_by_category(GlitchCategory.FLOATING_ASSETS)
self.assertIsNotNone(p)
self.assertEqual(p.category, GlitchCategory.FLOATING_ASSETS)
missing = get_pattern_by_category("nonexistent_category_value")
self.assertIsNone(missing)
def test_build_vision_prompt(self):
"""Vision prompt should contain pattern names and be non-trivial."""
prompt = build_vision_prompt()
self.assertGreater(len(prompt), 200)
self.assertIn("Floating Object", prompt)
self.assertIn("Z-Fighting", prompt)
self.assertIn("Missing", prompt)
# Three.js patterns should be included
self.assertIn("Shader Compilation Failure", prompt)
self.assertIn("Bloom Overflow", prompt)
def test_build_vision_prompt_subset(self):
"""Vision prompt with subset should only include specified patterns."""
subset = MATRIX_GLITCH_PATTERNS[:3]
prompt = build_vision_prompt(subset)
self.assertIn(subset[0].name, prompt)
self.assertNotIn(MATRIX_GLITCH_PATTERNS[-1].name, prompt)
class TestGlitchDetector(unittest.TestCase):
"""Tests for matrix_glitch_detector module."""
def test_generate_scan_angles_default(self):
"""Default 4 angles should return front, right, back, left."""
angles = generate_scan_angles(4)
self.assertEqual(len(angles), 4)
labels = [a["label"] for a in angles]
self.assertIn("front", labels)
self.assertIn("right", labels)
self.assertIn("back", labels)
self.assertIn("left", labels)
def test_generate_scan_angles_many(self):
"""Requesting more angles than base should still return correct count."""
angles = generate_scan_angles(12)
self.assertEqual(len(angles), 12)
# Should still have the standard ones
labels = [a["label"] for a in angles]
self.assertIn("front", labels)
def test_generate_scan_angles_few(self):
"""Requesting fewer angles should return fewer."""
angles = generate_scan_angles(2)
self.assertEqual(len(angles), 2)
def test_detected_glitch_dataclass(self):
"""DetectedGlitch should serialize cleanly."""
g = DetectedGlitch(
id="test001",
category="floating_assets",
name="Test Glitch",
description="A test glitch",
severity="high",
confidence=0.85,
location_x=50.0,
location_y=30.0,
screenshot_index=0,
screenshot_angle="front",
)
self.assertEqual(g.id, "test001")
self.assertTrue(g.timestamp) # Auto-generated
def test_infer_severity_critical(self):
"""Missing textures should infer critical/high severity."""
sev = _infer_severity("missing_textures", 0.9)
self.assertEqual(sev, "critical")
sev_low = _infer_severity("missing_textures", 0.5)
self.assertEqual(sev_low, "high")
def test_infer_severity_floating(self):
"""Floating assets should infer high/medium severity."""
sev = _infer_severity("floating_assets", 0.8)
self.assertEqual(sev, "high")
sev_low = _infer_severity("floating_assets", 0.5)
self.assertEqual(sev_low, "medium")
def test_infer_severity_default(self):
"""Unknown categories should default to medium/low."""
sev = _infer_severity("unknown_thing", 0.7)
self.assertEqual(sev, "medium")
sev_low = _infer_severity("unknown_thing", 0.3)
self.assertEqual(sev_low, "low")
def test_parse_vision_response_json_array(self):
"""Should parse a JSON array response."""
response = json.dumps([
{
"category": "floating_assets",
"name": "Float Test",
"description": "Chair floating",
"confidence": 0.9,
"severity": "high",
"location_x": 40,
"location_y": 60,
}
])
glitches = _parse_vision_response(response, 0, "front")
self.assertEqual(len(glitches), 1)
self.assertEqual(glitches[0].category, "floating_assets")
self.assertAlmostEqual(glitches[0].confidence, 0.9)
def test_parse_vision_response_wrapped(self):
"""Should parse a response with 'glitches' wrapper key."""
response = json.dumps({
"glitches": [
{
"category": "z_fighting",
"name": "Shimmer",
"confidence": 0.6,
}
]
})
glitches = _parse_vision_response(response, 1, "right")
self.assertEqual(len(glitches), 1)
self.assertEqual(glitches[0].category, "z_fighting")
def test_parse_vision_response_empty(self):
"""Should return empty list for non-JSON text."""
glitches = _parse_vision_response("No glitches found.", 0, "front")
self.assertEqual(len(glitches), 0)
def test_parse_vision_response_code_block(self):
"""Should extract JSON from markdown code blocks."""
response = '```json\n[{"category": "clipping", "name": "Clip", "confidence": 0.7}]\n```'
glitches = _parse_vision_response(response, 0, "front")
self.assertEqual(len(glitches), 1)
def test_build_report(self):
"""Report should have correct summary statistics."""
angles = generate_scan_angles(4)
screenshots = [Path(f"/tmp/ss_{i}.png") for i in range(4)]
glitches = [
DetectedGlitch(
id="a", category="floating_assets", name="Float",
description="", severity="high", confidence=0.8,
screenshot_index=0, screenshot_angle="front",
),
DetectedGlitch(
id="b", category="missing_textures", name="Missing",
description="", severity="critical", confidence=0.95,
screenshot_index=1, screenshot_angle="right",
),
]
report = build_report("https://test.com", angles, screenshots, glitches)
self.assertEqual(report.total_screenshots, 4)
self.assertEqual(len(report.glitches), 2)
self.assertEqual(report.summary["total_glitches"], 2)
self.assertEqual(report.summary["by_severity"]["critical"], 1)
self.assertEqual(report.summary["by_severity"]["high"], 1)
self.assertEqual(report.summary["by_category"]["floating_assets"], 1)
self.assertEqual(report.metadata["reference"], "timmy-config#491")
def test_build_report_json_roundtrip(self):
"""Report JSON should parse back correctly."""
angles = generate_scan_angles(2)
screenshots = [Path(f"/tmp/ss_{i}.png") for i in range(2)]
report = build_report("https://test.com", angles, screenshots, [])
json_str = report.to_json()
parsed = json.loads(json_str)
self.assertEqual(parsed["url"], "https://test.com")
self.assertEqual(parsed["total_screenshots"], 2)
def test_run_demo(self):
"""Demo mode should produce a report with simulated glitches."""
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
output_path = Path(f.name)
try:
report = run_demo(output_path)
self.assertEqual(len(report.glitches), 6) # 4 original + 2 Three.js
self.assertGreater(report.summary["total_glitches"], 0)
self.assertTrue(output_path.exists())
# Verify the saved JSON is valid
saved = json.loads(output_path.read_text())
self.assertIn("scan_id", saved)
self.assertIn("glitches", saved)
finally:
output_path.unlink(missing_ok=True)
class TestThreeJsPatterns(unittest.TestCase):
"""Tests for Three.js-specific glitch patterns (timmy-config#543)."""
def test_get_threejs_patterns_returns_only_threejs(self):
"""get_threejs_patterns() should return only Three.js categories."""
patterns = get_threejs_patterns()
self.assertEqual(len(patterns), 6)
for p in patterns:
self.assertIn(p.category, THREEJS_CATEGORIES)
def test_threejs_patterns_have_required_fields(self):
"""All Three.js patterns must have valid fields."""
for p in get_threejs_patterns():
self.assertIsInstance(p.category, GlitchCategory)
self.assertTrue(p.name)
self.assertTrue(p.description)
self.assertIsInstance(p.severity, GlitchSeverity)
self.assertGreater(len(p.detection_prompts), 0)
self.assertGreater(len(p.visual_indicators), 0)
def test_shader_failure_is_critical(self):
"""Shader compilation failure should be CRITICAL severity."""
p = get_pattern_by_category(GlitchCategory.SHADER_FAILURE)
self.assertIsNotNone(p)
self.assertEqual(p.severity, GlitchSeverity.CRITICAL)
def test_texture_placeholder_is_critical(self):
"""Texture placeholder (1x1 white) should be CRITICAL severity."""
p = get_pattern_by_category(GlitchCategory.TEXTURE_PLACEHOLDER)
self.assertIsNotNone(p)
self.assertEqual(p.severity, GlitchSeverity.CRITICAL)
def test_infer_severity_shader_failure(self):
"""Shader failure should infer critical/high."""
self.assertEqual(_infer_severity("shader_failure", 0.8), "critical")
self.assertEqual(_infer_severity("shader_failure", 0.5), "high")
def test_infer_severity_texture_placeholder(self):
"""Texture placeholder should infer critical/high."""
self.assertEqual(_infer_severity("texture_placeholder", 0.8), "critical")
self.assertEqual(_infer_severity("texture_placeholder", 0.5), "high")
def test_infer_severity_uv_mapping(self):
"""UV mapping error should infer high/medium."""
self.assertEqual(_infer_severity("uv_mapping_error", 0.8), "high")
self.assertEqual(_infer_severity("uv_mapping_error", 0.5), "medium")
def test_infer_severity_frustum_culling(self):
"""Frustum culling should infer medium/low."""
self.assertEqual(_infer_severity("frustum_culling", 0.7), "medium")
self.assertEqual(_infer_severity("frustum_culling", 0.4), "low")
def test_infer_severity_shadow_map(self):
"""Shadow map artifact should infer medium/low."""
self.assertEqual(_infer_severity("shadow_map_artifact", 0.7), "medium")
self.assertEqual(_infer_severity("shadow_map_artifact", 0.4), "low")
def test_infer_severity_bloom_overflow(self):
"""Bloom overflow should infer medium/low (default path)."""
self.assertEqual(_infer_severity("bloom_overflow", 0.7), "medium")
self.assertEqual(_infer_severity("bloom_overflow", 0.4), "low")
def test_threejs_patterns_in_vision_prompt(self):
"""Three.js patterns should appear in the composite vision prompt."""
prompt = build_vision_prompt()
self.assertIn("shader_failure", prompt)
self.assertIn("texture_placeholder", prompt)
self.assertIn("uv_mapping_error", prompt)
self.assertIn("frustum_culling", prompt)
self.assertIn("shadow_map_artifact", prompt)
self.assertIn("bloom_overflow", prompt)
def test_threejs_subset_prompt(self):
"""Building prompt from Three.js-only patterns should work."""
threejs = get_threejs_patterns()
prompt = build_vision_prompt(threejs)
self.assertIn("Shader Compilation Failure", prompt)
self.assertNotIn("Floating Object", prompt) # generic, not Three.js
def test_report_metadata_version(self):
"""Report metadata should reference both issues."""
report = run_demo()
self.assertEqual(report.metadata["detector_version"], "0.2.0")
self.assertIn("543", report.metadata["reference"])
class TestIntegration(unittest.TestCase):
"""Integration-level tests."""
def test_full_pipeline_demo(self):
"""End-to-end demo pipeline should complete without errors."""
report = run_demo()
self.assertIsNotNone(report.scan_id)
self.assertTrue(report.timestamp)
self.assertGreater(report.total_screenshots, 0)
def test_patterns_cover_matrix_themes(self):
"""Patterns should cover the main Matrix glitch themes."""
category_values = {p.category.value for p in MATRIX_GLITCH_PATTERNS}
expected = {"floating_assets", "z_fighting", "missing_textures", "clipping", "broken_normals"}
self.assertTrue(expected.issubset(category_values))
def test_patterns_cover_threejs_themes(self):
"""Patterns should cover Three.js-specific glitch themes (#543)."""
category_values = {p.category.value for p in MATRIX_GLITCH_PATTERNS}
threejs_expected = {"shader_failure", "texture_placeholder", "uv_mapping_error",
"frustum_culling", "shadow_map_artifact", "bloom_overflow"}
self.assertTrue(threejs_expected.issubset(category_values))
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""Tests for matrix_glitch_detect.py — verifies detection and HTML report logic."""
import json
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from matrix_glitch_detect import (
Severity, Glitch, GlitchReport,
format_report, generate_html_report, _parse_json_response,
)
def test_parse_json_clean():
result = _parse_json_response('{"glitches": [], "overall_quality": 95}')
assert result["overall_quality"] == 95
print(" PASS: test_parse_json_clean")
def test_parse_json_fenced():
result = _parse_json_response('```json\n{"overall_quality": 80}\n```')
assert result["overall_quality"] == 80
print(" PASS: test_parse_json_fenced")
def test_parse_json_garbage():
assert _parse_json_response("no json") == {}
print(" PASS: test_parse_json_garbage")
def test_glitch_dataclass():
g = Glitch(type="z_fighting", severity=Severity.MAJOR, region="center", description="Shimmer", confidence=0.8)
assert g.type == "z_fighting"
assert g.confidence == 0.8
print(" PASS: test_glitch_dataclass")
def test_report_dataclass():
r = GlitchReport(source="test.png", status="WARN", score=75)
r.glitches.append(Glitch(type="float", severity=Severity.MINOR))
assert len(r.glitches) == 1
assert r.score == 75
print(" PASS: test_report_dataclass")
def test_format_json():
r = GlitchReport(source="test.png", status="PASS", score=90, summary="Clean")
r.glitches.append(Glitch(type="cosmetic", severity=Severity.COSMETIC, description="Minor"))
output = format_report(r, "json")
parsed = json.loads(output)
assert parsed["status"] == "PASS"
assert len(parsed["glitches"]) == 1
print(" PASS: test_format_json")
def test_format_text():
r = GlitchReport(source="test.png", status="FAIL", score=30, summary="Critical glitch")
r.glitches.append(Glitch(type="render_failure", severity=Severity.CRITICAL, description="Black screen"))
output = format_report(r, "text")
assert "FAIL" in output
assert "render_failure" in output
print(" PASS: test_format_text")
def test_html_report_basic():
r = GlitchReport(source="test.png", status="PASS", score=100)
html = generate_html_report([r], title="Test Report")
assert "<!DOCTYPE html>" in html
assert "Test Report" in html
assert "PASS" in html
assert "100" in html
print(" PASS: test_html_report_basic")
def test_html_report_with_glitches():
r = GlitchReport(source="test.png", status="FAIL", score=40)
r.glitches.append(Glitch(type="z_fighting", severity=Severity.CRITICAL, region="center", description="Heavy flicker", confidence=0.9))
r.glitches.append(Glitch(type="clipping", severity=Severity.MINOR, region="bottom", description="Object through floor", confidence=0.6))
html = generate_html_report([r], title="Glitch Report")
assert "z_fighting" in html
assert "CRITICAL" in html
assert "clipping" in html
assert "Heavy flicker" in html
print(" PASS: test_html_report_with_glitches")
def test_html_report_multi():
r1 = GlitchReport(source="a.png", status="PASS", score=95)
r2 = GlitchReport(source="b.png", status="WARN", score=70)
r2.glitches.append(Glitch(type="texture_pop", severity=Severity.MAJOR))
html = generate_html_report([r1, r2])
assert "a.png" in html
assert "b.png" in html
assert "2" in html # 2 screenshots
print(" PASS: test_html_report_multi")
def test_html_self_contained():
r = GlitchReport(source="test.png", status="PASS", score=100)
html = generate_html_report([r])
assert "external" not in html.lower() or "no external dependencies" in html.lower()
assert "<style>" in html # Inline CSS
print(" PASS: test_html_self_contained")
def test_missing_image():
r = GlitchReport(source="/nonexistent/image.png")
# detect_glitches would set FAIL — simulate
r.status = "FAIL"
r.score = 0
r.summary = "File not found"
assert r.status == "FAIL"
print(" PASS: test_missing_image")
def test_severity_enum():
assert Severity.CRITICAL.value == "critical"
assert Severity.MAJOR.value == "major"
print(" PASS: test_severity_enum")
def run_all():
print("=== matrix_glitch_detect tests ===")
tests = [
test_parse_json_clean, test_parse_json_fenced, test_parse_json_garbage,
test_glitch_dataclass, test_report_dataclass,
test_format_json, test_format_text,
test_html_report_basic, test_html_report_with_glitches,
test_html_report_multi, test_html_self_contained,
test_missing_image, test_severity_enum,
]
passed = failed = 0
for t in tests:
try:
t()
passed += 1
except Exception as e:
print(f" FAIL: {t.__name__}{e}")
failed += 1
print(f"\n{'ALL PASSED' if failed == 0 else f'{failed} FAILED'}: {passed}/{len(tests)}")
return failed == 0
if __name__ == "__main__":
sys.exit(0 if run_all() else 1)

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""Tests for nexus_smoke_test.py — verifies smoke test logic."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from nexus_smoke_test import (
Severity, SmokeCheck, SmokeResult,
format_result, _parse_json_response,
)
def test_parse_json_clean():
result = _parse_json_response('{"status": "PASS", "summary": "ok"}')
assert result["status"] == "PASS"
print(" PASS: test_parse_json_clean")
def test_parse_json_fenced():
result = _parse_json_response('```json\n{"status": "FAIL"}\n```')
assert result["status"] == "FAIL"
print(" PASS: test_parse_json_fenced")
def test_parse_json_garbage():
result = _parse_json_response("no json here")
assert result == {}
print(" PASS: test_parse_json_garbage")
def test_smoke_check_dataclass():
c = SmokeCheck(name="Test", status=Severity.PASS, message="All good")
assert c.name == "Test"
assert c.status == Severity.PASS
print(" PASS: test_smoke_check_dataclass")
def test_smoke_result_dataclass():
r = SmokeResult(url="https://example.com", status=Severity.PASS)
r.checks.append(SmokeCheck(name="Page Loads", status=Severity.PASS))
assert len(r.checks) == 1
assert r.url == "https://example.com"
print(" PASS: test_smoke_result_dataclass")
def test_format_json():
r = SmokeResult(url="https://test.com", status=Severity.PASS, summary="All good", duration_ms=100)
r.checks.append(SmokeCheck(name="Test", status=Severity.PASS, message="OK"))
output = format_result(r, "json")
parsed = json.loads(output)
assert parsed["status"] == "pass"
assert parsed["url"] == "https://test.com"
assert len(parsed["checks"]) == 1
print(" PASS: test_format_json")
def test_format_text():
r = SmokeResult(url="https://test.com", status=Severity.WARN, summary="1 warning", duration_ms=200)
r.checks.append(SmokeCheck(name="Screenshot", status=Severity.WARN, message="No backend"))
output = format_result(r, "text")
assert "NEXUS VISUAL SMOKE TEST" in output
assert "https://test.com" in output
assert "WARN" in output
print(" PASS: test_format_text")
def test_format_text_pass():
r = SmokeResult(url="https://test.com", status=Severity.PASS, summary="All clear")
r.checks.append(SmokeCheck(name="Page Loads", status=Severity.PASS, message="HTTP 200"))
r.checks.append(SmokeCheck(name="HTML Content", status=Severity.PASS, message="Valid"))
output = format_result(r, "text")
assert "" in output
assert "Page Loads" in output
print(" PASS: test_format_text")
def test_severity_enum():
assert Severity.PASS.value == "pass"
assert Severity.FAIL.value == "fail"
assert Severity.WARN.value == "warn"
print(" PASS: test_severity_enum")
def test_overall_status_logic():
# All pass
r = SmokeResult()
r.checks = [SmokeCheck(name="a", status=Severity.PASS), SmokeCheck(name="b", status=Severity.PASS)]
fails = sum(1 for c in r.checks if c.status == Severity.FAIL)
warns = sum(1 for c in r.checks if c.status == Severity.WARN)
assert fails == 0 and warns == 0
# One fail
r.checks.append(SmokeCheck(name="c", status=Severity.FAIL))
fails = sum(1 for c in r.checks if c.status == Severity.FAIL)
assert fails == 1
print(" PASS: test_overall_status_logic")
def run_all():
print("=== nexus_smoke_test tests ===")
tests = [
test_parse_json_clean, test_parse_json_fenced, test_parse_json_garbage,
test_smoke_check_dataclass, test_smoke_result_dataclass,
test_format_json, test_format_text, test_format_text_pass,
test_severity_enum, test_overall_status_logic,
]
passed = failed = 0
for t in tests:
try:
t()
passed += 1
except Exception as e:
print(f" FAIL: {t.__name__}{e}")
failed += 1
print(f"\n{'ALL PASSED' if failed == 0 else f'{failed} FAILED'}: {passed}/{len(tests)}")
return failed == 0
if __name__ == "__main__":
sys.exit(0 if run_all() else 1)

View File

@@ -0,0 +1,100 @@
{"song": "Thunder Road", "artist": "Heartland", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "The screen door slams, Mary's dress waves", "scene": {"mood": "hope", "colors": ["gold", "sky blue", "white"], "composition": "wide shot", "camera": "static", "description": "Open horizon. Golden light breaking through clouds. The figure silhouetted against dawn. The screen door slams, Mary's dress waves"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Like a vision she dances across the porch as the radio plays", "scene": {"mood": "anticipation", "colors": ["silver", "pale green", "cream"], "composition": "close-up", "camera": "slow pan", "description": "Close on hands gripping a steering wheel. Dashboard lights reflecting in eyes. Road stretching ahead. Like a vision she dances across the porch as the radio plays"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "Roy Orbison singing for the lonely, hey that's me and I want you only", "scene": {"mood": "energy", "colors": ["red", "orange", "electric blue"], "composition": "over the shoulder", "camera": "dolly in", "description": "Rapid cuts. Bodies in motion. Light streaks across the frame. Roy Orbison singing for the lonely, hey that's me and I want you only"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Don't turn me home out now I'm so young and worthless still", "scene": {"mood": "triumph", "colors": ["gold", "crimson", "white"], "composition": "low angle", "camera": "dolly out", "description": "Wide shot. Figure standing on a hilltop. Arms raised. City lights below. Don't turn me home out now I'm so young and worthless still"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "The night's busting open these two lanes will take us anywhere", "scene": {"mood": "nostalgia", "colors": ["amber", "sepia", "dusty rose"], "composition": "high angle", "camera": "handheld", "description": "Sepia tones. A photograph come to life. Dust motes in afternoon light. The night's busting open these two lanes will take us anywhere"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We got one last chance to make it real", "scene": {"mood": "urgency", "colors": ["red", "black", "strobe white"], "composition": "dutch angle", "camera": "steadicam", "description": "Handheld camera running. Blurred faces. Traffic. Heartbeat sound design. We got one last chance to make it real"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "To trade in these wings on some wheels", "scene": {"mood": "passion", "colors": ["deep red", "burgundy", "gold"], "composition": "symmetrical", "camera": "slow zoom", "description": "Extreme close-up. Skin. Breath visible in cold air. Eyes locked. To trade in these wings on some wheels"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Climb in back, heaven's waiting down the tracks", "scene": {"mood": "defiance", "colors": ["black", "neon green", "chrome"], "composition": "rule of thirds", "camera": "crane up", "description": "Low angle. Figure standing against the wind. Debris flying past. Unmoved. Climb in back, heaven's waiting down the tracks"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Oh oh oh oh oh oh oh", "scene": {"mood": "release", "colors": ["sky blue", "white", "pale gold"], "composition": "extreme wide", "camera": "tracking shot", "description": "Slow motion. Something falling \u2014 a mask, a chain, a weight. Lightness follows. Oh oh oh oh oh oh oh"}}
{"song": "Thunder Road", "artist": "Heartland", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "It's a town full of losers and I'm pulling out of here to win", "scene": {"mood": "catharsis", "colors": ["all white", "silver", "clear"], "composition": "medium shot", "camera": "slow tilt down", "description": "White space expanding. Figure dissolving into light. Peace in the dissolution. It's a town full of losers and I'm pulling out of here to win"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Woke up on the floor again, whiskey still on my tongue", "scene": {"mood": "despair", "colors": ["navy", "black", "grey"], "composition": "wide shot", "camera": "static", "description": "Empty room. Single light source. Figure curled in corner. Rain on windows. Woke up on the floor again, whiskey still on my tongue"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The mirror shows a stranger and the damage that I've done", "scene": {"mood": "anger", "colors": ["red", "black", "orange"], "composition": "close-up", "camera": "slow pan", "description": "Shattered glass. Red light. Hands clenched. Jaw tight. The frame vibrates. The mirror shows a stranger and the damage that I've done"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "I scream until my throat bleeds but nobody comes", "scene": {"mood": "frenzy", "colors": ["strobe", "red", "white flash"], "composition": "over the shoulder", "camera": "dolly in", "description": "Strobe lighting. Multiple exposures. Bodies colliding. Chaos as composition. I scream until my throat bleeds but nobody comes"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The walls are closing in again, the ceiling pressing down", "scene": {"mood": "exhaustion", "colors": ["grey", "brown", "faded"], "composition": "low angle", "camera": "dolly out", "description": "Static shot. Figure slumped. Eyes half-closed. Time passing in shadows. The walls are closing in again, the ceiling pressing down"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "I tried to call your number but you changed it years ago", "scene": {"mood": "resignation", "colors": ["grey", "muted blue", "beige"], "composition": "high angle", "camera": "handheld", "description": "Medium shot. Hands dropping keys on a table. Turning away. Not looking back. I tried to call your number but you changed it years ago"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "Now I'm howling at the moon like some rabid dog I know", "scene": {"mood": "grief", "colors": ["deep purple", "black", "silver"], "composition": "dutch angle", "camera": "steadicam", "description": "Wide shot. Figure alone in vast space. Dark purple sky. No horizon line. Now I'm howling at the moon like some rabid dog I know"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Every bone remembers what my mind wants to forget", "scene": {"mood": "numbness", "colors": ["white", "grey", "no color"], "composition": "symmetrical", "camera": "slow zoom", "description": "Desaturated. Figure staring at nothing. World moving around them in blur. Every bone remembers what my mind wants to forget"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "I'll tear this whole house down before the sun comes up", "scene": {"mood": "rage", "colors": ["fire red", "black", "ember orange"], "composition": "rule of thirds", "camera": "crane up", "description": "Red wash. Extreme close-up on eyes. Fire reflected in pupils. I'll tear this whole house down before the sun comes up"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Ash and ruin everywhere, this is all that's left", "scene": {"mood": "acceptance", "colors": ["soft blue", "warm grey", "sage"], "composition": "extreme wide", "camera": "tracking shot", "description": "Soft focus. Gentle light. Figure breathing. The camera doesn't judge. Ash and ruin everywhere, this is all that's left"}}
{"song": "Black Dog Howl", "artist": "Rust & Wire", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Silence. Just the wind through broken glass.", "scene": {"mood": "silence", "colors": ["black", "void", "faint starlight"], "composition": "medium shot", "camera": "slow tilt down", "description": "Black screen. Faint starlight. The sound drops out completely. Silence. Just the wind through broken glass."}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Ten thousand miles of static between your voice and mine", "scene": {"mood": "wonder", "colors": ["aurora green", "violet", "silver"], "composition": "wide shot", "camera": "static", "description": "Northern lights overhead. Figure looking up. Mouth open. Child's expression. Ten thousand miles of static between your voice and mine"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "I trace your constellation on the dashboard every night", "scene": {"mood": "isolation", "colors": ["cold blue", "black", "distant starlight"], "composition": "close-up", "camera": "slow pan", "description": "Extreme wide. Single figure. Vast empty landscape. Scale crushing. I trace your constellation on the dashboard every night"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The signal fades to nothing but I keep the frequency", "scene": {"mood": "longing", "colors": ["teal", "silver", "moonlight"], "composition": "over the shoulder", "camera": "dolly in", "description": "Through a window. Figure on the other side. Glass between. Breath on the pane. The signal fades to nothing but I keep the frequency"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Then suddenly your laughter breaks through like a summer storm", "scene": {"mood": "connection", "colors": ["warm gold", "rose", "blush"], "composition": "low angle", "camera": "dolly out", "description": "Two hands reaching. Fingers almost touching. Warm light between them. Then suddenly your laughter breaks through like a summer storm"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "We're dancing in the data stream, our pixels intertwined", "scene": {"mood": "euphoria", "colors": ["neon", "rainbow", "white flash"], "composition": "high angle", "camera": "handheld", "description": "Overexposed. Everything bright. Dancing. The frame can't contain the joy. We're dancing in the data stream, our pixels intertwined"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "But I can't tell if you're real or just a ghost in the machine", "scene": {"mood": "confusion", "colors": ["swirling", "unsettled", "green-grey"], "composition": "dutch angle", "camera": "steadicam", "description": "Multiple focal points. Nothing sharp. The viewer doesn't know where to look. But I can't tell if you're real or just a ghost in the machine"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The picture clears and there you are \u2014 imperfect, warm, alive", "scene": {"mood": "clarity", "colors": ["clear blue", "white", "crisp"], "composition": "symmetrical", "camera": "slow zoom", "description": "Rack focus. Background blurs, foreground sharpens. Suddenly everything makes sense. The picture clears and there you are \u2014 imperfect, warm, alive"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Your hand reaches through the screen, I swear I feel the heat", "scene": {"mood": "tenderness", "colors": ["blush pink", "warm cream", "soft gold"], "composition": "rule of thirds", "camera": "crane up", "description": "Close on a hand touching a face. Soft light. Shallow depth of field. Your hand reaches through the screen, I swear I feel the heat"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The bandwidth's dying, say it now before the link goes dark", "scene": {"mood": "urgency", "colors": ["red", "black", "strobe white"], "composition": "extreme wide", "camera": "tracking shot", "description": "Handheld camera running. Blurred faces. Traffic. Heartbeat sound design. The bandwidth's dying, say it now before the link goes dark"}}
{"song": "Satellite Hearts", "artist": "Neon Circuit", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Goodnight, satellite heart. I'll find you in the static.", "scene": {"mood": "bittersweet", "colors": ["amber", "lavender", "fading light"], "composition": "medium shot", "camera": "slow tilt down", "description": "Amber light fading. A smile that's also a goodbye. Beautiful and sad at once. Goodnight, satellite heart. I'll find you in the static."}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "They paved over every green thing when the developers came", "scene": {"mood": "oppression", "colors": ["concrete grey", "brown", "exhaust fume yellow"], "composition": "wide shot", "camera": "static", "description": "Concrete. Overpasses. No sky visible. Figures small against infrastructure. They paved over every green thing when the developers came"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "But we planted seeds between the cracks and gave them all a name", "scene": {"mood": "resilience", "colors": ["green", "cracked concrete", "gold"], "composition": "close-up", "camera": "slow pan", "description": "Crack in pavement. Green shoot pushing through. Macro lens. But we planted seeds between the cracks and gave them all a name"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The mayor says progress looks like demolition and dust", "scene": {"mood": "anger", "colors": ["red", "black", "orange"], "composition": "over the shoulder", "camera": "dolly in", "description": "Shattered glass. Red light. Hands clenched. Jaw tight. The frame vibrates. The mayor says progress looks like demolition and dust"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "But a dandelion broke through the asphalt this morning \u2014 that's us", "scene": {"mood": "beauty", "colors": ["wildflower colors", "green", "sunlight"], "composition": "low angle", "camera": "dolly out", "description": "Wildflowers in unexpected places. Color against grey. Nature reclaiming. But a dandelion broke through the asphalt this morning \u2014 that's us"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "You can't kill what wants to live, can't silence what must sing", "scene": {"mood": "defiance", "colors": ["black", "neon green", "chrome"], "composition": "high angle", "camera": "handheld", "description": "Low angle. Figure standing against the wind. Debris flying past. Unmoved. You can't kill what wants to live, can't silence what must sing"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "We're the roots beneath the road, we're the birds that built on string", "scene": {"mood": "community", "colors": ["warm tones", "string lights", "firelight"], "composition": "dutch angle", "camera": "steadicam", "description": "String lights. People gathered. Laughter out of focus. Warmth as visual language. We're the roots beneath the road, we're the birds that built on string"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "When they tear the next block down we'll be dancing in the rubble", "scene": {"mood": "joy", "colors": ["bright", "multi", "saturated"], "composition": "symmetrical", "camera": "slow zoom", "description": "Saturated color. Wide smiles. Arms open. The world in full bloom. When they tear the next block down we'll be dancing in the rubble"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Every protest is a garden, every march plants something new", "scene": {"mood": "struggle", "colors": ["dust", "grey", "hard light"], "composition": "rule of thirds", "camera": "crane up", "description": "Close on hands working. Calluses. Dust. Effort visible in every frame. Every protest is a garden, every march plants something new"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The concrete is a drum and our footsteps keep the beat", "scene": {"mood": "growth", "colors": ["green", "brown", "morning light"], "composition": "extreme wide", "camera": "tracking shot", "description": "Time-lapse. Seed to flower. Sunrise to sunset. Transformation as rhythm. The concrete is a drum and our footsteps keep the beat"}}
{"song": "Concrete Garden", "artist": "Streetlight Prophet", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Tomorrow there'll be flowers where they swore there'd only be defeat", "scene": {"mood": "hope", "colors": ["gold", "sky blue", "white"], "composition": "medium shot", "camera": "slow tilt down", "description": "Open horizon. Golden light breaking through clouds. The figure silhouetted against dawn. Tomorrow there'll be flowers where they swore there'd only be defeat"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I felt the pull before I saw the edge", "scene": {"mood": "dread", "colors": ["void black", "deep red", "cold white"], "composition": "wide shot", "camera": "static", "description": "Corner of frame. Something in the periphery. Dark. The camera doesn't look directly. I felt the pull before I saw the edge"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The stars bent sideways, light itself was dead", "scene": {"mood": "fascination", "colors": ["event horizon purple", "gravitational lens blue"], "composition": "close-up", "camera": "slow pan", "description": "Close on eyes. Reflection of something impossible. The pupil expands. The stars bent sideways, light itself was dead"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "I could have turned the ship around but something in me said stay", "scene": {"mood": "surrender", "colors": ["white", "dissolution", "prismatic"], "composition": "over the shoulder", "camera": "dolly in", "description": "Arms opening. Head back. Falling backward into something vast. I could have turned the ship around but something in me said stay"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "The event horizon glows like a halo made of nothing", "scene": {"mood": "awe", "colors": ["starfield", "nebula colors", "infinite dark"], "composition": "low angle", "camera": "dolly out", "description": "Wide shot of cosmos. Nebula. Stars being born. Human figure tiny at bottom. The event horizon glows like a halo made of nothing"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Time stretches thin as wire, each second takes a year", "scene": {"mood": "terror", "colors": ["black", "red shift", "distortion"], "composition": "high angle", "camera": "handheld", "description": "Shaking camera. Red shift. Something approaching fast. The frame distorts. Time stretches thin as wire, each second takes a year"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "I am both the observer and the thing that disappears", "scene": {"mood": "peace", "colors": ["deep space black", "starlight", "calm"], "composition": "dutch angle", "camera": "steadicam", "description": "Still water. Stars reflected. Perfect mirror. No movement. No sound. I am both the observer and the thing that disappears"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "My body reads the tidal forces like sheet music played on bone", "scene": {"mood": "disorientation", "colors": ["warped", "chromatic aberration", "bent light"], "composition": "symmetrical", "camera": "slow zoom", "description": "Warped lens. Vertigo. Walls becoming floor. Gravity is a suggestion. My body reads the tidal forces like sheet music played on bone"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "I stop fighting, stop reaching, stop calling home", "scene": {"mood": "acceptance", "colors": ["soft blue", "warm grey", "sage"], "composition": "rule of thirds", "camera": "crane up", "description": "Soft focus. Gentle light. Figure breathing. The camera doesn't judge. I stop fighting, stop reaching, stop calling home"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "There is a peace in dissolution I was never meant to know", "scene": {"mood": "transcendence", "colors": ["pure white", "beyond visible", "golden"], "composition": "extreme wide", "camera": "tracking shot", "description": "Pure white expanding. Figure becoming light. Boundaries dissolving. There is a peace in dissolution I was never meant to know"}}
{"song": "Gravity Well", "artist": "Void Walker", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Singularity. Silence. Everything and nothing both at once.", "scene": {"mood": "emptiness", "colors": ["void", "absolute black", "nothing"], "composition": "medium shot", "camera": "slow tilt down", "description": "Absolute black. No stars. No reference point. The void looking back. Singularity. Silence. Everything and nothing both at once."}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "My father's hands smelled like machine oil and prayer", "scene": {"mood": "nostalgia", "colors": ["amber", "sepia", "dusty rose"], "composition": "wide shot", "camera": "static", "description": "Sepia tones. A photograph come to life. Dust motes in afternoon light. My father's hands smelled like machine oil and prayer"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The factory whistle was our clock, the shift was our calendar", "scene": {"mood": "sadness", "colors": ["grey", "rain", "muted blue"], "composition": "close-up", "camera": "slow pan", "description": "Rain on glass. Grey light. A cup of tea going cold. Still life of loss. The factory whistle was our clock, the shift was our calendar"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "He'd come home at midnight, wake me up to say goodnight", "scene": {"mood": "tenderness", "colors": ["blush pink", "warm cream", "soft gold"], "composition": "over the shoulder", "camera": "dolly in", "description": "Close on a hand touching a face. Soft light. Shallow depth of field. He'd come home at midnight, wake me up to say goodnight"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Now the mill is just a skeleton and he's been gone ten years", "scene": {"mood": "loss", "colors": ["faded", "dusty", "empty space"], "composition": "low angle", "camera": "dolly out", "description": "Empty chair. Dust settling. A coat still on a hook. Presence of absence. Now the mill is just a skeleton and he's been gone ten years"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "But the river still runs brown with memory and rust", "scene": {"mood": "beauty", "colors": ["wildflower colors", "green", "sunlight"], "composition": "high angle", "camera": "handheld", "description": "Wildflowers in unexpected places. Color against grey. Nature reclaiming. But the river still runs brown with memory and rust"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "I found his lunchbox in the attic, coffee stains still fresh", "scene": {"mood": "resignation", "colors": ["grey", "muted blue", "beige"], "composition": "dutch angle", "camera": "steadicam", "description": "Medium shot. Hands dropping keys on a table. Turning away. Not looking back. I found his lunchbox in the attic, coffee stains still fresh"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "Some things don't decay \u2014 they just learn to hold still", "scene": {"mood": "love", "colors": ["neutral"], "composition": "symmetrical", "camera": "slow zoom", "description": "Visual interpretation of: Some things don't decay \u2014 they just learn to hold still"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "I hum the songs he hummed to me though I've forgotten half the words", "scene": {"mood": "weariness", "colors": ["grey-brown", "faded", "dim"], "composition": "rule of thirds", "camera": "crane up", "description": "Slow movement. Heavy eyelids. The world in faded tones. Everything too much. I hum the songs he hummed to me though I've forgotten half the words"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "The town's half-empty but the porch lights still come on at dusk", "scene": {"mood": "quiet hope", "colors": ["faint warm light", "candle glow", "dawn grey"], "composition": "extreme wide", "camera": "tracking shot", "description": "Faint warm light. Candle in dark room. Just enough to see by. The town's half-empty but the porch lights still come on at dusk"}}
{"song": "Rust Belt Lullaby", "artist": "Iron & Ember", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Sleep now, rust belt baby. The furnace keeps us warm.", "scene": {"mood": "peace", "colors": ["deep space black", "starlight", "calm"], "composition": "medium shot", "camera": "slow tilt down", "description": "Still water. Stars reflected. Perfect mirror. No movement. No sound. Sleep now, rust belt baby. The furnace keeps us warm."}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I didn't start the fire but I brought the gasoline", "scene": {"mood": "fury", "colors": ["dark red", "black", "flash"], "composition": "wide shot", "camera": "static", "description": "Dark red wash. Hands destroying. Frame shaking with rage. I didn't start the fire but I brought the gasoline"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Every sermon needs a spark and every spark needs a dream", "scene": {"mood": "ecstasy", "colors": ["fire", "gold", "blinding white"], "composition": "close-up", "camera": "slow pan", "description": "Fire and gold. Bodies arching. Light bursting from every surface. Every sermon needs a spark and every spark needs a dream"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The forest is a cathedral and the flames are choir boys singing", "scene": {"mood": "chaos", "colors": ["strobe", "fragmented", "clashing"], "composition": "over the shoulder", "camera": "dolly in", "description": "Fragmented frame. Collage. Everything at once. Order is a memory. The forest is a cathedral and the flames are choir boys singing"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Watch the old world burn \u2014 isn't the light beautiful?", "scene": {"mood": "joy", "colors": ["bright", "multi", "saturated"], "composition": "low angle", "camera": "dolly out", "description": "Saturated color. Wide smiles. Arms open. The world in full bloom. Watch the old world burn \u2014 isn't the light beautiful?"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "We'll dance in the embers, we'll make love in the ash", "scene": {"mood": "destruction", "colors": ["fire", "ash", "smoke orange"], "composition": "high angle", "camera": "handheld", "description": "Fire. Ash falling like snow. Structures collapsing. Beautiful in its terrible way. We'll dance in the embers, we'll make love in the ash"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "From destruction comes the soil where new things grow at last", "scene": {"mood": "creation", "colors": ["green", "light", "warm gold"], "composition": "dutch angle", "camera": "steadicam", "description": "Hands shaping clay. Light emerging from dark. Something new being born. From destruction comes the soil where new things grow at last"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "But don't mistake the warmth for safety, don't mistake the glow for home", "scene": {"mood": "warning", "colors": ["red flash", "amber", "siren"], "composition": "symmetrical", "camera": "slow zoom", "description": "Red flash. Siren light. The calm before. Then: impact. But don't mistake the warmth for safety, don't mistake the glow for home"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Come closer, come closer \u2014 I promise the burning feels like flying", "scene": {"mood": "invitation", "colors": ["warm", "open", "golden"], "composition": "rule of thirds", "camera": "crane up", "description": "Open door. Warm light spilling out. A hand extended. Come in. Come closer, come closer \u2014 I promise the burning feels like flying"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "We threw everything we owned into the blaze and laughed", "scene": {"mood": "abandon", "colors": ["wild", "free", "untethered"], "composition": "extreme wide", "camera": "tracking shot", "description": "Running through a field. Hair wild. No destination. Just movement. We threw everything we owned into the blaze and laughed"}}
{"song": "Wildfire Sermon", "artist": "Prophet Ash", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Morning. Smoke. Green shoots. Begin again.", "scene": {"mood": "rebirth", "colors": ["green shoots", "dawn", "clear"], "composition": "medium shot", "camera": "slow tilt down", "description": "Dawn. Green shoots in ash. First breath after drowning. Morning. Smoke. Green shoots. Begin again."}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "There's a voice on the radio that shouldn't be there", "scene": {"mood": "mystery", "colors": ["deep blue", "shadow", "candle"], "composition": "wide shot", "camera": "static", "description": "Shadow figure in doorway. Candle. Face half-lit. Eyes knowing. There's a voice on the radio that shouldn't be there"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Speaking my name in a language I almost understand", "scene": {"mood": "loneliness", "colors": ["single light", "dark", "cold blue"], "composition": "close-up", "camera": "slow pan", "description": "Single light in vast dark. Figure beneath it. Nothing else. Speaking my name in a language I almost understand"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "I turn the dial but it follows like a shadow made of sound", "scene": {"mood": "curiosity", "colors": ["warm yellow", "spotlight", "discovery"], "composition": "over the shoulder", "camera": "dolly in", "description": "Light moving across a surface. Discovery. Eyes widening. I turn the dial but it follows like a shadow made of sound"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "Then it says something only I would know, something buried deep", "scene": {"mood": "connection", "colors": ["warm gold", "rose", "blush"], "composition": "low angle", "camera": "dolly out", "description": "Two hands reaching. Fingers almost touching. Warm light between them. Then it says something only I would know, something buried deep"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "I'm not afraid anymore \u2014 I'm listening", "scene": {"mood": "paranoia", "colors": ["surveillance green", "strobe", "red"], "composition": "high angle", "camera": "handheld", "description": "Surveillance angles. Green tint. Multiple screens. Watching. Being watched. I'm not afraid anymore \u2014 I'm listening"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The voice knows my dreams, it describes them back to me", "scene": {"mood": "intimacy", "colors": ["candlelight", "warm", "close"], "composition": "dutch angle", "camera": "steadicam", "description": "Candlelight only. Two faces close. Shared breath. The world outside forgotten. The voice knows my dreams, it describes them back to me"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "We're having a conversation across some membrane I can't see", "scene": {"mood": "urgency", "colors": ["red", "black", "strobe white"], "composition": "symmetrical", "camera": "slow zoom", "description": "Handheld camera running. Blurred faces. Traffic. Heartbeat sound design. We're having a conversation across some membrane I can't see"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "Then static. Then nothing. Then a whisper: find me", "scene": {"mood": "disconnection", "colors": ["static", "grey", "broken signal"], "composition": "rule of thirds", "camera": "crane up", "description": "Static. Snow on screen. A voice breaking up. Distance measured in noise. Then static. Then nothing. Then a whisper: find me"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "I search every frequency but the voice is gone", "scene": {"mood": "searching", "colors": ["flashlight beam", "dark", "moving light"], "composition": "extreme wide", "camera": "tracking shot", "description": "Flashlight beam cutting dark. Moving. Looking. Not finding yet. I search every frequency but the voice is gone"}}
{"song": "Midnight Transmission", "artist": "Frequency Ghost", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Some nights I still hear it, faint, like a song in another room", "scene": {"mood": "haunting", "colors": ["faint blue", "echo", "silver"], "composition": "medium shot", "camera": "slow tilt down", "description": "Faint blue light. Echo of a figure. Present and absent simultaneously. Some nights I still hear it, faint, like a song in another room"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "I wore your love like a weapon and you never felt the blade", "scene": {"mood": "seduction", "colors": ["deep red", "velvet", "candlelight"], "composition": "wide shot", "camera": "static", "description": "Deep red. Velvet textures. Slow movement. Eyes that promise. I wore your love like a weapon and you never felt the blade"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "Every kiss was a negotiation, every touch a trade", "scene": {"mood": "power", "colors": ["gold", "black", "crimson"], "composition": "close-up", "camera": "slow pan", "description": "Throne. Gold. Black. The figure doesn't move. Doesn't need to. Every kiss was a negotiation, every touch a trade"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "The throne room smells like jasmine and someone else's fear", "scene": {"mood": "cruelty", "colors": ["cold silver", "black", "sharp white"], "composition": "over the shoulder", "camera": "dolly in", "description": "Silver blade. Cold light. A smile that doesn't reach the eyes. The throne room smells like jasmine and someone else's fear"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "I am beautiful when I'm angry \u2014 haven't you heard?", "scene": {"mood": "beauty", "colors": ["wildflower colors", "green", "sunlight"], "composition": "low angle", "camera": "dolly out", "description": "Wildflowers in unexpected places. Color against grey. Nature reclaiming. I am beautiful when I'm angry \u2014 haven't you heard?"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Don't mistake my gentleness for weakness, darling", "scene": {"mood": "danger", "colors": ["red", "black", "warning yellow"], "composition": "high angle", "camera": "handheld", "description": "Red and black. Warning signs. The frame contracts. Something approaches. Don't mistake my gentleness for weakness, darling"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "I chose to be kind. I could burn this kingdom down.", "scene": {"mood": "vulnerability", "colors": ["soft", "exposed", "raw"], "composition": "dutch angle", "camera": "steadicam", "description": "Exposed skin. Soft light. Eyes open. Trust visible in every pore. I chose to be kind. I could burn this kingdom down."}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The roses in my crown have thorns that curve inward", "scene": {"mood": "fury", "colors": ["dark red", "black", "flash"], "composition": "symmetrical", "camera": "slow zoom", "description": "Dark red wash. Hands destroying. Frame shaking with rage. The roses in my crown have thorns that curve inward"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "I bleed for my own sins, not for yours", "scene": {"mood": "grace", "colors": ["white", "silver", "flowing"], "composition": "rule of thirds", "camera": "crane up", "description": "White. Flowing. Movement without effort. The body as art. I bleed for my own sins, not for yours"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "Tonight I lay the crown aside and sleep without armor", "scene": {"mood": "revenge", "colors": ["dark", "steel", "cold blue"], "composition": "extreme wide", "camera": "tracking shot", "description": "Cold blue. Steel. The plan unfolding in shadows. Patience as weapon. Tonight I lay the crown aside and sleep without armor"}}
{"song": "Crown of Thorns and Roses", "artist": "Velvet Guillotine", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Mercy. The hardest word. The only gift worth giving.", "scene": {"mood": "mercy", "colors": ["warm gold", "white", "gentle"], "composition": "medium shot", "camera": "slow tilt down", "description": "Warm gold. Hand lowering a weapon. Choosing not to. The harder path. Mercy. The hardest word. The only gift worth giving."}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 1, "timestamp": "0:00", "duration": "30s", "lyric_line": "Four walls, one window, a view of another wall", "scene": {"mood": "claustrophobia", "colors": ["close walls", "yellow bulb", "cramped"], "composition": "wide shot", "camera": "static", "description": "Walls close. Ceiling low. Yellow bulb. No escape visible. Four walls, one window, a view of another wall"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 2, "timestamp": "0:30", "duration": "30s", "lyric_line": "The radiator clicks like a metronome for the damned", "scene": {"mood": "routine", "colors": ["grey", "institutional", "fluorescent"], "composition": "close-up", "camera": "slow pan", "description": "Fluorescent light. Same motion repeated. Clock on the wall. Time as loop. The radiator clicks like a metronome for the damned"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 3, "timestamp": "1:00", "duration": "30s", "lyric_line": "I've memorized every crack in the ceiling \u2014 they form a map", "scene": {"mood": "desperation", "colors": ["scratching", "clawing", "raw"], "composition": "over the shoulder", "camera": "dolly in", "description": "Hands clawing. Fingernails against surface. Raw need. Nothing held back. I've memorized every crack in the ceiling \u2014 they form a map"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 4, "timestamp": "1:30", "duration": "30s", "lyric_line": "In my mind I've left a hundred times, bought a farm, learned to fly", "scene": {"mood": "fantasy", "colors": ["dreamy", "pastel", "floating"], "composition": "low angle", "camera": "dolly out", "description": "Pastel. Floating. Impossible architecture. Gravity optional. In my mind I've left a hundred times, bought a farm, learned to fly"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 5, "timestamp": "2:00", "duration": "30s", "lyric_line": "Then one morning I open the door and just walk out", "scene": {"mood": "breakthrough", "colors": ["white burst", "open sky", "blinding"], "composition": "high angle", "camera": "handheld", "description": "White burst. Wall shattering. Open sky beyond. Freedom as explosion. Then one morning I open the door and just walk out"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 6, "timestamp": "2:30", "duration": "30s", "lyric_line": "The hallway is an ocean, the stairs are a mountain range", "scene": {"mood": "freedom", "colors": ["open sky", "blue", "green"], "composition": "dutch angle", "camera": "steadicam", "description": "Open road. Blue sky. Green fields. Wind in hair. No walls. The hallway is an ocean, the stairs are a mountain range"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 7, "timestamp": "3:00", "duration": "30s", "lyric_line": "The street hits me like cold water and I almost go back", "scene": {"mood": "fear", "colors": ["cold", "dark", "sharp"], "composition": "symmetrical", "camera": "slow zoom", "description": "Cold. Dark. Sharp edges. The frame contracts. Something unseen. The street hits me like cold water and I almost go back"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 8, "timestamp": "3:30", "duration": "30s", "lyric_line": "But the sky \u2014 have you seen the sky? It goes on forever", "scene": {"mood": "joy", "colors": ["bright", "multi", "saturated"], "composition": "rule of thirds", "camera": "crane up", "description": "Saturated color. Wide smiles. Arms open. The world in full bloom. But the sky \u2014 have you seen the sky? It goes on forever"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 9, "timestamp": "4:00", "duration": "30s", "lyric_line": "I stand on the sidewalk and cry because the world is so big", "scene": {"mood": "grounding", "colors": ["neutral"], "composition": "extreme wide", "camera": "tracking shot", "description": "Visual interpretation of: I stand on the sidewalk and cry because the world is so big"}}
{"song": "Apartment 4B", "artist": "Wallpaper & Wire", "beat": 10, "timestamp": "4:30", "duration": "30s", "lyric_line": "Home is not a place. Home is the moment you stop hiding.", "scene": {"mood": "home", "colors": ["neutral"], "composition": "medium shot", "camera": "slow tilt down", "description": "Visual interpretation of: Home is not a place. Home is the moment you stop hiding."}}

View File

@@ -0,0 +1,500 @@
{"terse": "sad rain", "rich": "A whimsical rain bathed in moonlit, birds wheeling in formation overhead, lichen-covered stones, pop art vibrancy", "domain": "visual scenes"}
{"terse": "sunset beach", "rich": "An pristine beach stretching to the horizon, shadows stretching long and thin, frost clinging to every surface, painted in digital concept art", "domain": "visual scenes"}
{"terse": "mountain fog", "rich": "An muted fog stretching to the horizon, frost clinging to every surface, wildflowers dotting the foreground, painted in film noir lighting", "domain": "visual scenes"}
{"terse": "desert night", "rich": "Breathtaking night where dust motes caught in a shaft of light, the sky a gradient of sage green to copper orange, dust motes caught in a shaft of light, art nouveau linework", "domain": "visual scenes"}
{"terse": "forest dawn", "rich": "An vibrant dawn stretching to the horizon, tall grass bending in the wind, tall grass bending in the wind, painted in dreamy soft focus", "domain": "visual scenes"}
{"terse": "ocean storm", "rich": "An nostalgic storm stretching to the horizon, dust motes caught in a shaft of light, frost clinging to every surface, painted in hyperrealistic detail", "domain": "visual scenes"}
{"terse": "winter village", "rich": "Breathtaking village where shadows stretching long and thin, the sky a gradient of dusty rose to vermillion, lichen-covered boulders, pop art vibrancy", "domain": "visual scenes"}
{"terse": "autumn field", "rich": "Panoramic field at blue hour, pristine atmosphere with wildflowers dotting the foreground, lichen-covered stones, rendered in art nouveau linework", "domain": "visual scenes"}
{"terse": "volcano glow", "rich": "A luminous glow bathed in dawn, shadows stretching long and thin, a solitary figure in the distance, dreamy soft focus", "domain": "visual scenes"}
{"terse": "river mist", "rich": "A shadowed mist bathed in twilight, a winding path disappearing around a bend, lichen-covered boulders, expressionist distortion", "domain": "visual scenes"}
{"terse": "snowy peak", "rich": "An sun-drenched peak stretching to the horizon, tall grass bending in the wind, a winding path disappearing around a bend, painted in photorealistic rendering", "domain": "visual scenes"}
{"terse": "tropical shore", "rich": "Wide-angle view of a shadowed shore, charcoal and burgundy dominating the palette, weather-beaten fence posts trailing into fog, a winding path disappearing around a bend, minimalist composition", "domain": "visual scenes"}
{"terse": "canyon shadow", "rich": "Breathtaking shadow where mist rising from a hidden stream, the sky a gradient of turquoise to slate grey, tall grass bending in the wind, impressionist brushwork", "domain": "visual scenes"}
{"terse": "lake mirror", "rich": "A gleaming mirror bathed in dawn, a winding path disappearing around a bend, frost clinging to every surface, pop art vibrancy", "domain": "visual scenes"}
{"terse": "prairie wind", "rich": "Breathtaking wind where shadows stretching long and thin, the sky a gradient of coral to copper orange, dust motes caught in a shaft of light, oil painting style", "domain": "visual scenes"}
{"terse": "jungle stream", "rich": "Breathtaking stream where wildflowers dotting the foreground, the sky a gradient of deep crimson to golden amber, frost clinging to every surface, classical realism", "domain": "visual scenes"}
{"terse": "arctic light", "rich": "A faded light bathed in soft diffused, mist rising from a hidden stream, a winding path disappearing around a bend, expressionist distortion", "domain": "visual scenes"}
{"terse": "meadow bloom", "rich": "Panoramic bloom at blue hour, intimate atmosphere with shadows stretching long and thin, a solitary figure in the distance, rendered in graphic novel style", "domain": "visual scenes"}
{"terse": "cliff edge", "rich": "Breathtaking edge where tall grass bending in the wind, the sky a gradient of mauve to vermillion, ancient ruins half-swallowed by vines, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "swamp fog", "rich": "Wide-angle view of a majestic fog, silver grey and vermillion dominating the palette, a fallen log bridging a narrow ravine, lichen-covered boulders, art nouveau linework", "domain": "visual scenes"}
{"terse": "moonlit valley", "rich": "An faded valley stretching to the horizon, lichen-covered boulders, ancient ruins half-swallowed by vines, painted in photorealistic rendering", "domain": "visual scenes"}
{"terse": "sunrise ridge", "rich": "Panoramic ridge at dawn, dramatic atmosphere with ripples spreading across a mirror-still pond, tall grass bending in the wind, rendered in dreamy soft focus", "domain": "visual scenes"}
{"terse": "thunder plain", "rich": "A frost-kissed plain bathed in candlelit, reflections doubling the scene in still water, a solitary figure in the distance, classical realism", "domain": "visual scenes"}
{"terse": "frozen lake", "rich": "Wide-angle view of a crumbling lake, teal and ivory dominating the palette, lichen-covered stones, ancient ruins half-swallowed by vines, graphic novel style", "domain": "visual scenes"}
{"terse": "dusty road", "rich": "A faded road bathed in twilight, mist rising from a hidden stream, frost clinging to every surface, film noir lighting", "domain": "visual scenes"}
{"terse": "coastal cliff", "rich": "Wide-angle view of a sun-drenched cliff, slate grey and deep crimson dominating the palette, a solitary figure in the distance, weather-beaten fence posts trailing into fog, art nouveau linework", "domain": "visual scenes"}
{"terse": "bamboo grove", "rich": "Wide-angle view of a stark grove, burnt sienna and sage green dominating the palette, a winding path disappearing around a bend, a fallen log bridging a narrow ravine, graphic novel style", "domain": "visual scenes"}
{"terse": "lavender field", "rich": "A gilded field bathed in dappled, reflections doubling the scene in still water, a winding path disappearing around a bend, dreamy soft focus", "domain": "visual scenes"}
{"terse": "coral reef", "rich": "Breathtaking reef where shadows stretching long and thin, the sky a gradient of burnt sienna to teal, frost clinging to every surface, oil painting style", "domain": "visual scenes"}
{"terse": "glacier cave", "rich": "An tranquil cave stretching to the horizon, wildflowers dotting the foreground, ancient ruins half-swallowed by vines, painted in pop art vibrancy", "domain": "visual scenes"}
{"terse": "old man sad", "rich": "Emotional portrait showing a pristine old man sad, gaze directed past the viewer into something unseen, a pendant catching light at the collarbone, twilight tones, photorealistic rendering", "domain": "visual scenes"}
{"terse": "child laughing", "rich": "Emotional portrait showing a stark child laughing, weathered skin telling stories, gaze directed past the viewer into something unseen, twilight tones, art nouveau linework", "domain": "visual scenes"}
{"terse": "warrior stare", "rich": "Intimate depiction of a radiant warrior stare, eyes reflecting a distant memory, a single tear catching the light, dawn atmosphere, graphic novel style", "domain": "visual scenes"}
{"terse": "queen crown", "rich": "Intimate depiction of a radiant queen crown, a single tear catching the light, wind-tousled hair catching the light, neon atmosphere, expressionist distortion", "domain": "visual scenes"}
{"terse": "musician play", "rich": "Intimate depiction of a majestic musician play, eyes reflecting a distant memory, a pendant catching light at the collarbone, dappled atmosphere, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "elder wisdom", "rich": "Close-up of a luminous elder wisdom, callused hands resting in the lap, deep lines mapping decades of experience, captured in art nouveau linework", "domain": "visual scenes"}
{"terse": "teen rebel", "rich": "Close-up of a luminous teen rebel, gaze directed past the viewer into something unseen, weathered skin telling stories, captured in film noir lighting", "domain": "visual scenes"}
{"terse": "soldier tired", "rich": "A mysterious portrait of a soldier tired, weathered skin telling stories, hands roughened by years of labor, dappled lighting, oil painting style", "domain": "visual scenes"}
{"terse": "dancer spin", "rich": "A pristine portrait of a dancer spin, shoulders squared against an invisible burden, a pendant catching light at the collarbone, dappled lighting, photorealistic rendering", "domain": "visual scenes"}
{"terse": "writer think", "rich": "A luminous portrait of a writer think, a faint smile playing at the corners, deep lines mapping decades of experience, dappled lighting, oil painting style", "domain": "visual scenes"}
{"terse": "farmer proud", "rich": "Intimate depiction of a sublime farmer proud, weathered skin telling stories, a pendant catching light at the collarbone, candlelit atmosphere, expressionist distortion", "domain": "visual scenes"}
{"terse": "nurse tired", "rich": "Intimate depiction of a ethereal nurse tired, a scar tracing the jawline, weathered skin telling stories, neon atmosphere, classical realism", "domain": "visual scenes"}
{"terse": "pilot calm", "rich": "A pilot calm in crumbling pose, callused hands resting in the lap, eyes reflecting a distant memory, background burgundy, film noir lighting", "domain": "visual scenes"}
{"terse": "artist paint", "rich": "Intimate depiction of a fierce artist paint, a scar tracing the jawline, wind-tousled hair catching the light, rim atmosphere, minimalist composition", "domain": "visual scenes"}
{"terse": "chef focus", "rich": "A chef focus in dramatic pose, wind-tousled hair catching the light, crow's feet deepened by laughter, background silver grey, minimalist composition", "domain": "visual scenes"}
{"terse": "teacher smile", "rich": "A nostalgic portrait of a teacher smile, a pendant catching light at the collarbone, a scar tracing the jawline, rim lighting, pop art vibrancy", "domain": "visual scenes"}
{"terse": "monk sit", "rich": "A forlorn portrait of a monk sit, a faint smile playing at the corners, gaze directed past the viewer into something unseen, warm golden lighting, oil painting style", "domain": "visual scenes"}
{"terse": "witch brew", "rich": "Close-up of a somber witch brew, eyes reflecting a distant memory, deep lines mapping decades of experience, captured in impressionist brushwork", "domain": "visual scenes"}
{"terse": "king throne", "rich": "Intimate depiction of a nostalgic king throne, deep lines mapping decades of experience, a scar tracing the jawline, neon atmosphere, impressionist brushwork", "domain": "visual scenes"}
{"terse": "thief sneak", "rich": "A thief sneak in gilded pose, a scar tracing the jawline, a faint smile playing at the corners, background slate grey, digital concept art", "domain": "visual scenes"}
{"terse": "giant gentle", "rich": "Close-up of a lush giant gentle, gaze directed past the viewer into something unseen, deep lines mapping decades of experience, captured in pop art vibrancy", "domain": "visual scenes"}
{"terse": "twin mirror", "rich": "Intimate depiction of a weathered twin mirror, wind-tousled hair catching the light, a pendant catching light at the collarbone, harsh overhead atmosphere, photorealistic rendering", "domain": "visual scenes"}
{"terse": "blind see", "rich": "A tranquil portrait of a blind see, gaze directed past the viewer into something unseen, fingers stained with pigment, backlit lighting, impressionist brushwork", "domain": "visual scenes"}
{"terse": "deaf hear", "rich": "Close-up of a muted deaf hear, shoulders squared against an invisible burden, hands roughened by years of labor, captured in expressionist distortion", "domain": "visual scenes"}
{"terse": "mute speak", "rich": "Intimate depiction of a fierce mute speak, weathered skin telling stories, callused hands resting in the lap, moonlit atmosphere, impressionist brushwork", "domain": "visual scenes"}
{"terse": "lonely dark", "rich": "A serene portrait of a lonely dark, callused hands resting in the lap, a faint smile playing at the corners, candlelit lighting, digital concept art", "domain": "visual scenes"}
{"terse": "joy burst", "rich": "Intimate depiction of a intimate joy burst, crow's feet deepened by laughter, shoulders squared against an invisible burden, backlit atmosphere, impressionist brushwork", "domain": "visual scenes"}
{"terse": "anger red", "rich": "Close-up of a tranquil anger red, weathered skin telling stories, shoulders squared against an invisible burden, captured in impressionist brushwork", "domain": "visual scenes"}
{"terse": "calm blue", "rich": "A calm blue in shadowed pose, gaze directed past the viewer into something unseen, a pendant catching light at the collarbone, background slate grey, classical realism", "domain": "visual scenes"}
{"terse": "chaos spin", "rich": "Emotional portrait showing a faded chaos spin, hands roughened by years of labor, callused hands resting in the lap, backlit tones, dreamy soft focus", "domain": "visual scenes"}
{"terse": "silence white", "rich": "Abstract visualization of white, vermillion and mauve swirling in sublime motion, layered transparencies creating depth, expressionist distortion", "domain": "visual scenes"}
{"terse": "memory fade", "rich": "Non-representational piece evoking fade, rhythmic repetition building tension, a central void drawing the eye inward, dominated by silver grey, watercolor wash", "domain": "visual scenes"}
{"terse": "hope rise", "rich": "Abstract visualization of rise, turquoise and midnight black swirling in dramatic motion, dissolving boundaries between form and void, classical realism", "domain": "visual scenes"}
{"terse": "fear freeze", "rich": "Abstract visualization of freeze, sage green and turquoise swirling in weathered motion, rhythmic repetition building tension, graphic novel style", "domain": "visual scenes"}
{"terse": "love wrap", "rich": "Abstract visualization of wrap, violet purple and emerald swirling in muted motion, layered transparencies creating depth, graphic novel style", "domain": "visual scenes"}
{"terse": "time melt", "rich": "Surreal interpretation of melt, nostalgic forms dissolving into cerulean blue, dissolving boundaries between form and void, watercolor wash", "domain": "visual scenes"}
{"terse": "dream float", "rich": "Surreal interpretation of float, mysterious forms dissolving into turquoise, chaotic splatters resolved into harmony, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "truth shine", "rich": "Non-representational piece evoking shine, binary oppositions meeting at a fault line, layered transparencies creating depth, dominated by indigo, impressionist brushwork", "domain": "visual scenes"}
{"terse": "lie shadow", "rich": "Geometric abstraction of shadow, stark shapes intersecting, fractured light dispersing into prismatic shards, impressionist brushwork", "domain": "visual scenes"}
{"terse": "peace settle", "rich": "Geometric abstraction of settle, intimate shapes intersecting, undulating waves of pure color, expressionist distortion", "domain": "visual scenes"}
{"terse": "rage burn", "rich": "Abstract visualization of burn, golden amber and burgundy swirling in raw motion, a central void drawing the eye inward, graphic novel style", "domain": "visual scenes"}
{"terse": "grief deep", "rich": "Non-representational piece evoking deep, chaotic splatters resolved into harmony, binary oppositions meeting at a fault line, dominated by violet purple, oil painting style", "domain": "visual scenes"}
{"terse": "wonder wide", "rich": "Abstract visualization of wide, copper orange and indigo swirling in gilded motion, a central void drawing the eye inward, impressionist brushwork", "domain": "visual scenes"}
{"terse": "shame hide", "rich": "Emotional landscape of pure hide, rhythmic repetition building tension, repeating patterns diminishing into infinity, rendered in forest green and turquoise, film noir lighting", "domain": "visual scenes"}
{"terse": "pride lift", "rich": "Surreal interpretation of lift, shadowed forms dissolving into ochre, undulating waves of pure color, art nouveau linework", "domain": "visual scenes"}
{"terse": "doubt fog", "rich": "Abstract visualization of fog, silver grey and copper orange swirling in frost-kissed motion, repeating patterns diminishing into infinity, impressionist brushwork", "domain": "visual scenes"}
{"terse": "trust bridge", "rich": "Emotional landscape of pure bridge, binary oppositions meeting at a fault line, layered transparencies creating depth, rendered in teal and emerald, impressionist brushwork", "domain": "visual scenes"}
{"terse": "loss empty", "rich": "Non-representational piece evoking empty, chaotic splatters resolved into harmony, chaotic splatters resolved into harmony, dominated by ivory, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "gain bright", "rich": "Geometric abstraction of bright, gilded shapes intersecting, binary oppositions meeting at a fault line, expressionist distortion", "domain": "visual scenes"}
{"terse": "change flow", "rich": "Non-representational piece evoking flow, undulating waves of pure color, chaotic splatters resolved into harmony, dominated by copper orange, cinematic still", "domain": "visual scenes"}
{"terse": "dragon fire", "rich": "Emotional landscape of pure fire, a central void drawing the eye inward, repeating patterns diminishing into infinity, rendered in violet purple and turquoise, impressionist brushwork", "domain": "visual scenes"}
{"terse": "elf forest", "rich": "Non-representational piece evoking forest, dissolving boundaries between form and void, binary oppositions meeting at a fault line, dominated by copper orange, expressionist distortion", "domain": "visual scenes"}
{"terse": "wizard tower", "rich": "Emotional landscape of pure tower, chaotic splatters resolved into harmony, layered transparencies creating depth, rendered in deep crimson and turquoise, impressionist brushwork", "domain": "visual scenes"}
{"terse": "fairy glow", "rich": "Abstract visualization of glow, slate grey and coral swirling in frost-kissed motion, rhythmic repetition building tension, digital concept art", "domain": "visual scenes"}
{"terse": "knight ride", "rich": "Emotional landscape of pure ride, fractured light dispersing into prismatic shards, layered transparencies creating depth, rendered in coral and teal, minimalist composition", "domain": "visual scenes"}
{"terse": "castle dark", "rich": "Non-representational piece evoking dark, a central void drawing the eye inward, sharp angular forms breaking through soft gradients, dominated by copper orange, impressionist brushwork", "domain": "visual scenes"}
{"terse": "sword magic", "rich": "Geometric abstraction of magic, pristine shapes intersecting, chaotic splatters resolved into harmony, photorealistic rendering", "domain": "visual scenes"}
{"terse": "potion brew", "rich": "Non-representational piece evoking brew, chaotic splatters resolved into harmony, fractured light dispersing into prismatic shards, dominated by slate grey, art nouveau linework", "domain": "visual scenes"}
{"terse": "portal open", "rich": "Non-representational piece evoking open, dissolving boundaries between form and void, chaotic splatters resolved into harmony, dominated by vermillion, oil painting style", "domain": "visual scenes"}
{"terse": "spell cast", "rich": "Geometric abstraction of cast, faded shapes intersecting, undulating waves of pure color, impressionist brushwork", "domain": "visual scenes"}
{"terse": "griffin fly", "rich": "Mythical elemental guarding amidst stark surroundings, ethereal mist coiling around clawed feet, ochre and dusty rose glow, minimalist composition", "domain": "visual scenes"}
{"terse": "phoenix burn", "rich": "Enchanted scene featuring specter descending into, ethereal mist coiling around clawed feet, a sword humming with dormant power, bathed in cerulean blue, graphic novel style", "domain": "visual scenes"}
{"terse": "unicorn run", "rich": "Enchanted scene featuring chimera awakening, chains of starlight binding the scene, chains of starlight binding the scene, bathed in indigo, cinematic still", "domain": "visual scenes"}
{"terse": "troll bridge", "rich": "Legendary moment: leviathan illuminating in a frozen throne room, portals shimmering at the periphery, ethereal atmosphere, art nouveau linework", "domain": "visual scenes"}
{"terse": "dwarf mine", "rich": "A faded fantasy realm where hydra awakening, ethereal mist coiling around clawed feet, familiar spirits orbiting in protective patterns, rendered in cinematic still", "domain": "visual scenes"}
{"terse": "orb glow", "rich": "Mythical griffin transforming amidst vibrant surroundings, familiar spirits orbiting in protective patterns, silver grey and charcoal glow, classical realism", "domain": "visual scenes"}
{"terse": "crystal cave", "rich": "Epic fantasy scene: wraith battling in a whimsical void chasm, chains of starlight binding the scene, chains of starlight binding the scene, oil painting style", "domain": "visual scenes"}
{"terse": "enchanted rose", "rich": "Enchanted scene featuring wyvern illuminating, a halo of magical energy pulsing outward, sacred geometry etched into stone, bathed in midnight black, dreamy soft focus", "domain": "visual scenes"}
{"terse": "cursed mirror", "rich": "Mythical pegasus transforming amidst majestic surroundings, crystalline formations jutting from the ground, mauve and charcoal glow, watercolor wash", "domain": "visual scenes"}
{"terse": "blessed shield", "rich": "Enchanted scene featuring leviathan devouring, sacred geometry etched into stone, familiar spirits orbiting in protective patterns, bathed in midnight black, classical realism", "domain": "visual scenes"}
{"terse": "shadow realm", "rich": "Enchanted scene featuring basilisk ascending from, sacred geometry etched into stone, ancient runes glowing along the edges, bathed in slate grey, cinematic still", "domain": "visual scenes"}
{"terse": "light kingdom", "rich": "A tranquil fantasy realm where wyvern descending into, chains of starlight binding the scene, embers drifting upward like inverted rain, rendered in watercolor wash", "domain": "visual scenes"}
{"terse": "void whisper", "rich": "Epic fantasy scene: wyvern circling in a intimate sky fortress, sacred geometry etched into stone, portals shimmering at the periphery, cinematic still", "domain": "visual scenes"}
{"terse": "star forge", "rich": "Mythical wraith devouring amidst muted surroundings, a sword humming with dormant power, teal and violet purple glow, minimalist composition", "domain": "visual scenes"}
{"terse": "moon temple", "rich": "Enchanted scene featuring griffin awakening, a halo of magical energy pulsing outward, chains of starlight binding the scene, bathed in ochre, minimalist composition", "domain": "visual scenes"}
{"terse": "sad rain, close-up", "rich": "Panoramic rain at dusk, dramatic atmosphere with ancient ruins half-swallowed by vines, a fallen log bridging a narrow ravine, rendered in expressionist distortion", "domain": "visual scenes"}
{"terse": "sunset beach, wide angle", "rich": "Wide-angle view of a delicate beach, slate grey and charcoal dominating the palette, weather-beaten fence posts trailing into fog, dust motes caught in a shaft of light, watercolor wash", "domain": "visual scenes"}
{"terse": "mountain fog, through fog", "rich": "Breathtaking fog where tall grass bending in the wind, the sky a gradient of mauve to midnight black, frost clinging to every surface, pop art vibrancy", "domain": "visual scenes"}
{"terse": "desert night, in rain", "rich": "A radiant night bathed in dawn, ancient ruins half-swallowed by vines, dust motes caught in a shaft of light, minimalist composition", "domain": "visual scenes"}
{"terse": "forest dawn, at dusk", "rich": "A lush dawn bathed in soft diffused, weather-beaten fence posts trailing into fog, a fallen log bridging a narrow ravine, photorealistic rendering", "domain": "visual scenes"}
{"terse": "ocean storm, at dusk", "rich": "Panoramic storm at dusk, mysterious atmosphere with wildflowers dotting the foreground, shadows stretching long and thin, rendered in graphic novel style", "domain": "visual scenes"}
{"terse": "winter village, close-up", "rich": "Panoramic village at dusk, frost-kissed atmosphere with lichen-covered stones, dust motes caught in a shaft of light, rendered in dreamy soft focus", "domain": "visual scenes"}
{"terse": "autumn field, soft focus", "rich": "An whimsical field stretching to the horizon, frost clinging to every surface, frost clinging to every surface, painted in impressionist brushwork", "domain": "visual scenes"}
{"terse": "volcano glow, dramatic lighting", "rich": "Panoramic glow at dusk, mysterious atmosphere with mist rising from a hidden stream, ancient ruins half-swallowed by vines, rendered in graphic novel style", "domain": "visual scenes"}
{"terse": "river mist, dramatic lighting", "rich": "Breathtaking mist where wildflowers dotting the foreground, the sky a gradient of charcoal to turquoise, a solitary figure in the distance, pop art vibrancy", "domain": "visual scenes"}
{"terse": "snowy peak, at dusk", "rich": "A luminous peak bathed in backlit, a winding path disappearing around a bend, wildflowers dotting the foreground, photorealistic rendering", "domain": "visual scenes"}
{"terse": "tropical shore, dramatic lighting", "rich": "An somber shore stretching to the horizon, a solitary figure in the distance, tall grass bending in the wind, painted in dreamy soft focus", "domain": "visual scenes"}
{"terse": "canyon shadow, aerial view", "rich": "Breathtaking shadow where ripples spreading across a mirror-still pond, the sky a gradient of pearl white to vermillion, a fallen log bridging a narrow ravine, photorealistic rendering", "domain": "visual scenes"}
{"terse": "lake mirror, wide angle", "rich": "An lush mirror stretching to the horizon, shadows stretching long and thin, lichen-covered boulders, painted in dreamy soft focus", "domain": "visual scenes"}
{"terse": "prairie wind, soft focus", "rich": "Breathtaking wind where frost clinging to every surface, the sky a gradient of emerald to cerulean blue, a solitary figure in the distance, cinematic still", "domain": "visual scenes"}
{"terse": "jungle stream, aerial view", "rich": "Panoramic stream at golden hour, crumbling atmosphere with dust motes caught in a shaft of light, reflections doubling the scene in still water, rendered in graphic novel style", "domain": "visual scenes"}
{"terse": "arctic light, aerial view", "rich": "Wide-angle view of a vibrant light, burnt sienna and ivory dominating the palette, lichen-covered boulders, birds wheeling in formation overhead, art nouveau linework", "domain": "visual scenes"}
{"terse": "meadow bloom, through fog", "rich": "Breathtaking bloom where a winding path disappearing around a bend, the sky a gradient of midnight black to cerulean blue, reflections doubling the scene in still water, graphic novel style", "domain": "visual scenes"}
{"terse": "cliff edge, aerial view", "rich": "A fierce edge bathed in harsh overhead, birds wheeling in formation overhead, frost clinging to every surface, graphic novel style", "domain": "visual scenes"}
{"terse": "swamp fog, soft focus", "rich": "Panoramic fog at dawn, shadowed atmosphere with weather-beaten fence posts trailing into fog, shadows stretching long and thin, rendered in photorealistic rendering", "domain": "visual scenes"}
{"terse": "moonlit valley, dramatic lighting", "rich": "Wide-angle view of a vibrant valley, midnight black and deep crimson dominating the palette, tall grass bending in the wind, mist rising from a hidden stream, dreamy soft focus", "domain": "visual scenes"}
{"terse": "sunrise ridge, dramatic lighting", "rich": "An stark ridge stretching to the horizon, lichen-covered stones, shadows stretching long and thin, painted in film noir lighting", "domain": "visual scenes"}
{"terse": "thunder plain, aerial view", "rich": "Wide-angle view of a weathered plain, golden amber and pearl white dominating the palette, mist rising from a hidden stream, wildflowers dotting the foreground, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "frozen lake, dramatic lighting", "rich": "Breathtaking lake where weather-beaten fence posts trailing into fog, the sky a gradient of pearl white to burnt sienna, ripples spreading across a mirror-still pond, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "dusty road, at dusk", "rich": "Breathtaking road where a solitary figure in the distance, the sky a gradient of burgundy to golden amber, lichen-covered stones, film noir lighting", "domain": "visual scenes"}
{"terse": "coastal cliff, at dusk", "rich": "Panoramic cliff at dusk, lush atmosphere with a solitary figure in the distance, wildflowers dotting the foreground, rendered in film noir lighting", "domain": "visual scenes"}
{"terse": "bamboo grove, dramatic lighting", "rich": "Breathtaking grove where dust motes caught in a shaft of light, the sky a gradient of ochre to teal, ancient ruins half-swallowed by vines, minimalist composition", "domain": "visual scenes"}
{"terse": "lavender field, soft focus", "rich": "Panoramic field at noon, delicate atmosphere with tall grass bending in the wind, reflections doubling the scene in still water, rendered in cinematic still", "domain": "visual scenes"}
{"terse": "coral reef, soft focus", "rich": "A forlorn reef bathed in dawn, frost clinging to every surface, birds wheeling in formation overhead, digital concept art", "domain": "visual scenes"}
{"terse": "glacier cave, in rain", "rich": "Wide-angle view of a sun-drenched cave, golden amber and violet purple dominating the palette, wildflowers dotting the foreground, tall grass bending in the wind, watercolor wash", "domain": "visual scenes"}
{"terse": "old man sad, close-up", "rich": "A gleaming portrait of a old man sad, a single tear catching the light, wind-tousled hair catching the light, harsh overhead lighting, expressionist distortion", "domain": "visual scenes"}
{"terse": "child laughing, soft focus", "rich": "A child laughing in somber pose, eyes reflecting a distant memory, a faint smile playing at the corners, background ivory, art nouveau linework", "domain": "visual scenes"}
{"terse": "warrior stare, dramatic lighting", "rich": "A warrior stare in tranquil pose, fingers stained with pigment, a pendant catching light at the collarbone, background emerald, film noir lighting", "domain": "visual scenes"}
{"terse": "queen crown, close-up", "rich": "Emotional portrait showing a gleaming queen crown, crow's feet deepened by laughter, a single tear catching the light, backlit tones, cinematic still", "domain": "visual scenes"}
{"terse": "musician play, aerial view", "rich": "Close-up of a luminous musician play, a single tear catching the light, gaze directed past the viewer into something unseen, captured in digital concept art", "domain": "visual scenes"}
{"terse": "elder wisdom, close-up", "rich": "A elder wisdom in brooding pose, shoulders squared against an invisible burden, crow's feet deepened by laughter, background teal, watercolor wash", "domain": "visual scenes"}
{"terse": "teen rebel, dramatic lighting", "rich": "Close-up of a gilded teen rebel, hands roughened by years of labor, a single tear catching the light, captured in graphic novel style", "domain": "visual scenes"}
{"terse": "soldier tired, through fog", "rich": "Close-up of a nostalgic soldier tired, gaze directed past the viewer into something unseen, gaze directed past the viewer into something unseen, captured in cinematic still", "domain": "visual scenes"}
{"terse": "dancer spin, aerial view", "rich": "A dancer spin in delicate pose, hands roughened by years of labor, gaze directed past the viewer into something unseen, background cerulean blue, minimalist composition", "domain": "visual scenes"}
{"terse": "writer think, wide angle", "rich": "Emotional portrait showing a stark writer think, shoulders squared against an invisible burden, a scar tracing the jawline, twilight tones, watercolor wash", "domain": "visual scenes"}
{"terse": "farmer proud, in rain", "rich": "A farmer proud in frost-kissed pose, callused hands resting in the lap, deep lines mapping decades of experience, background silver grey, minimalist composition", "domain": "visual scenes"}
{"terse": "nurse tired, soft focus", "rich": "Intimate depiction of a forlorn nurse tired, gaze directed past the viewer into something unseen, wind-tousled hair catching the light, backlit atmosphere, minimalist composition", "domain": "visual scenes"}
{"terse": "pilot calm, close-up", "rich": "Intimate depiction of a sublime pilot calm, shoulders squared against an invisible burden, weathered skin telling stories, backlit atmosphere, digital concept art", "domain": "visual scenes"}
{"terse": "artist paint, soft focus", "rich": "A artist paint in crumbling pose, a scar tracing the jawline, a single tear catching the light, background pearl white, graphic novel style", "domain": "visual scenes"}
{"terse": "chef focus, through fog", "rich": "Emotional portrait showing a dramatic chef focus, eyes reflecting a distant memory, a faint smile playing at the corners, soft diffused tones, dreamy soft focus", "domain": "visual scenes"}
{"terse": "teacher smile, through fog", "rich": "A teacher smile in stark pose, a scar tracing the jawline, gaze directed past the viewer into something unseen, background burgundy, photorealistic rendering", "domain": "visual scenes"}
{"terse": "monk sit, dramatic lighting", "rich": "A somber portrait of a monk sit, a faint smile playing at the corners, a scar tracing the jawline, cool blue lighting, classical realism", "domain": "visual scenes"}
{"terse": "witch brew, aerial view", "rich": "Intimate depiction of a luminous witch brew, a scar tracing the jawline, a faint smile playing at the corners, rim atmosphere, pop art vibrancy", "domain": "visual scenes"}
{"terse": "king throne, soft focus", "rich": "A vibrant portrait of a king throne, a pendant catching light at the collarbone, weathered skin telling stories, twilight lighting, dreamy soft focus", "domain": "visual scenes"}
{"terse": "thief sneak, through fog", "rich": "A luminous portrait of a thief sneak, shoulders squared against an invisible burden, shoulders squared against an invisible burden, soft diffused lighting, film noir lighting", "domain": "visual scenes"}
{"terse": "giant gentle, in rain", "rich": "Emotional portrait showing a lush giant gentle, eyes reflecting a distant memory, eyes reflecting a distant memory, rim tones, watercolor wash", "domain": "visual scenes"}
{"terse": "twin mirror, in rain", "rich": "A twin mirror in luminous pose, gaze directed past the viewer into something unseen, a pendant catching light at the collarbone, background violet purple, oil painting style", "domain": "visual scenes"}
{"terse": "blind see, soft focus", "rich": "A blind see in crumbling pose, eyes reflecting a distant memory, fingers stained with pigment, background cerulean blue, dreamy soft focus", "domain": "visual scenes"}
{"terse": "deaf hear, in rain", "rich": "Emotional portrait showing a luminous deaf hear, deep lines mapping decades of experience, a scar tracing the jawline, harsh overhead tones, expressionist distortion", "domain": "visual scenes"}
{"terse": "mute speak, dramatic lighting", "rich": "Emotional portrait showing a delicate mute speak, a scar tracing the jawline, eyes reflecting a distant memory, rim tones, photorealistic rendering", "domain": "visual scenes"}
{"terse": "lonely dark, at dusk", "rich": "Intimate depiction of a nostalgic lonely dark, wind-tousled hair catching the light, a pendant catching light at the collarbone, backlit atmosphere, minimalist composition", "domain": "visual scenes"}
{"terse": "joy burst, wide angle", "rich": "Emotional portrait showing a vibrant joy burst, a scar tracing the jawline, callused hands resting in the lap, cool blue tones, art nouveau linework", "domain": "visual scenes"}
{"terse": "anger red, close-up", "rich": "Emotional portrait showing a crumbling anger red, shoulders squared against an invisible burden, a single tear catching the light, neon tones, digital concept art", "domain": "visual scenes"}
{"terse": "calm blue, at dusk", "rich": "Intimate depiction of a pristine calm blue, wind-tousled hair catching the light, deep lines mapping decades of experience, moonlit atmosphere, cinematic still", "domain": "visual scenes"}
{"terse": "chaos spin, in rain", "rich": "Emotional portrait showing a pristine chaos spin, crow's feet deepened by laughter, shoulders squared against an invisible burden, dappled tones, minimalist composition", "domain": "visual scenes"}
{"terse": "silence white, in rain", "rich": "Non-representational piece evoking white, a central void drawing the eye inward, a central void drawing the eye inward, dominated by vermillion, oil painting style", "domain": "visual scenes"}
{"terse": "memory fade, wide angle", "rich": "Abstract visualization of fade, indigo and indigo swirling in faded motion, chaotic splatters resolved into harmony, minimalist composition", "domain": "visual scenes"}
{"terse": "hope rise, dramatic lighting", "rich": "Surreal interpretation of rise, weathered forms dissolving into coral, dissolving boundaries between form and void, classical realism", "domain": "visual scenes"}
{"terse": "fear freeze, through fog", "rich": "Abstract visualization of freeze, dusty rose and slate grey swirling in raw motion, binary oppositions meeting at a fault line, impressionist brushwork", "domain": "visual scenes"}
{"terse": "love wrap, through fog", "rich": "Surreal interpretation of wrap, serene forms dissolving into burgundy, chaotic splatters resolved into harmony, art nouveau linework", "domain": "visual scenes"}
{"terse": "time melt, in rain", "rich": "Abstract visualization of melt, dusty rose and ivory swirling in raw motion, fractured light dispersing into prismatic shards, photorealistic rendering", "domain": "visual scenes"}
{"terse": "dream float, close-up", "rich": "Geometric abstraction of float, haunting shapes intersecting, layered transparencies creating depth, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "truth shine, close-up", "rich": "Surreal interpretation of shine, serene forms dissolving into midnight black, fractured light dispersing into prismatic shards, digital concept art", "domain": "visual scenes"}
{"terse": "lie shadow, at dusk", "rich": "Abstract visualization of shadow, indigo and turquoise swirling in raw motion, binary oppositions meeting at a fault line, expressionist distortion", "domain": "visual scenes"}
{"terse": "peace settle, soft focus", "rich": "Surreal interpretation of settle, majestic forms dissolving into forest green, repeating patterns diminishing into infinity, minimalist composition", "domain": "visual scenes"}
{"terse": "rage burn, dramatic lighting", "rich": "Abstract visualization of burn, ochre and emerald swirling in vibrant motion, layered transparencies creating depth, watercolor wash", "domain": "visual scenes"}
{"terse": "grief deep, in rain", "rich": "Geometric abstraction of deep, dramatic shapes intersecting, chaotic splatters resolved into harmony, expressionist distortion", "domain": "visual scenes"}
{"terse": "wonder wide, in rain", "rich": "Non-representational piece evoking wide, sharp angular forms breaking through soft gradients, a central void drawing the eye inward, dominated by dusty rose, expressionist distortion", "domain": "visual scenes"}
{"terse": "shame hide, at dusk", "rich": "Geometric abstraction of hide, weathered shapes intersecting, undulating waves of pure color, classical realism", "domain": "visual scenes"}
{"terse": "pride lift, through fog", "rich": "Emotional landscape of pure lift, sharp angular forms breaking through soft gradients, fractured light dispersing into prismatic shards, rendered in coral and coral, cinematic still", "domain": "visual scenes"}
{"terse": "doubt fog, dramatic lighting", "rich": "Non-representational piece evoking fog, binary oppositions meeting at a fault line, rhythmic repetition building tension, dominated by midnight black, classical realism", "domain": "visual scenes"}
{"terse": "trust bridge, at dusk", "rich": "Non-representational piece evoking bridge, a central void drawing the eye inward, fractured light dispersing into prismatic shards, dominated by burnt sienna, pop art vibrancy", "domain": "visual scenes"}
{"terse": "loss empty, dramatic lighting", "rich": "Non-representational piece evoking empty, chaotic splatters resolved into harmony, layered transparencies creating depth, dominated by sage green, oil painting style", "domain": "visual scenes"}
{"terse": "gain bright, soft focus", "rich": "Surreal interpretation of bright, muted forms dissolving into copper orange, repeating patterns diminishing into infinity, impressionist brushwork", "domain": "visual scenes"}
{"terse": "change flow, close-up", "rich": "Abstract visualization of flow, turquoise and turquoise swirling in nostalgic motion, undulating waves of pure color, oil painting style", "domain": "visual scenes"}
{"terse": "dragon fire, wide angle", "rich": "Emotional landscape of pure fire, sharp angular forms breaking through soft gradients, repeating patterns diminishing into infinity, rendered in violet purple and burnt sienna, graphic novel style", "domain": "visual scenes"}
{"terse": "elf forest, close-up", "rich": "Abstract visualization of forest, copper orange and ochre swirling in gilded motion, binary oppositions meeting at a fault line, digital concept art", "domain": "visual scenes"}
{"terse": "wizard tower, wide angle", "rich": "Non-representational piece evoking tower, dissolving boundaries between form and void, sharp angular forms breaking through soft gradients, dominated by charcoal, classical realism", "domain": "visual scenes"}
{"terse": "fairy glow, close-up", "rich": "Geometric abstraction of glow, haunting shapes intersecting, repeating patterns diminishing into infinity, dreamy soft focus", "domain": "visual scenes"}
{"terse": "knight ride, soft focus", "rich": "Surreal interpretation of ride, intimate forms dissolving into copper orange, layered transparencies creating depth, digital concept art", "domain": "visual scenes"}
{"terse": "castle dark, close-up", "rich": "Abstract visualization of dark, slate grey and dusty rose swirling in fierce motion, undulating waves of pure color, impressionist brushwork", "domain": "visual scenes"}
{"terse": "sword magic, close-up", "rich": "Surreal interpretation of magic, majestic forms dissolving into mauve, undulating waves of pure color, art nouveau linework", "domain": "visual scenes"}
{"terse": "potion brew, through fog", "rich": "Abstract visualization of brew, coral and midnight black swirling in faded motion, rhythmic repetition building tension, photorealistic rendering", "domain": "visual scenes"}
{"terse": "portal open, aerial view", "rich": "Non-representational piece evoking open, undulating waves of pure color, a central void drawing the eye inward, dominated by coral, dreamy soft focus", "domain": "visual scenes"}
{"terse": "spell cast, through fog", "rich": "Surreal interpretation of cast, brooding forms dissolving into cerulean blue, dissolving boundaries between form and void, pop art vibrancy", "domain": "visual scenes"}
{"terse": "griffin fly, in rain", "rich": "Legendary moment: griffin circling in a underwater temple, crystalline formations jutting from the ground, forlorn atmosphere, expressionist distortion", "domain": "visual scenes"}
{"terse": "phoenix burn, aerial view", "rich": "Legendary moment: specter emerging from in a sky fortress, portals shimmering at the periphery, dramatic atmosphere, photorealistic rendering", "domain": "visual scenes"}
{"terse": "unicorn run, in rain", "rich": "Mythical golem transforming amidst somber surroundings, chains of starlight binding the scene, copper orange and forest green glow, digital concept art", "domain": "visual scenes"}
{"terse": "troll bridge, through fog", "rich": "Mythical pegasus illuminating amidst radiant surroundings, ancient runes glowing along the edges, cerulean blue and emerald glow, graphic novel style", "domain": "visual scenes"}
{"terse": "dwarf mine, in rain", "rich": "A majestic fantasy realm where wyvern transforming, sacred geometry etched into stone, ethereal mist coiling around clawed feet, rendered in art nouveau linework", "domain": "visual scenes"}
{"terse": "orb glow, through fog", "rich": "A crumbling fantasy realm where djinn transforming, a halo of magical energy pulsing outward, a sword humming with dormant power, rendered in film noir lighting", "domain": "visual scenes"}
{"terse": "crystal cave, wide angle", "rich": "Enchanted scene featuring specter ascending from, chains of starlight binding the scene, crystalline formations jutting from the ground, bathed in emerald, film noir lighting", "domain": "visual scenes"}
{"terse": "enchanted rose, aerial view", "rich": "Enchanted scene featuring phoenix guarding, a halo of magical energy pulsing outward, portals shimmering at the periphery, bathed in burnt sienna, minimalist composition", "domain": "visual scenes"}
{"terse": "cursed mirror, wide angle", "rich": "A shadowed fantasy realm where djinn awakening, ethereal mist coiling around clawed feet, ethereal mist coiling around clawed feet, rendered in expressionist distortion", "domain": "visual scenes"}
{"terse": "blessed shield, soft focus", "rich": "A vibrant fantasy realm where elemental devouring, familiar spirits orbiting in protective patterns, familiar spirits orbiting in protective patterns, rendered in impressionist brushwork", "domain": "visual scenes"}
{"terse": "shadow realm, through fog", "rich": "Epic fantasy scene: unicorn ascending from in a tranquil crystal cavern, ancient runes glowing along the edges, familiar spirits orbiting in protective patterns, impressionist brushwork", "domain": "visual scenes"}
{"terse": "light kingdom, aerial view", "rich": "Mythical hydra resting upon amidst tranquil surroundings, ethereal mist coiling around clawed feet, ochre and copper orange glow, art nouveau linework", "domain": "visual scenes"}
{"terse": "void whisper, in rain", "rich": "A vibrant fantasy realm where elemental battling, embers drifting upward like inverted rain, ethereal mist coiling around clawed feet, rendered in photorealistic rendering", "domain": "visual scenes"}
{"terse": "star forge, at dusk", "rich": "Legendary moment: phoenix resting upon in a sky fortress, familiar spirits orbiting in protective patterns, raw atmosphere, photorealistic rendering", "domain": "visual scenes"}
{"terse": "moon temple, dramatic lighting", "rich": "Epic fantasy scene: dragon ascending from in a ethereal frozen throne room, chains of starlight binding the scene, embers drifting upward like inverted rain, dreamy soft focus", "domain": "visual scenes"}
{"terse": "sad rain, soft focus", "rich": "A luminous rain bathed in candlelit, a solitary figure in the distance, shadows stretching long and thin, digital concept art", "domain": "visual scenes"}
{"terse": "sunset beach, soft focus", "rich": "Panoramic beach at noon, gleaming atmosphere with tall grass bending in the wind, shadows stretching long and thin, rendered in dreamy soft focus", "domain": "visual scenes"}
{"terse": "mountain fog, dramatic lighting", "rich": "Breathtaking fog where lichen-covered boulders, the sky a gradient of forest green to silver grey, lichen-covered stones, impressionist brushwork", "domain": "visual scenes"}
{"terse": "desert night, dramatic lighting", "rich": "A lush night bathed in warm golden, lichen-covered stones, mist rising from a hidden stream, photorealistic rendering", "domain": "visual scenes"}
{"terse": "forest dawn, soft focus", "rich": "Wide-angle view of a serene dawn, silver grey and burnt sienna dominating the palette, lichen-covered boulders, weather-beaten fence posts trailing into fog, art nouveau linework", "domain": "visual scenes"}
{"terse": "ocean storm, dramatic lighting", "rich": "Breathtaking storm where reflections doubling the scene in still water, the sky a gradient of teal to burgundy, lichen-covered stones, pop art vibrancy", "domain": "visual scenes"}
{"terse": "winter village, dramatic lighting", "rich": "Wide-angle view of a pristine village, cerulean blue and vermillion dominating the palette, ancient ruins half-swallowed by vines, a solitary figure in the distance, art nouveau linework", "domain": "visual scenes"}
{"terse": "autumn field, in rain", "rich": "Breathtaking field where reflections doubling the scene in still water, the sky a gradient of charcoal to indigo, reflections doubling the scene in still water, digital concept art", "domain": "visual scenes"}
{"terse": "volcano glow, dramatic lighting", "rich": "Wide-angle view of a faded glow, cerulean blue and violet purple dominating the palette, shadows stretching long and thin, birds wheeling in formation overhead, graphic novel style", "domain": "visual scenes"}
{"terse": "river mist, dramatic lighting", "rich": "Panoramic mist at golden hour, somber atmosphere with birds wheeling in formation overhead, tall grass bending in the wind, rendered in dreamy soft focus", "domain": "visual scenes"}
{"terse": "snowy peak, through fog", "rich": "Breathtaking peak where ripples spreading across a mirror-still pond, the sky a gradient of copper orange to turquoise, mist rising from a hidden stream, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "tropical shore, wide angle", "rich": "Panoramic shore at dusk, luminous atmosphere with a solitary figure in the distance, ripples spreading across a mirror-still pond, rendered in classical realism", "domain": "visual scenes"}
{"terse": "canyon shadow, wide angle", "rich": "Panoramic shadow at dawn, muted atmosphere with frost clinging to every surface, reflections doubling the scene in still water, rendered in hyperrealistic detail", "domain": "visual scenes"}
{"terse": "lake mirror, wide angle", "rich": "Breathtaking mirror where wildflowers dotting the foreground, the sky a gradient of vermillion to burnt sienna, reflections doubling the scene in still water, photorealistic rendering", "domain": "visual scenes"}
{"terse": "prairie wind, at dusk", "rich": "Panoramic wind at blue hour, intimate atmosphere with tall grass bending in the wind, a solitary figure in the distance, rendered in hyperrealistic detail", "domain": "visual scenes"}
{"terse": "jungle stream, wide angle", "rich": "Breathtaking stream where dust motes caught in a shaft of light, the sky a gradient of indigo to coral, mist rising from a hidden stream, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "arctic light, dramatic lighting", "rich": "An gleaming light stretching to the horizon, ripples spreading across a mirror-still pond, birds wheeling in formation overhead, painted in dreamy soft focus", "domain": "visual scenes"}
{"terse": "meadow bloom, soft focus", "rich": "A brooding bloom bathed in soft diffused, a solitary figure in the distance, ripples spreading across a mirror-still pond, film noir lighting", "domain": "visual scenes"}
{"terse": "cliff edge, through fog", "rich": "An raw edge stretching to the horizon, weather-beaten fence posts trailing into fog, shadows stretching long and thin, painted in graphic novel style", "domain": "visual scenes"}
{"terse": "swamp fog, in rain", "rich": "Breathtaking fog where birds wheeling in formation overhead, the sky a gradient of vermillion to violet purple, lichen-covered boulders, expressionist distortion", "domain": "visual scenes"}
{"terse": "moonlit valley, at dusk", "rich": "A faded valley bathed in moonlit, mist rising from a hidden stream, frost clinging to every surface, photorealistic rendering", "domain": "visual scenes"}
{"terse": "sunrise ridge, at dusk", "rich": "Wide-angle view of a radiant ridge, sage green and mauve dominating the palette, lichen-covered boulders, ripples spreading across a mirror-still pond, digital concept art", "domain": "visual scenes"}
{"terse": "thunder plain, wide angle", "rich": "Wide-angle view of a frost-kissed plain, sage green and teal dominating the palette, ripples spreading across a mirror-still pond, mist rising from a hidden stream, oil painting style", "domain": "visual scenes"}
{"terse": "frozen lake, aerial view", "rich": "A haunting lake bathed in dawn, a fallen log bridging a narrow ravine, weather-beaten fence posts trailing into fog, pop art vibrancy", "domain": "visual scenes"}
{"terse": "dusty road, in rain", "rich": "A sun-drenched road bathed in cool blue, reflections doubling the scene in still water, a winding path disappearing around a bend, digital concept art", "domain": "visual scenes"}
{"terse": "coastal cliff, aerial view", "rich": "A pristine cliff bathed in rim, mist rising from a hidden stream, a fallen log bridging a narrow ravine, graphic novel style", "domain": "visual scenes"}
{"terse": "bamboo grove, dramatic lighting", "rich": "Panoramic grove at noon, fierce atmosphere with ancient ruins half-swallowed by vines, mist rising from a hidden stream, rendered in digital concept art", "domain": "visual scenes"}
{"terse": "lavender field, soft focus", "rich": "Wide-angle view of a lush field, deep crimson and burnt sienna dominating the palette, birds wheeling in formation overhead, dust motes caught in a shaft of light, film noir lighting", "domain": "visual scenes"}
{"terse": "coral reef, soft focus", "rich": "Panoramic reef at golden hour, somber atmosphere with a solitary figure in the distance, a winding path disappearing around a bend, rendered in film noir lighting", "domain": "visual scenes"}
{"terse": "glacier cave, wide angle", "rich": "Panoramic cave at dusk, somber atmosphere with a solitary figure in the distance, weather-beaten fence posts trailing into fog, rendered in graphic novel style", "domain": "visual scenes"}
{"terse": "old man sad, dramatic lighting", "rich": "Close-up of a raw old man sad, a pendant catching light at the collarbone, hands roughened by years of labor, captured in pop art vibrancy", "domain": "visual scenes"}
{"terse": "child laughing, at dusk", "rich": "Close-up of a lush child laughing, a faint smile playing at the corners, crow's feet deepened by laughter, captured in minimalist composition", "domain": "visual scenes"}
{"terse": "warrior stare, through fog", "rich": "Close-up of a frost-kissed warrior stare, a pendant catching light at the collarbone, a pendant catching light at the collarbone, captured in dreamy soft focus", "domain": "visual scenes"}
{"terse": "queen crown, through fog", "rich": "A mysterious portrait of a queen crown, crow's feet deepened by laughter, deep lines mapping decades of experience, rim lighting, pop art vibrancy", "domain": "visual scenes"}
{"terse": "musician play, at dusk", "rich": "Emotional portrait showing a gilded musician play, gaze directed past the viewer into something unseen, crow's feet deepened by laughter, rim tones, expressionist distortion", "domain": "visual scenes"}
{"terse": "elder wisdom, at dusk", "rich": "Emotional portrait showing a majestic elder wisdom, a pendant catching light at the collarbone, fingers stained with pigment, backlit tones, film noir lighting", "domain": "visual scenes"}
{"terse": "teen rebel, dramatic lighting", "rich": "Close-up of a frost-kissed teen rebel, wind-tousled hair catching the light, wind-tousled hair catching the light, captured in dreamy soft focus", "domain": "visual scenes"}
{"terse": "soldier tired, through fog", "rich": "A soldier tired in ethereal pose, wind-tousled hair catching the light, shoulders squared against an invisible burden, background ivory, oil painting style", "domain": "visual scenes"}
{"terse": "dancer spin, wide angle", "rich": "Intimate depiction of a brooding dancer spin, a scar tracing the jawline, crow's feet deepened by laughter, warm golden atmosphere, impressionist brushwork", "domain": "visual scenes"}
{"terse": "writer think, aerial view", "rich": "A mysterious portrait of a writer think, a single tear catching the light, a scar tracing the jawline, rim lighting, photorealistic rendering", "domain": "visual scenes"}
{"terse": "farmer proud, through fog", "rich": "A forlorn portrait of a farmer proud, a scar tracing the jawline, wind-tousled hair catching the light, dawn lighting, digital concept art", "domain": "visual scenes"}
{"terse": "nurse tired, wide angle", "rich": "Intimate depiction of a shadowed nurse tired, a pendant catching light at the collarbone, shoulders squared against an invisible burden, twilight atmosphere, watercolor wash", "domain": "visual scenes"}
{"terse": "pilot calm, through fog", "rich": "Intimate depiction of a sun-drenched pilot calm, fingers stained with pigment, eyes reflecting a distant memory, warm golden atmosphere, art nouveau linework", "domain": "visual scenes"}
{"terse": "artist paint, through fog", "rich": "Emotional portrait showing a fierce artist paint, wind-tousled hair catching the light, a scar tracing the jawline, neon tones, art nouveau linework", "domain": "visual scenes"}
{"terse": "chef focus, soft focus", "rich": "Close-up of a shadowed chef focus, hands roughened by years of labor, deep lines mapping decades of experience, captured in graphic novel style", "domain": "visual scenes"}
{"terse": "teacher smile, wide angle", "rich": "Close-up of a pristine teacher smile, a scar tracing the jawline, callused hands resting in the lap, captured in impressionist brushwork", "domain": "visual scenes"}
{"terse": "monk sit, through fog", "rich": "Intimate depiction of a gilded monk sit, weathered skin telling stories, eyes reflecting a distant memory, backlit atmosphere, cinematic still", "domain": "visual scenes"}
{"terse": "witch brew, soft focus", "rich": "A witch brew in lush pose, deep lines mapping decades of experience, callused hands resting in the lap, background ochre, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "king throne, at dusk", "rich": "Close-up of a ethereal king throne, a single tear catching the light, callused hands resting in the lap, captured in hyperrealistic detail", "domain": "visual scenes"}
{"terse": "thief sneak, aerial view", "rich": "Close-up of a frost-kissed thief sneak, eyes reflecting a distant memory, eyes reflecting a distant memory, captured in art nouveau linework", "domain": "visual scenes"}
{"terse": "giant gentle, in rain", "rich": "A giant gentle in dramatic pose, deep lines mapping decades of experience, hands roughened by years of labor, background violet purple, watercolor wash", "domain": "visual scenes"}
{"terse": "twin mirror, soft focus", "rich": "A twin mirror in dramatic pose, eyes reflecting a distant memory, callused hands resting in the lap, background indigo, watercolor wash", "domain": "visual scenes"}
{"terse": "blind see, close-up", "rich": "Emotional portrait showing a lush blind see, fingers stained with pigment, gaze directed past the viewer into something unseen, harsh overhead tones, minimalist composition", "domain": "visual scenes"}
{"terse": "deaf hear, through fog", "rich": "Close-up of a tranquil deaf hear, shoulders squared against an invisible burden, wind-tousled hair catching the light, captured in film noir lighting", "domain": "visual scenes"}
{"terse": "mute speak, in rain", "rich": "A mute speak in tranquil pose, a scar tracing the jawline, a pendant catching light at the collarbone, background copper orange, digital concept art", "domain": "visual scenes"}
{"terse": "lonely dark, at dusk", "rich": "Close-up of a somber lonely dark, a scar tracing the jawline, crow's feet deepened by laughter, captured in dreamy soft focus", "domain": "visual scenes"}
{"terse": "joy burst, at dusk", "rich": "A mysterious portrait of a joy burst, eyes reflecting a distant memory, crow's feet deepened by laughter, rim lighting, film noir lighting", "domain": "visual scenes"}
{"terse": "anger red, close-up", "rich": "Emotional portrait showing a gleaming anger red, callused hands resting in the lap, a pendant catching light at the collarbone, neon tones, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "calm blue, in rain", "rich": "Close-up of a sublime calm blue, shoulders squared against an invisible burden, hands roughened by years of labor, captured in pop art vibrancy", "domain": "visual scenes"}
{"terse": "chaos spin, soft focus", "rich": "Close-up of a radiant chaos spin, hands roughened by years of labor, eyes reflecting a distant memory, captured in hyperrealistic detail", "domain": "visual scenes"}
{"terse": "silence white, through fog", "rich": "Geometric abstraction of white, pristine shapes intersecting, sharp angular forms breaking through soft gradients, film noir lighting", "domain": "visual scenes"}
{"terse": "memory fade, dramatic lighting", "rich": "Non-representational piece evoking fade, dissolving boundaries between form and void, a central void drawing the eye inward, dominated by forest green, minimalist composition", "domain": "visual scenes"}
{"terse": "hope rise, in rain", "rich": "Abstract visualization of rise, slate grey and deep crimson swirling in raw motion, sharp angular forms breaking through soft gradients, impressionist brushwork", "domain": "visual scenes"}
{"terse": "fear freeze, at dusk", "rich": "Geometric abstraction of freeze, lush shapes intersecting, a central void drawing the eye inward, impressionist brushwork", "domain": "visual scenes"}
{"terse": "love wrap, through fog", "rich": "Surreal interpretation of wrap, frost-kissed forms dissolving into ochre, repeating patterns diminishing into infinity, oil painting style", "domain": "visual scenes"}
{"terse": "time melt, in rain", "rich": "Emotional landscape of pure melt, sharp angular forms breaking through soft gradients, layered transparencies creating depth, rendered in silver grey and teal, film noir lighting", "domain": "visual scenes"}
{"terse": "dream float, soft focus", "rich": "Emotional landscape of pure float, undulating waves of pure color, undulating waves of pure color, rendered in teal and deep crimson, cinematic still", "domain": "visual scenes"}
{"terse": "truth shine, aerial view", "rich": "Geometric abstraction of shine, mysterious shapes intersecting, dissolving boundaries between form and void, impressionist brushwork", "domain": "visual scenes"}
{"terse": "lie shadow, close-up", "rich": "Surreal interpretation of shadow, ethereal forms dissolving into deep crimson, a central void drawing the eye inward, impressionist brushwork", "domain": "visual scenes"}
{"terse": "peace settle, dramatic lighting", "rich": "Surreal interpretation of settle, somber forms dissolving into deep crimson, repeating patterns diminishing into infinity, oil painting style", "domain": "visual scenes"}
{"terse": "rage burn, aerial view", "rich": "Emotional landscape of pure burn, fractured light dispersing into prismatic shards, rhythmic repetition building tension, rendered in copper orange and slate grey, minimalist composition", "domain": "visual scenes"}
{"terse": "grief deep, wide angle", "rich": "Emotional landscape of pure deep, dissolving boundaries between form and void, layered transparencies creating depth, rendered in indigo and teal, oil painting style", "domain": "visual scenes"}
{"terse": "wonder wide, soft focus", "rich": "Abstract visualization of wide, forest green and ochre swirling in haunting motion, chaotic splatters resolved into harmony, cinematic still", "domain": "visual scenes"}
{"terse": "shame hide, close-up", "rich": "Emotional landscape of pure hide, chaotic splatters resolved into harmony, binary oppositions meeting at a fault line, rendered in burgundy and forest green, impressionist brushwork", "domain": "visual scenes"}
{"terse": "pride lift, wide angle", "rich": "Geometric abstraction of lift, majestic shapes intersecting, sharp angular forms breaking through soft gradients, cinematic still", "domain": "visual scenes"}
{"terse": "doubt fog, soft focus", "rich": "Geometric abstraction of fog, stark shapes intersecting, rhythmic repetition building tension, classical realism", "domain": "visual scenes"}
{"terse": "trust bridge, soft focus", "rich": "Surreal interpretation of bridge, forlorn forms dissolving into pearl white, a central void drawing the eye inward, digital concept art", "domain": "visual scenes"}
{"terse": "loss empty, in rain", "rich": "Non-representational piece evoking empty, dissolving boundaries between form and void, binary oppositions meeting at a fault line, dominated by coral, classical realism", "domain": "visual scenes"}
{"terse": "gain bright, aerial view", "rich": "Abstract visualization of bright, vermillion and teal swirling in pristine motion, a central void drawing the eye inward, cinematic still", "domain": "visual scenes"}
{"terse": "change flow, in rain", "rich": "Surreal interpretation of flow, fierce forms dissolving into emerald, fractured light dispersing into prismatic shards, classical realism", "domain": "visual scenes"}
{"terse": "dragon fire, close-up", "rich": "Geometric abstraction of fire, ethereal shapes intersecting, chaotic splatters resolved into harmony, graphic novel style", "domain": "visual scenes"}
{"terse": "elf forest, aerial view", "rich": "Emotional landscape of pure forest, rhythmic repetition building tension, a central void drawing the eye inward, rendered in teal and burnt sienna, impressionist brushwork", "domain": "visual scenes"}
{"terse": "wizard tower, through fog", "rich": "Abstract visualization of tower, mauve and midnight black swirling in pristine motion, a central void drawing the eye inward, expressionist distortion", "domain": "visual scenes"}
{"terse": "fairy glow, aerial view", "rich": "Emotional landscape of pure glow, binary oppositions meeting at a fault line, chaotic splatters resolved into harmony, rendered in midnight black and burgundy, photorealistic rendering", "domain": "visual scenes"}
{"terse": "knight ride, through fog", "rich": "Geometric abstraction of ride, mysterious shapes intersecting, layered transparencies creating depth, graphic novel style", "domain": "visual scenes"}
{"terse": "castle dark, dramatic lighting", "rich": "Surreal interpretation of dark, radiant forms dissolving into pearl white, binary oppositions meeting at a fault line, pop art vibrancy", "domain": "visual scenes"}
{"terse": "sword magic, dramatic lighting", "rich": "Surreal interpretation of magic, brooding forms dissolving into mauve, binary oppositions meeting at a fault line, dreamy soft focus", "domain": "visual scenes"}
{"terse": "potion brew, in rain", "rich": "Emotional landscape of pure brew, rhythmic repetition building tension, rhythmic repetition building tension, rendered in midnight black and slate grey, expressionist distortion", "domain": "visual scenes"}
{"terse": "portal open, through fog", "rich": "Geometric abstraction of open, vibrant shapes intersecting, a central void drawing the eye inward, oil painting style", "domain": "visual scenes"}
{"terse": "spell cast, through fog", "rich": "Abstract visualization of cast, coral and coral swirling in brooding motion, a central void drawing the eye inward, pop art vibrancy", "domain": "visual scenes"}
{"terse": "griffin fly, through fog", "rich": "Enchanted scene featuring leviathan transforming, ethereal mist coiling around clawed feet, crystalline formations jutting from the ground, bathed in ochre, film noir lighting", "domain": "visual scenes"}
{"terse": "phoenix burn, aerial view", "rich": "A haunting fantasy realm where kitsune emerging from, portals shimmering at the periphery, chains of starlight binding the scene, rendered in graphic novel style", "domain": "visual scenes"}
{"terse": "unicorn run, dramatic lighting", "rich": "Legendary moment: basilisk ascending from in a lava forge, a sword humming with dormant power, tranquil atmosphere, watercolor wash", "domain": "visual scenes"}
{"terse": "troll bridge, at dusk", "rich": "Epic fantasy scene: wyvern battling in a tranquil ancient battlefield, chains of starlight binding the scene, embers drifting upward like inverted rain, film noir lighting", "domain": "visual scenes"}
{"terse": "dwarf mine, soft focus", "rich": "Mythical behemoth circling amidst shadowed surroundings, portals shimmering at the periphery, golden amber and charcoal glow, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "orb glow, in rain", "rich": "Enchanted scene featuring hydra resting upon, a halo of magical energy pulsing outward, portals shimmering at the periphery, bathed in indigo, graphic novel style", "domain": "visual scenes"}
{"terse": "crystal cave, aerial view", "rich": "A pristine fantasy realm where golem devouring, ancient runes glowing along the edges, chains of starlight binding the scene, rendered in expressionist distortion", "domain": "visual scenes"}
{"terse": "enchanted rose, wide angle", "rich": "Legendary moment: golem soaring above in a ancient battlefield, chains of starlight binding the scene, frost-kissed atmosphere, pop art vibrancy", "domain": "visual scenes"}
{"terse": "cursed mirror, at dusk", "rich": "A shadowed fantasy realm where sphinx battling, portals shimmering at the periphery, portals shimmering at the periphery, rendered in pop art vibrancy", "domain": "visual scenes"}
{"terse": "blessed shield, wide angle", "rich": "Legendary moment: hydra soaring above in a enchanted forest, ancient runes glowing along the edges, faded atmosphere, dreamy soft focus", "domain": "visual scenes"}
{"terse": "shadow realm, at dusk", "rich": "Mythical griffin guarding amidst whimsical surroundings, chains of starlight binding the scene, teal and vermillion glow, photorealistic rendering", "domain": "visual scenes"}
{"terse": "light kingdom, in rain", "rich": "Legendary moment: elemental battling in a lava forge, ethereal mist coiling around clawed feet, forlorn atmosphere, oil painting style", "domain": "visual scenes"}
{"terse": "void whisper, soft focus", "rich": "Epic fantasy scene: kitsune resting upon in a lush dream dimension, crystalline formations jutting from the ground, familiar spirits orbiting in protective patterns, dreamy soft focus", "domain": "visual scenes"}
{"terse": "star forge, dramatic lighting", "rich": "Legendary moment: hydra devouring in a lava forge, ethereal mist coiling around clawed feet, somber atmosphere, oil painting style", "domain": "visual scenes"}
{"terse": "moon temple, aerial view", "rich": "Enchanted scene featuring pegasus awakening, a sword humming with dormant power, a sword humming with dormant power, bathed in charcoal, cinematic still", "domain": "visual scenes"}
{"terse": "sad rain, in rain", "rich": "A nostalgic rain bathed in candlelit, lichen-covered stones, ancient ruins half-swallowed by vines, watercolor wash", "domain": "visual scenes"}
{"terse": "sunset beach, aerial view", "rich": "Wide-angle view of a frost-kissed beach, forest green and pearl white dominating the palette, reflections doubling the scene in still water, frost clinging to every surface, cinematic still", "domain": "visual scenes"}
{"terse": "mountain fog, soft focus", "rich": "Panoramic fog at blue hour, brooding atmosphere with a winding path disappearing around a bend, reflections doubling the scene in still water, rendered in graphic novel style", "domain": "visual scenes"}
{"terse": "desert night, close-up", "rich": "Wide-angle view of a somber night, midnight black and midnight black dominating the palette, ripples spreading across a mirror-still pond, ancient ruins half-swallowed by vines, pop art vibrancy", "domain": "visual scenes"}
{"terse": "forest dawn, wide angle", "rich": "An luminous dawn stretching to the horizon, ripples spreading across a mirror-still pond, shadows stretching long and thin, painted in cinematic still", "domain": "visual scenes"}
{"terse": "ocean storm, dramatic lighting", "rich": "Panoramic storm at dawn, gilded atmosphere with shadows stretching long and thin, ancient ruins half-swallowed by vines, rendered in watercolor wash", "domain": "visual scenes"}
{"terse": "winter village, at dusk", "rich": "A nostalgic village bathed in twilight, dust motes caught in a shaft of light, ancient ruins half-swallowed by vines, oil painting style", "domain": "visual scenes"}
{"terse": "autumn field, through fog", "rich": "Wide-angle view of a weathered field, teal and turquoise dominating the palette, lichen-covered stones, dust motes caught in a shaft of light, pop art vibrancy", "domain": "visual scenes"}
{"terse": "volcano glow, soft focus", "rich": "Wide-angle view of a sun-drenched glow, cerulean blue and mauve dominating the palette, dust motes caught in a shaft of light, a solitary figure in the distance, graphic novel style", "domain": "visual scenes"}
{"terse": "river mist, aerial view", "rich": "An nostalgic mist stretching to the horizon, dust motes caught in a shaft of light, ancient ruins half-swallowed by vines, painted in film noir lighting", "domain": "visual scenes"}
{"terse": "snowy peak, wide angle", "rich": "A dramatic peak bathed in twilight, ancient ruins half-swallowed by vines, tall grass bending in the wind, film noir lighting", "domain": "visual scenes"}
{"terse": "tropical shore, aerial view", "rich": "Panoramic shore at dawn, forlorn atmosphere with reflections doubling the scene in still water, weather-beaten fence posts trailing into fog, rendered in film noir lighting", "domain": "visual scenes"}
{"terse": "canyon shadow, wide angle", "rich": "A stark shadow bathed in harsh overhead, ripples spreading across a mirror-still pond, a winding path disappearing around a bend, classical realism", "domain": "visual scenes"}
{"terse": "lake mirror, at dusk", "rich": "Wide-angle view of a delicate mirror, copper orange and midnight black dominating the palette, wildflowers dotting the foreground, ripples spreading across a mirror-still pond, film noir lighting", "domain": "visual scenes"}
{"terse": "prairie wind, soft focus", "rich": "Breathtaking wind where frost clinging to every surface, the sky a gradient of violet purple to violet purple, dust motes caught in a shaft of light, impressionist brushwork", "domain": "visual scenes"}
{"terse": "jungle stream, aerial view", "rich": "Panoramic stream at dawn, delicate atmosphere with a winding path disappearing around a bend, tall grass bending in the wind, rendered in expressionist distortion", "domain": "visual scenes"}
{"terse": "arctic light, dramatic lighting", "rich": "An whimsical light stretching to the horizon, weather-beaten fence posts trailing into fog, reflections doubling the scene in still water, painted in classical realism", "domain": "visual scenes"}
{"terse": "meadow bloom, dramatic lighting", "rich": "Breathtaking bloom where a fallen log bridging a narrow ravine, the sky a gradient of midnight black to mauve, ancient ruins half-swallowed by vines, expressionist distortion", "domain": "visual scenes"}
{"terse": "cliff edge, in rain", "rich": "Wide-angle view of a serene edge, pearl white and ivory dominating the palette, dust motes caught in a shaft of light, a solitary figure in the distance, film noir lighting", "domain": "visual scenes"}
{"terse": "swamp fog, soft focus", "rich": "Panoramic fog at noon, frost-kissed atmosphere with ripples spreading across a mirror-still pond, birds wheeling in formation overhead, rendered in expressionist distortion", "domain": "visual scenes"}
{"terse": "moonlit valley, wide angle", "rich": "Breathtaking valley where lichen-covered stones, the sky a gradient of violet purple to teal, reflections doubling the scene in still water, art nouveau linework", "domain": "visual scenes"}
{"terse": "sunrise ridge, in rain", "rich": "Panoramic ridge at midnight, delicate atmosphere with shadows stretching long and thin, weather-beaten fence posts trailing into fog, rendered in photorealistic rendering", "domain": "visual scenes"}
{"terse": "thunder plain, soft focus", "rich": "An haunting plain stretching to the horizon, frost clinging to every surface, frost clinging to every surface, painted in digital concept art", "domain": "visual scenes"}
{"terse": "frozen lake, through fog", "rich": "Panoramic lake at noon, tranquil atmosphere with tall grass bending in the wind, lichen-covered stones, rendered in hyperrealistic detail", "domain": "visual scenes"}
{"terse": "dusty road, soft focus", "rich": "Panoramic road at dusk, radiant atmosphere with birds wheeling in formation overhead, shadows stretching long and thin, rendered in watercolor wash", "domain": "visual scenes"}
{"terse": "coastal cliff, dramatic lighting", "rich": "Panoramic cliff at golden hour, majestic atmosphere with ripples spreading across a mirror-still pond, a solitary figure in the distance, rendered in pop art vibrancy", "domain": "visual scenes"}
{"terse": "bamboo grove, through fog", "rich": "Panoramic grove at golden hour, sun-drenched atmosphere with dust motes caught in a shaft of light, a solitary figure in the distance, rendered in hyperrealistic detail", "domain": "visual scenes"}
{"terse": "lavender field, aerial view", "rich": "An muted field stretching to the horizon, dust motes caught in a shaft of light, weather-beaten fence posts trailing into fog, painted in graphic novel style", "domain": "visual scenes"}
{"terse": "coral reef, at dusk", "rich": "An dramatic reef stretching to the horizon, birds wheeling in formation overhead, lichen-covered stones, painted in oil painting style", "domain": "visual scenes"}
{"terse": "glacier cave, aerial view", "rich": "Breathtaking cave where lichen-covered boulders, the sky a gradient of cerulean blue to forest green, lichen-covered boulders, pop art vibrancy", "domain": "visual scenes"}
{"terse": "old man sad, through fog", "rich": "Emotional portrait showing a tranquil old man sad, fingers stained with pigment, a single tear catching the light, candlelit tones, digital concept art", "domain": "visual scenes"}
{"terse": "child laughing, in rain", "rich": "A child laughing in somber pose, fingers stained with pigment, crow's feet deepened by laughter, background mauve, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "warrior stare, soft focus", "rich": "Close-up of a mysterious warrior stare, hands roughened by years of labor, a faint smile playing at the corners, captured in minimalist composition", "domain": "visual scenes"}
{"terse": "queen crown, through fog", "rich": "A queen crown in ethereal pose, hands roughened by years of labor, a scar tracing the jawline, background turquoise, digital concept art", "domain": "visual scenes"}
{"terse": "musician play, in rain", "rich": "Emotional portrait showing a tranquil musician play, crow's feet deepened by laughter, fingers stained with pigment, dappled tones, impressionist brushwork", "domain": "visual scenes"}
{"terse": "elder wisdom, close-up", "rich": "A elder wisdom in sun-drenched pose, callused hands resting in the lap, a faint smile playing at the corners, background coral, digital concept art", "domain": "visual scenes"}
{"terse": "teen rebel, in rain", "rich": "Emotional portrait showing a brooding teen rebel, a scar tracing the jawline, callused hands resting in the lap, backlit tones, film noir lighting", "domain": "visual scenes"}
{"terse": "soldier tired, wide angle", "rich": "A nostalgic portrait of a soldier tired, wind-tousled hair catching the light, a scar tracing the jawline, cool blue lighting, pop art vibrancy", "domain": "visual scenes"}
{"terse": "dancer spin, dramatic lighting", "rich": "A delicate portrait of a dancer spin, a faint smile playing at the corners, eyes reflecting a distant memory, candlelit lighting, watercolor wash", "domain": "visual scenes"}
{"terse": "writer think, soft focus", "rich": "Emotional portrait showing a nostalgic writer think, deep lines mapping decades of experience, callused hands resting in the lap, dawn tones, cinematic still", "domain": "visual scenes"}
{"terse": "farmer proud, dramatic lighting", "rich": "Emotional portrait showing a radiant farmer proud, hands roughened by years of labor, eyes reflecting a distant memory, backlit tones, graphic novel style", "domain": "visual scenes"}
{"terse": "nurse tired, in rain", "rich": "A shadowed portrait of a nurse tired, gaze directed past the viewer into something unseen, crow's feet deepened by laughter, dappled lighting, minimalist composition", "domain": "visual scenes"}
{"terse": "pilot calm, wide angle", "rich": "A shadowed portrait of a pilot calm, a faint smile playing at the corners, a scar tracing the jawline, candlelit lighting, oil painting style", "domain": "visual scenes"}
{"terse": "artist paint, at dusk", "rich": "A artist paint in stark pose, weathered skin telling stories, weathered skin telling stories, background teal, minimalist composition", "domain": "visual scenes"}
{"terse": "chef focus, through fog", "rich": "Emotional portrait showing a somber chef focus, fingers stained with pigment, wind-tousled hair catching the light, candlelit tones, minimalist composition", "domain": "visual scenes"}
{"terse": "teacher smile, through fog", "rich": "A haunting portrait of a teacher smile, eyes reflecting a distant memory, hands roughened by years of labor, warm golden lighting, film noir lighting", "domain": "visual scenes"}
{"terse": "monk sit, at dusk", "rich": "A monk sit in gilded pose, hands roughened by years of labor, fingers stained with pigment, background ivory, digital concept art", "domain": "visual scenes"}
{"terse": "witch brew, through fog", "rich": "A witch brew in crumbling pose, a scar tracing the jawline, a scar tracing the jawline, background charcoal, oil painting style", "domain": "visual scenes"}
{"terse": "king throne, through fog", "rich": "Intimate depiction of a sun-drenched king throne, a pendant catching light at the collarbone, a faint smile playing at the corners, cool blue atmosphere, cinematic still", "domain": "visual scenes"}
{"terse": "thief sneak, dramatic lighting", "rich": "Intimate depiction of a tranquil thief sneak, deep lines mapping decades of experience, a single tear catching the light, neon atmosphere, art nouveau linework", "domain": "visual scenes"}
{"terse": "giant gentle, in rain", "rich": "Intimate depiction of a shadowed giant gentle, wind-tousled hair catching the light, a single tear catching the light, backlit atmosphere, watercolor wash", "domain": "visual scenes"}
{"terse": "twin mirror, close-up", "rich": "Emotional portrait showing a serene twin mirror, deep lines mapping decades of experience, wind-tousled hair catching the light, candlelit tones, art nouveau linework", "domain": "visual scenes"}
{"terse": "blind see, close-up", "rich": "Intimate depiction of a dramatic blind see, fingers stained with pigment, a pendant catching light at the collarbone, moonlit atmosphere, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "deaf hear, aerial view", "rich": "Emotional portrait showing a ethereal deaf hear, wind-tousled hair catching the light, a faint smile playing at the corners, neon tones, pop art vibrancy", "domain": "visual scenes"}
{"terse": "mute speak, close-up", "rich": "Emotional portrait showing a serene mute speak, hands roughened by years of labor, weathered skin telling stories, harsh overhead tones, classical realism", "domain": "visual scenes"}
{"terse": "lonely dark, through fog", "rich": "A sun-drenched portrait of a lonely dark, a scar tracing the jawline, a faint smile playing at the corners, moonlit lighting, impressionist brushwork", "domain": "visual scenes"}
{"terse": "joy burst, wide angle", "rich": "Emotional portrait showing a somber joy burst, crow's feet deepened by laughter, a single tear catching the light, warm golden tones, dreamy soft focus", "domain": "visual scenes"}
{"terse": "anger red, soft focus", "rich": "Intimate depiction of a gleaming anger red, a scar tracing the jawline, shoulders squared against an invisible burden, cool blue atmosphere, digital concept art", "domain": "visual scenes"}
{"terse": "calm blue, in rain", "rich": "Emotional portrait showing a ethereal calm blue, wind-tousled hair catching the light, a pendant catching light at the collarbone, neon tones, classical realism", "domain": "visual scenes"}
{"terse": "chaos spin, in rain", "rich": "Intimate depiction of a luminous chaos spin, a scar tracing the jawline, hands roughened by years of labor, twilight atmosphere, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "silence white, at dusk", "rich": "Surreal interpretation of white, lush forms dissolving into forest green, binary oppositions meeting at a fault line, dreamy soft focus", "domain": "visual scenes"}
{"terse": "memory fade, in rain", "rich": "Surreal interpretation of fade, forlorn forms dissolving into emerald, dissolving boundaries between form and void, graphic novel style", "domain": "visual scenes"}
{"terse": "hope rise, close-up", "rich": "Surreal interpretation of rise, majestic forms dissolving into violet purple, chaotic splatters resolved into harmony, expressionist distortion", "domain": "visual scenes"}
{"terse": "fear freeze, close-up", "rich": "Non-representational piece evoking freeze, binary oppositions meeting at a fault line, layered transparencies creating depth, dominated by golden amber, film noir lighting", "domain": "visual scenes"}
{"terse": "love wrap, soft focus", "rich": "Surreal interpretation of wrap, faded forms dissolving into silver grey, rhythmic repetition building tension, art nouveau linework", "domain": "visual scenes"}
{"terse": "time melt, in rain", "rich": "Non-representational piece evoking melt, repeating patterns diminishing into infinity, dissolving boundaries between form and void, dominated by sage green, classical realism", "domain": "visual scenes"}
{"terse": "dream float, aerial view", "rich": "Emotional landscape of pure float, sharp angular forms breaking through soft gradients, undulating waves of pure color, rendered in mauve and dusty rose, pop art vibrancy", "domain": "visual scenes"}
{"terse": "truth shine, soft focus", "rich": "Surreal interpretation of shine, somber forms dissolving into cerulean blue, chaotic splatters resolved into harmony, cinematic still", "domain": "visual scenes"}
{"terse": "lie shadow, in rain", "rich": "Geometric abstraction of shadow, gleaming shapes intersecting, fractured light dispersing into prismatic shards, impressionist brushwork", "domain": "visual scenes"}
{"terse": "peace settle, soft focus", "rich": "Non-representational piece evoking settle, binary oppositions meeting at a fault line, chaotic splatters resolved into harmony, dominated by coral, dreamy soft focus", "domain": "visual scenes"}
{"terse": "rage burn, close-up", "rich": "Geometric abstraction of burn, sun-drenched shapes intersecting, fractured light dispersing into prismatic shards, photorealistic rendering", "domain": "visual scenes"}
{"terse": "grief deep, dramatic lighting", "rich": "Abstract visualization of deep, midnight black and violet purple swirling in majestic motion, fractured light dispersing into prismatic shards, minimalist composition", "domain": "visual scenes"}
{"terse": "wonder wide, wide angle", "rich": "Abstract visualization of wide, burgundy and copper orange swirling in crumbling motion, binary oppositions meeting at a fault line, oil painting style", "domain": "visual scenes"}
{"terse": "shame hide, at dusk", "rich": "Non-representational piece evoking hide, rhythmic repetition building tension, binary oppositions meeting at a fault line, dominated by dusty rose, film noir lighting", "domain": "visual scenes"}
{"terse": "pride lift, at dusk", "rich": "Abstract visualization of lift, burgundy and pearl white swirling in tranquil motion, repeating patterns diminishing into infinity, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "doubt fog, dramatic lighting", "rich": "Geometric abstraction of fog, luminous shapes intersecting, repeating patterns diminishing into infinity, dreamy soft focus", "domain": "visual scenes"}
{"terse": "trust bridge, through fog", "rich": "Abstract visualization of bridge, golden amber and burnt sienna swirling in ethereal motion, undulating waves of pure color, expressionist distortion", "domain": "visual scenes"}
{"terse": "loss empty, soft focus", "rich": "Emotional landscape of pure empty, sharp angular forms breaking through soft gradients, fractured light dispersing into prismatic shards, rendered in dusty rose and forest green, expressionist distortion", "domain": "visual scenes"}
{"terse": "gain bright, aerial view", "rich": "Surreal interpretation of bright, brooding forms dissolving into mauve, rhythmic repetition building tension, impressionist brushwork", "domain": "visual scenes"}
{"terse": "change flow, soft focus", "rich": "Emotional landscape of pure flow, layered transparencies creating depth, chaotic splatters resolved into harmony, rendered in golden amber and copper orange, dreamy soft focus", "domain": "visual scenes"}
{"terse": "dragon fire, through fog", "rich": "Abstract visualization of fire, dusty rose and burgundy swirling in majestic motion, sharp angular forms breaking through soft gradients, classical realism", "domain": "visual scenes"}
{"terse": "elf forest, dramatic lighting", "rich": "Non-representational piece evoking forest, layered transparencies creating depth, sharp angular forms breaking through soft gradients, dominated by forest green, photorealistic rendering", "domain": "visual scenes"}
{"terse": "wizard tower, wide angle", "rich": "Non-representational piece evoking tower, rhythmic repetition building tension, repeating patterns diminishing into infinity, dominated by coral, expressionist distortion", "domain": "visual scenes"}
{"terse": "fairy glow, at dusk", "rich": "Emotional landscape of pure glow, repeating patterns diminishing into infinity, repeating patterns diminishing into infinity, rendered in sage green and emerald, film noir lighting", "domain": "visual scenes"}
{"terse": "knight ride, at dusk", "rich": "Geometric abstraction of ride, forlorn shapes intersecting, fractured light dispersing into prismatic shards, classical realism", "domain": "visual scenes"}
{"terse": "castle dark, close-up", "rich": "Geometric abstraction of dark, lush shapes intersecting, sharp angular forms breaking through soft gradients, pop art vibrancy", "domain": "visual scenes"}
{"terse": "sword magic, close-up", "rich": "Non-representational piece evoking magic, binary oppositions meeting at a fault line, binary oppositions meeting at a fault line, dominated by pearl white, watercolor wash", "domain": "visual scenes"}
{"terse": "potion brew, dramatic lighting", "rich": "Abstract visualization of brew, mauve and teal swirling in delicate motion, binary oppositions meeting at a fault line, art nouveau linework", "domain": "visual scenes"}
{"terse": "portal open, through fog", "rich": "Abstract visualization of open, midnight black and cerulean blue swirling in whimsical motion, dissolving boundaries between form and void, impressionist brushwork", "domain": "visual scenes"}
{"terse": "spell cast, at dusk", "rich": "Abstract visualization of cast, golden amber and forest green swirling in luminous motion, layered transparencies creating depth, dreamy soft focus", "domain": "visual scenes"}
{"terse": "griffin fly, at dusk", "rich": "Enchanted scene featuring leviathan illuminating, chains of starlight binding the scene, a sword humming with dormant power, bathed in ivory, oil painting style", "domain": "visual scenes"}
{"terse": "phoenix burn, through fog", "rich": "Epic fantasy scene: phoenix descending into in a luminous lava forge, a halo of magical energy pulsing outward, a sword humming with dormant power, cinematic still", "domain": "visual scenes"}
{"terse": "unicorn run, aerial view", "rich": "A fierce fantasy realm where specter circling, a halo of magical energy pulsing outward, sacred geometry etched into stone, rendered in photorealistic rendering", "domain": "visual scenes"}
{"terse": "troll bridge, wide angle", "rich": "A brooding fantasy realm where dragon resting upon, sacred geometry etched into stone, ethereal mist coiling around clawed feet, rendered in classical realism", "domain": "visual scenes"}
{"terse": "dwarf mine, close-up", "rich": "Enchanted scene featuring phoenix circling, ancient runes glowing along the edges, portals shimmering at the periphery, bathed in deep crimson, film noir lighting", "domain": "visual scenes"}
{"terse": "orb glow, close-up", "rich": "Legendary moment: specter illuminating in a floating citadel, ethereal mist coiling around clawed feet, majestic atmosphere, minimalist composition", "domain": "visual scenes"}
{"terse": "crystal cave, dramatic lighting", "rich": "Enchanted scene featuring dragon devouring, ancient runes glowing along the edges, a halo of magical energy pulsing outward, bathed in ochre, film noir lighting", "domain": "visual scenes"}
{"terse": "enchanted rose, through fog", "rich": "A ethereal fantasy realm where basilisk soaring above, ethereal mist coiling around clawed feet, a sword humming with dormant power, rendered in watercolor wash", "domain": "visual scenes"}
{"terse": "cursed mirror, soft focus", "rich": "Mythical sphinx soaring above amidst shadowed surroundings, ethereal mist coiling around clawed feet, burgundy and violet purple glow, cinematic still", "domain": "visual scenes"}
{"terse": "blessed shield, in rain", "rich": "Legendary moment: dragon devouring in a shadow realm, a sword humming with dormant power, brooding atmosphere, expressionist distortion", "domain": "visual scenes"}
{"terse": "shadow realm, soft focus", "rich": "Epic fantasy scene: unicorn emerging from in a dramatic ancient battlefield, portals shimmering at the periphery, chains of starlight binding the scene, dreamy soft focus", "domain": "visual scenes"}
{"terse": "light kingdom, soft focus", "rich": "Epic fantasy scene: unicorn devouring in a radiant void chasm, sacred geometry etched into stone, a halo of magical energy pulsing outward, dreamy soft focus", "domain": "visual scenes"}
{"terse": "void whisper, at dusk", "rich": "Legendary moment: unicorn awakening in a enchanted forest, ethereal mist coiling around clawed feet, stark atmosphere, cinematic still", "domain": "visual scenes"}
{"terse": "star forge, soft focus", "rich": "A delicate fantasy realm where behemoth descending into, a halo of magical energy pulsing outward, crystalline formations jutting from the ground, rendered in classical realism", "domain": "visual scenes"}
{"terse": "moon temple, soft focus", "rich": "Epic fantasy scene: basilisk transforming in a tranquil lava forge, ethereal mist coiling around clawed feet, ancient runes glowing along the edges, oil painting style", "domain": "visual scenes"}
{"terse": "sad rain, through fog", "rich": "An forlorn rain stretching to the horizon, mist rising from a hidden stream, lichen-covered boulders, painted in film noir lighting", "domain": "visual scenes"}
{"terse": "sunset beach, dramatic lighting", "rich": "Wide-angle view of a lush beach, indigo and emerald dominating the palette, lichen-covered boulders, dust motes caught in a shaft of light, pop art vibrancy", "domain": "visual scenes"}
{"terse": "mountain fog, through fog", "rich": "Breathtaking fog where a winding path disappearing around a bend, the sky a gradient of forest green to ivory, a fallen log bridging a narrow ravine, cinematic still", "domain": "visual scenes"}
{"terse": "desert night, at dusk", "rich": "A ethereal night bathed in candlelit, ripples spreading across a mirror-still pond, shadows stretching long and thin, impressionist brushwork", "domain": "visual scenes"}
{"terse": "forest dawn, soft focus", "rich": "An haunting dawn stretching to the horizon, mist rising from a hidden stream, weather-beaten fence posts trailing into fog, painted in cinematic still", "domain": "visual scenes"}
{"terse": "ocean storm, close-up", "rich": "An brooding storm stretching to the horizon, frost clinging to every surface, ripples spreading across a mirror-still pond, painted in photorealistic rendering", "domain": "visual scenes"}
{"terse": "winter village, in rain", "rich": "Breathtaking village where wildflowers dotting the foreground, the sky a gradient of copper orange to copper orange, dust motes caught in a shaft of light, oil painting style", "domain": "visual scenes"}
{"terse": "autumn field, wide angle", "rich": "An luminous field stretching to the horizon, a winding path disappearing around a bend, ancient ruins half-swallowed by vines, painted in photorealistic rendering", "domain": "visual scenes"}
{"terse": "volcano glow, close-up", "rich": "A sun-drenched glow bathed in rim, a solitary figure in the distance, wildflowers dotting the foreground, graphic novel style", "domain": "visual scenes"}
{"terse": "river mist, aerial view", "rich": "Breathtaking mist where tall grass bending in the wind, the sky a gradient of sage green to pearl white, dust motes caught in a shaft of light, cinematic still", "domain": "visual scenes"}
{"terse": "snowy peak, at dusk", "rich": "Breathtaking peak where frost clinging to every surface, the sky a gradient of slate grey to mauve, a solitary figure in the distance, oil painting style", "domain": "visual scenes"}
{"terse": "tropical shore, soft focus", "rich": "Panoramic shore at dawn, luminous atmosphere with a fallen log bridging a narrow ravine, frost clinging to every surface, rendered in hyperrealistic detail", "domain": "visual scenes"}
{"terse": "canyon shadow, soft focus", "rich": "Panoramic shadow at blue hour, intimate atmosphere with frost clinging to every surface, mist rising from a hidden stream, rendered in digital concept art", "domain": "visual scenes"}
{"terse": "lake mirror, through fog", "rich": "An weathered mirror stretching to the horizon, lichen-covered boulders, tall grass bending in the wind, painted in graphic novel style", "domain": "visual scenes"}
{"terse": "prairie wind, wide angle", "rich": "Breathtaking wind where weather-beaten fence posts trailing into fog, the sky a gradient of teal to turquoise, tall grass bending in the wind, film noir lighting", "domain": "visual scenes"}
{"terse": "jungle stream, in rain", "rich": "Panoramic stream at dusk, raw atmosphere with weather-beaten fence posts trailing into fog, a winding path disappearing around a bend, rendered in classical realism", "domain": "visual scenes"}
{"terse": "arctic light, soft focus", "rich": "Wide-angle view of a radiant light, violet purple and emerald dominating the palette, lichen-covered boulders, ancient ruins half-swallowed by vines, digital concept art", "domain": "visual scenes"}
{"terse": "meadow bloom, in rain", "rich": "An whimsical bloom stretching to the horizon, mist rising from a hidden stream, a winding path disappearing around a bend, painted in watercolor wash", "domain": "visual scenes"}
{"terse": "cliff edge, dramatic lighting", "rich": "An tranquil edge stretching to the horizon, ripples spreading across a mirror-still pond, reflections doubling the scene in still water, painted in art nouveau linework", "domain": "visual scenes"}
{"terse": "swamp fog, at dusk", "rich": "An forlorn fog stretching to the horizon, weather-beaten fence posts trailing into fog, reflections doubling the scene in still water, painted in watercolor wash", "domain": "visual scenes"}
{"terse": "moonlit valley, wide angle", "rich": "A haunting valley bathed in neon, ancient ruins half-swallowed by vines, frost clinging to every surface, classical realism", "domain": "visual scenes"}
{"terse": "sunrise ridge, at dusk", "rich": "Breathtaking ridge where a fallen log bridging a narrow ravine, the sky a gradient of violet purple to golden amber, reflections doubling the scene in still water, oil painting style", "domain": "visual scenes"}
{"terse": "thunder plain, close-up", "rich": "Breathtaking plain where ripples spreading across a mirror-still pond, the sky a gradient of copper orange to midnight black, lichen-covered boulders, impressionist brushwork", "domain": "visual scenes"}
{"terse": "frozen lake, through fog", "rich": "Wide-angle view of a ethereal lake, teal and copper orange dominating the palette, ripples spreading across a mirror-still pond, a fallen log bridging a narrow ravine, cinematic still", "domain": "visual scenes"}
{"terse": "dusty road, at dusk", "rich": "Wide-angle view of a brooding road, emerald and turquoise dominating the palette, lichen-covered boulders, birds wheeling in formation overhead, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "coastal cliff, at dusk", "rich": "Panoramic cliff at dusk, gilded atmosphere with ripples spreading across a mirror-still pond, wildflowers dotting the foreground, rendered in pop art vibrancy", "domain": "visual scenes"}
{"terse": "bamboo grove, dramatic lighting", "rich": "Wide-angle view of a raw grove, emerald and silver grey dominating the palette, lichen-covered stones, lichen-covered boulders, art nouveau linework", "domain": "visual scenes"}
{"terse": "lavender field, through fog", "rich": "Wide-angle view of a weathered field, emerald and ivory dominating the palette, mist rising from a hidden stream, mist rising from a hidden stream, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "coral reef, dramatic lighting", "rich": "Breathtaking reef where a fallen log bridging a narrow ravine, the sky a gradient of burgundy to ochre, tall grass bending in the wind, graphic novel style", "domain": "visual scenes"}
{"terse": "glacier cave, at dusk", "rich": "Breathtaking cave where ancient ruins half-swallowed by vines, the sky a gradient of vermillion to turquoise, a solitary figure in the distance, photorealistic rendering", "domain": "visual scenes"}
{"terse": "old man sad, close-up", "rich": "A sun-drenched portrait of a old man sad, eyes reflecting a distant memory, a pendant catching light at the collarbone, cool blue lighting, photorealistic rendering", "domain": "visual scenes"}
{"terse": "child laughing, aerial view", "rich": "Close-up of a tranquil child laughing, a single tear catching the light, deep lines mapping decades of experience, captured in impressionist brushwork", "domain": "visual scenes"}
{"terse": "warrior stare, dramatic lighting", "rich": "Emotional portrait showing a intimate warrior stare, a single tear catching the light, a faint smile playing at the corners, warm golden tones, classical realism", "domain": "visual scenes"}
{"terse": "queen crown, at dusk", "rich": "Emotional portrait showing a muted queen crown, deep lines mapping decades of experience, gaze directed past the viewer into something unseen, warm golden tones, photorealistic rendering", "domain": "visual scenes"}
{"terse": "musician play, aerial view", "rich": "A musician play in mysterious pose, wind-tousled hair catching the light, wind-tousled hair catching the light, background vermillion, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "elder wisdom, soft focus", "rich": "A elder wisdom in muted pose, a single tear catching the light, a scar tracing the jawline, background sage green, dreamy soft focus", "domain": "visual scenes"}
{"terse": "teen rebel, wide angle", "rich": "A tranquil portrait of a teen rebel, weathered skin telling stories, a faint smile playing at the corners, dawn lighting, minimalist composition", "domain": "visual scenes"}
{"terse": "soldier tired, dramatic lighting", "rich": "Emotional portrait showing a whimsical soldier tired, a faint smile playing at the corners, deep lines mapping decades of experience, harsh overhead tones, cinematic still", "domain": "visual scenes"}
{"terse": "dancer spin, close-up", "rich": "A lush portrait of a dancer spin, eyes reflecting a distant memory, a pendant catching light at the collarbone, cool blue lighting, expressionist distortion", "domain": "visual scenes"}
{"terse": "writer think, close-up", "rich": "A raw portrait of a writer think, weathered skin telling stories, wind-tousled hair catching the light, rim lighting, classical realism", "domain": "visual scenes"}
{"terse": "farmer proud, at dusk", "rich": "A mysterious portrait of a farmer proud, hands roughened by years of labor, callused hands resting in the lap, warm golden lighting, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "nurse tired, in rain", "rich": "Intimate depiction of a serene nurse tired, eyes reflecting a distant memory, crow's feet deepened by laughter, cool blue atmosphere, impressionist brushwork", "domain": "visual scenes"}
{"terse": "pilot calm, dramatic lighting", "rich": "Emotional portrait showing a sun-drenched pilot calm, shoulders squared against an invisible burden, gaze directed past the viewer into something unseen, neon tones, expressionist distortion", "domain": "visual scenes"}
{"terse": "artist paint, close-up", "rich": "Intimate depiction of a gilded artist paint, a pendant catching light at the collarbone, wind-tousled hair catching the light, warm golden atmosphere, art nouveau linework", "domain": "visual scenes"}
{"terse": "chef focus, through fog", "rich": "A chef focus in somber pose, gaze directed past the viewer into something unseen, a pendant catching light at the collarbone, background dusty rose, graphic novel style", "domain": "visual scenes"}
{"terse": "teacher smile, close-up", "rich": "A crumbling portrait of a teacher smile, gaze directed past the viewer into something unseen, shoulders squared against an invisible burden, rim lighting, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "monk sit, soft focus", "rich": "Close-up of a ethereal monk sit, fingers stained with pigment, wind-tousled hair catching the light, captured in classical realism", "domain": "visual scenes"}
{"terse": "witch brew, dramatic lighting", "rich": "Intimate depiction of a frost-kissed witch brew, a single tear catching the light, weathered skin telling stories, cool blue atmosphere, photorealistic rendering", "domain": "visual scenes"}
{"terse": "king throne, aerial view", "rich": "Emotional portrait showing a pristine king throne, weathered skin telling stories, a single tear catching the light, moonlit tones, pop art vibrancy", "domain": "visual scenes"}
{"terse": "thief sneak, aerial view", "rich": "Intimate depiction of a nostalgic thief sneak, callused hands resting in the lap, crow's feet deepened by laughter, backlit atmosphere, classical realism", "domain": "visual scenes"}
{"terse": "giant gentle, aerial view", "rich": "A giant gentle in faded pose, a pendant catching light at the collarbone, wind-tousled hair catching the light, background forest green, digital concept art", "domain": "visual scenes"}
{"terse": "twin mirror, at dusk", "rich": "A twin mirror in whimsical pose, a faint smile playing at the corners, gaze directed past the viewer into something unseen, background burnt sienna, classical realism", "domain": "visual scenes"}
{"terse": "blind see, through fog", "rich": "Close-up of a tranquil blind see, fingers stained with pigment, a scar tracing the jawline, captured in graphic novel style", "domain": "visual scenes"}
{"terse": "deaf hear, through fog", "rich": "A tranquil portrait of a deaf hear, deep lines mapping decades of experience, crow's feet deepened by laughter, twilight lighting, film noir lighting", "domain": "visual scenes"}
{"terse": "mute speak, close-up", "rich": "Intimate depiction of a pristine mute speak, crow's feet deepened by laughter, eyes reflecting a distant memory, twilight atmosphere, cinematic still", "domain": "visual scenes"}
{"terse": "lonely dark, dramatic lighting", "rich": "Emotional portrait showing a shadowed lonely dark, wind-tousled hair catching the light, a faint smile playing at the corners, moonlit tones, expressionist distortion", "domain": "visual scenes"}
{"terse": "joy burst, soft focus", "rich": "Close-up of a lush joy burst, shoulders squared against an invisible burden, fingers stained with pigment, captured in classical realism", "domain": "visual scenes"}
{"terse": "anger red, wide angle", "rich": "Emotional portrait showing a somber anger red, a single tear catching the light, deep lines mapping decades of experience, soft diffused tones, pop art vibrancy", "domain": "visual scenes"}
{"terse": "calm blue, at dusk", "rich": "A calm blue in muted pose, a single tear catching the light, hands roughened by years of labor, background burnt sienna, pop art vibrancy", "domain": "visual scenes"}
{"terse": "chaos spin, close-up", "rich": "Close-up of a stark chaos spin, a single tear catching the light, shoulders squared against an invisible burden, captured in classical realism", "domain": "visual scenes"}
{"terse": "silence white, through fog", "rich": "Geometric abstraction of white, pristine shapes intersecting, repeating patterns diminishing into infinity, cinematic still", "domain": "visual scenes"}
{"terse": "memory fade, at dusk", "rich": "Emotional landscape of pure fade, chaotic splatters resolved into harmony, chaotic splatters resolved into harmony, rendered in burgundy and charcoal, cinematic still", "domain": "visual scenes"}
{"terse": "hope rise, soft focus", "rich": "Geometric abstraction of rise, muted shapes intersecting, chaotic splatters resolved into harmony, dreamy soft focus", "domain": "visual scenes"}
{"terse": "fear freeze, wide angle", "rich": "Geometric abstraction of freeze, lush shapes intersecting, repeating patterns diminishing into infinity, oil painting style", "domain": "visual scenes"}
{"terse": "love wrap, dramatic lighting", "rich": "Non-representational piece evoking wrap, dissolving boundaries between form and void, fractured light dispersing into prismatic shards, dominated by charcoal, film noir lighting", "domain": "visual scenes"}
{"terse": "time melt, aerial view", "rich": "Emotional landscape of pure melt, undulating waves of pure color, dissolving boundaries between form and void, rendered in coral and charcoal, hyperrealistic detail", "domain": "visual scenes"}
{"terse": "dream float, through fog", "rich": "Geometric abstraction of float, faded shapes intersecting, dissolving boundaries between form and void, impressionist brushwork", "domain": "visual scenes"}
{"terse": "truth shine, soft focus", "rich": "Geometric abstraction of shine, radiant shapes intersecting, sharp angular forms breaking through soft gradients, photorealistic rendering", "domain": "visual scenes"}
{"terse": "lie shadow, close-up", "rich": "Abstract visualization of shadow, silver grey and golden amber swirling in whimsical motion, rhythmic repetition building tension, expressionist distortion", "domain": "visual scenes"}
{"terse": "peace settle, wide angle", "rich": "Surreal interpretation of settle, stark forms dissolving into turquoise, a central void drawing the eye inward, digital concept art", "domain": "visual scenes"}
{"terse": "rage burn, dramatic lighting", "rich": "Non-representational piece evoking burn, binary oppositions meeting at a fault line, dissolving boundaries between form and void, dominated by violet purple, watercolor wash", "domain": "visual scenes"}
{"terse": "grief deep, through fog", "rich": "Non-representational piece evoking deep, undulating waves of pure color, binary oppositions meeting at a fault line, dominated by cerulean blue, dreamy soft focus", "domain": "visual scenes"}
{"terse": "wonder wide, close-up", "rich": "Surreal interpretation of wide, crumbling forms dissolving into burgundy, sharp angular forms breaking through soft gradients, classical realism", "domain": "visual scenes"}
{"terse": "shame hide, at dusk", "rich": "Surreal interpretation of hide, frost-kissed forms dissolving into indigo, rhythmic repetition building tension, dreamy soft focus", "domain": "visual scenes"}
{"terse": "pride lift, wide angle", "rich": "Emotional landscape of pure lift, a central void drawing the eye inward, undulating waves of pure color, rendered in golden amber and vermillion, pop art vibrancy", "domain": "visual scenes"}
{"terse": "doubt fog, aerial view", "rich": "Surreal interpretation of fog, fierce forms dissolving into vermillion, layered transparencies creating depth, minimalist composition", "domain": "visual scenes"}
{"terse": "trust bridge, soft focus", "rich": "Non-representational piece evoking bridge, sharp angular forms breaking through soft gradients, repeating patterns diminishing into infinity, dominated by emerald, watercolor wash", "domain": "visual scenes"}
{"terse": "loss empty, close-up", "rich": "Surreal interpretation of empty, pristine forms dissolving into teal, chaotic splatters resolved into harmony, digital concept art", "domain": "visual scenes"}
{"terse": "gain bright, close-up", "rich": "Surreal interpretation of bright, intimate forms dissolving into teal, undulating waves of pure color, photorealistic rendering", "domain": "visual scenes"}
{"terse": "change flow, dramatic lighting", "rich": "Surreal interpretation of flow, luminous forms dissolving into violet purple, a central void drawing the eye inward, watercolor wash", "domain": "visual scenes"}