"""Visual provider abstraction. ``CompositeProvider`` renders procedural pixel art and caches PNGs to disk so each portrait/scene/prop is drawn once - instant, offline, deterministic, no model. """ from __future__ import annotations from pathlib import Path from typing import Protocol from ..config import Settings from ..constants import SPRITES_DIR from ..schemas.visual import VisualDescriptor from . import compositor from .cache import cached_png class VisualProvider(Protocol): is_local: bool def portrait_path(self, descriptor: VisualDescriptor | None, key: str) -> Path: ... def portrait_sheet_path(self, descriptor: VisualDescriptor | None, key: str) -> Path: ... def scene_path(self, name: str, key: str, accent: str | None = None) -> Path: ... def prop_path(self, name: str, key: str, accent: str | None = None) -> Path: ... class CompositeProvider: is_local = True def __init__(self, cache_dir: Path | None = None) -> None: self._cache = cache_dir or (SPRITES_DIR / "cache") def portrait_path(self, descriptor: VisualDescriptor | None, key: str) -> Path: accent = descriptor.accent_color if descriptor else "" mood = descriptor.mood if descriptor else "" return cached_png( self._cache, ("portrait", key, mood or "", accent or ""), lambda: compositor.render_portrait(descriptor, key), ) def portrait_sheet_path(self, descriptor: VisualDescriptor | None, key: str) -> Path: accent = descriptor.accent_color if descriptor else "" mood = descriptor.mood if descriptor else "" return cached_png( self._cache, ("sheet", key, mood or "", accent or ""), lambda: compositor.render_portrait_sheet(descriptor, key), ) def scene_path(self, name: str, key: str, accent: str | None = None) -> Path: return cached_png( self._cache, ("scene", key, name, accent or ""), lambda: compositor.render_scene(name, key, accent), ) def prop_path(self, name: str, key: str, accent: str | None = None) -> Path: return cached_png( self._cache, ("prop", key, name, accent or ""), lambda: compositor.render_prop(name, key, accent), ) def make_visual_provider(settings: Settings) -> VisualProvider: return CompositeProvider()