| """
|
| CanRun Engine - Main orchestration module for Universal Game Compatibility Checker
|
| Privacy-focused game compatibility analysis for NVIDIA RTX/GTX systems.
|
| """
|
|
|
| import logging
|
| import asyncio
|
| import json
|
| import os
|
| import re
|
| from typing import Dict, List, Optional, Tuple, Any
|
| from dataclasses import dataclass, asdict
|
| from datetime import datetime, timedelta
|
|
|
| from src.privacy_aware_hardware_detector import PrivacyAwareHardwareDetector, PrivacyAwareHardwareSpecs
|
| from src.game_requirements_fetcher import GameRequirementsFetcher, GameRequirements
|
| from src.optimized_game_fuzzy_matcher import OptimizedGameFuzzyMatcher
|
| from src.compatibility_analyzer import CompatibilityAnalyzer, CompatibilityAnalysis, ComponentAnalysis, ComponentType, CompatibilityLevel
|
| from src.dynamic_performance_predictor import DynamicPerformancePredictor, PerformanceAssessment, PerformanceTier
|
| from src.rtx_llm_analyzer import GAssistLLMAnalyzer, LLMAnalysisResult
|
|
|
|
|
| @dataclass
|
| class CanRunResult:
|
| """Complete CanRun analysis result."""
|
| game_name: str
|
| timestamp: str
|
| hardware_specs: PrivacyAwareHardwareSpecs
|
| game_requirements: GameRequirements
|
| compatibility_analysis: CompatibilityAnalysis
|
| performance_prediction: PerformanceAssessment
|
| llm_analysis: Optional[Dict[str, LLMAnalysisResult]]
|
| cache_used: bool
|
| analysis_time_ms: int
|
|
|
| def get_minimum_requirements_status(self) -> Dict[str, Any]:
|
| """Get clear status about minimum requirements compliance."""
|
| return self.compatibility_analysis.get_minimum_requirements_status()
|
|
|
| def get_runnable_status_message(self) -> str:
|
| """Get simple runnable status message for CANRUN."""
|
| return self.compatibility_analysis.get_runnable_status()
|
|
|
| def can_run_game(self) -> bool:
|
| """Check if the game can run on minimum requirements."""
|
| return self.compatibility_analysis.can_run_minimum
|
|
|
| def exceeds_recommended_requirements(self) -> bool:
|
| """Check if system exceeds recommended requirements."""
|
| return self.compatibility_analysis.can_run_recommended
|
|
|
|
|
| class CanRunEngine:
|
| """Main CanRun engine for privacy-aware game compatibility checking."""
|
|
|
| def __init__(self, cache_dir: str = "cache", enable_llm: bool = True):
|
| """Initialize CanRun engine with all components."""
|
| assert isinstance(cache_dir, str), "Cache directory must be a string"
|
| assert isinstance(enable_llm, bool), "LLM enable flag must be boolean"
|
|
|
| self.logger = logging.getLogger(__name__)
|
| self.cache_dir = cache_dir
|
| self.cache_duration = timedelta(minutes=15)
|
| self.enable_llm = enable_llm
|
|
|
|
|
| self.llm_analyzer = None
|
| if enable_llm:
|
| try:
|
| self.llm_analyzer = GAssistLLMAnalyzer()
|
| self.logger.info("G-Assist LLM analyzer initialized")
|
| except Exception as e:
|
| self.logger.warning(f"LLM analyzer initialization failed: {e}")
|
|
|
|
|
| self.hardware_detector = PrivacyAwareHardwareDetector()
|
| self.requirements_fetcher = GameRequirementsFetcher(self.llm_analyzer)
|
| self.fuzzy_matcher = OptimizedGameFuzzyMatcher()
|
| self.compatibility_analyzer = CompatibilityAnalyzer()
|
| self.performance_predictor = DynamicPerformancePredictor()
|
|
|
|
|
| os.makedirs(cache_dir, exist_ok=True)
|
| assert os.path.isdir(cache_dir), f"Cache directory creation failed: {cache_dir}"
|
|
|
|
|
| self._hardware_cache: Optional[PrivacyAwareHardwareSpecs] = None
|
|
|
| self.logger.info("CanRun engine initialized successfully")
|
|
|
| async def check_game_compatibility(self, game_name: str, use_cache: bool = True) -> CanRunResult:
|
| """
|
| Main entry point for game compatibility checking.
|
|
|
| Args:
|
| game_name: Name of the game to check
|
| use_cache: Whether to use cached results
|
|
|
| Returns:
|
| Complete CanRun analysis result
|
| """
|
|
|
| assert game_name and isinstance(game_name, str), "Game name must be a non-empty string"
|
| assert isinstance(use_cache, bool), "Cache flag must be boolean"
|
|
|
| game_name = game_name.strip()
|
| assert len(game_name) > 0, "Game name cannot be empty after strip"
|
|
|
| start_time = datetime.now()
|
| self.logger.info(f"Starting compatibility check for: {game_name}")
|
|
|
|
|
| all_known_games = self.requirements_fetcher.get_all_cached_game_names()
|
| if not all_known_games:
|
| self.logger.warning("No games in local cache. Matching will rely on external sources.")
|
|
|
| match_result = await self.fuzzy_matcher.find_best_match(game_name, all_known_games)
|
|
|
| if not match_result:
|
|
|
| self.logger.warning(f"No confident match for '{game_name}'. Proceeding with original name.")
|
| corrected_game_name = game_name
|
| else:
|
| corrected_game_name, match_confidence = match_result
|
| self.logger.info(f"Query '{game_name}' matched to '{corrected_game_name}' with confidence {match_confidence:.2f}")
|
|
|
|
|
| if use_cache:
|
| normalized_name = self.fuzzy_matcher.normalize_game_name(corrected_game_name)
|
| cache_file = os.path.join(self.cache_dir, f"{normalized_name}.json")
|
| cached_result = self._load_cache_file(cache_file)
|
| if cached_result:
|
| self.logger.info(f"Returning cached result for '{corrected_game_name}'")
|
| return cached_result
|
|
|
|
|
| game_requirements = await self._fetch_game_requirements(corrected_game_name)
|
| if game_requirements is None:
|
| raise ValueError(f"Game requirements not found for '{corrected_game_name}'.")
|
|
|
|
|
| hardware_specs = await self._get_hardware_specs()
|
| assert hardware_specs is not None, "Hardware detection failed"
|
|
|
|
|
| compatibility_analysis = await self._analyze_compatibility(
|
| game_name, hardware_specs, game_requirements
|
| )
|
| assert compatibility_analysis is not None, "Compatibility analysis failed"
|
|
|
|
|
| hardware_dict = {
|
| "gpu_model": hardware_specs.gpu_model,
|
| "gpu_vram_gb": hardware_specs.gpu_vram_gb,
|
| "cpu_model": hardware_specs.cpu_model,
|
| "ram_total_gb": hardware_specs.ram_total_gb,
|
| "supports_rtx": hardware_specs.supports_rtx,
|
| "supports_dlss": hardware_specs.supports_dlss
|
| }
|
|
|
| game_requirements_dict = {
|
| "minimum_gpu": game_requirements.minimum_gpu,
|
| "recommended_gpu": game_requirements.recommended_gpu,
|
| "minimum_cpu": game_requirements.minimum_cpu,
|
| "recommended_cpu": game_requirements.recommended_cpu,
|
| "minimum_ram_gb": game_requirements.minimum_ram_gb,
|
| "recommended_ram_gb": game_requirements.recommended_ram_gb
|
| }
|
|
|
| performance_prediction = await asyncio.get_event_loop().run_in_executor(
|
| None, self.performance_predictor.assess_performance, hardware_dict, game_requirements_dict
|
| )
|
| assert performance_prediction is not None, "Performance assessment failed"
|
|
|
|
|
| llm_analysis = None
|
| if self.llm_analyzer:
|
| llm_analysis = await self._perform_llm_analysis(
|
| compatibility_analysis, performance_prediction, hardware_specs
|
| )
|
|
|
|
|
| analysis_time = int((datetime.now() - start_time).total_seconds() * 1000)
|
|
|
|
|
| result = CanRunResult(
|
| game_name=corrected_game_name,
|
| timestamp=datetime.now().isoformat(),
|
| hardware_specs=hardware_specs,
|
| game_requirements=game_requirements,
|
| compatibility_analysis=compatibility_analysis,
|
| performance_prediction=performance_prediction,
|
| llm_analysis=llm_analysis,
|
| cache_used=False,
|
| analysis_time_ms=analysis_time
|
| )
|
|
|
|
|
| if use_cache:
|
| self._save_cached_result(corrected_game_name, result)
|
|
|
| self.logger.info(f"Analysis completed for {game_name} in {analysis_time}ms")
|
| return result
|
|
|
| async def get_hardware_info(self) -> PrivacyAwareHardwareSpecs:
|
| """Get current hardware specifications."""
|
| return await self._get_hardware_specs()
|
|
|
| async def batch_check_games(self, game_names: List[str], use_cache: bool = True) -> List[CanRunResult]:
|
| """Check compatibility for multiple games."""
|
| assert isinstance(game_names, list), "Game names must be a list"
|
| assert all(isinstance(name, str) for name in game_names), "All game names must be strings"
|
| assert len(game_names) > 0, "Game names list cannot be empty"
|
|
|
| self.logger.info(f"Starting batch check for {len(game_names)} games")
|
|
|
| results = []
|
| for game_name in game_names:
|
| try:
|
| result = await self.check_game_compatibility(game_name, use_cache)
|
| results.append(result)
|
| except Exception as e:
|
| self.logger.error(f"Batch check failed for {game_name}: {e}")
|
| results.append(self._create_error_result(game_name, str(e)))
|
|
|
| self.logger.info(f"Batch check completed for {len(game_names)} games")
|
| return results
|
|
|
| def clear_cache(self) -> None:
|
| """Clear all cached results."""
|
| assert os.path.isdir(self.cache_dir), "Cache directory does not exist"
|
|
|
| cache_files = [f for f in os.listdir(self.cache_dir) if f.endswith('.json')]
|
| for cache_file in cache_files:
|
| os.remove(os.path.join(self.cache_dir, cache_file))
|
|
|
| self.logger.info(f"Cleared {len(cache_files)} cache files")
|
|
|
| def get_cache_stats(self) -> Dict[str, int]:
|
| """Get cache statistics."""
|
| assert os.path.isdir(self.cache_dir), "Cache directory does not exist"
|
|
|
| cache_files = [f for f in os.listdir(self.cache_dir) if f.endswith('.json')]
|
| total_size = sum(os.path.getsize(os.path.join(self.cache_dir, f)) for f in cache_files)
|
|
|
| return {
|
| 'total_files': len(cache_files),
|
| 'total_size_bytes': total_size,
|
| 'total_size_mb': round(total_size / (1024 * 1024), 2)
|
| }
|
|
|
| async def _get_hardware_specs(self) -> PrivacyAwareHardwareSpecs:
|
| """Get hardware specifications with session caching."""
|
| if self._hardware_cache is None:
|
|
|
| self._hardware_cache = await self.hardware_detector.get_hardware_specs()
|
| assert self._hardware_cache is not None, "Hardware detection returned None"
|
|
|
| return self._hardware_cache
|
|
|
| async def _fetch_game_requirements(self, game_name: str) -> GameRequirements:
|
| """Fetch game requirements from available sources."""
|
| assert game_name and isinstance(game_name, str), "Game name must be a non-empty string"
|
|
|
| requirements = await self.requirements_fetcher.fetch_requirements(game_name)
|
| assert requirements is not None, f"Requirements not found for {game_name}"
|
|
|
| return requirements
|
|
|
| async def _analyze_compatibility(self, game_name: str,
|
| hardware_specs: PrivacyAwareHardwareSpecs,
|
| game_requirements: GameRequirements) -> CompatibilityAnalysis:
|
| """Analyze hardware compatibility with game requirements."""
|
| assert all([game_name, hardware_specs, game_requirements]), "All parameters are required"
|
|
|
| analysis = await asyncio.get_event_loop().run_in_executor(
|
| None, self.compatibility_analyzer.analyze_compatibility,
|
| game_name, hardware_specs, game_requirements
|
| )
|
| assert analysis is not None, "Compatibility analysis returned None"
|
|
|
| return analysis
|
|
|
|
|
| async def _predict_advanced_performance(self, hardware_specs: Dict, game_requirements: Dict = None) -> Dict:
|
| """
|
| Predict game performance using the advanced tiered assessment system.
|
|
|
| Args:
|
| hardware_specs: Hardware specifications from the detector
|
| game_requirements: Optional game requirements
|
|
|
| Returns:
|
| Dict containing advanced performance assessment with tier information
|
| """
|
| loop = asyncio.get_event_loop()
|
| assessment = await loop.run_in_executor(
|
| None,
|
| self.performance_predictor.predict_advanced_performance,
|
| hardware_specs,
|
| game_requirements
|
| )
|
|
|
|
|
| return {
|
| 'tier': assessment.tier.name,
|
| 'tier_description': assessment.tier_description,
|
| 'score': assessment.score,
|
| 'expected_fps': assessment.expected_fps,
|
| 'recommended_settings': assessment.recommended_settings,
|
| 'recommended_resolution': assessment.recommended_resolution,
|
| 'bottlenecks': assessment.bottlenecks,
|
| 'upgrade_suggestions': assessment.upgrade_suggestions
|
| }
|
|
|
| def _get_cached_result(self, game_name: str) -> Optional[CanRunResult]:
|
| """DEPRECATED: This method is no longer the primary way to get cached results.
|
| It is kept for potential direct cache inspection but should not be used in the main workflow.
|
| The main workflow now fetches requirements first, then checks the cache with the corrected name.
|
| """
|
| normalized_name = self.fuzzy_matcher.normalize_game_name(game_name)
|
| cache_file = os.path.join(self.cache_dir, f"{normalized_name}.json")
|
| return self._load_cache_file(cache_file)
|
|
|
| def _load_cache_file(self, cache_file: str) -> Optional[CanRunResult]:
|
| """Load and validate a single cache file."""
|
|
|
| if not os.path.isfile(cache_file):
|
| return None
|
|
|
| try:
|
| mtime = os.path.getmtime(cache_file)
|
| if (datetime.now().timestamp() - mtime) > self.cache_duration.total_seconds():
|
|
|
| return None
|
|
|
| with open(cache_file, "r", encoding="utf-8") as f:
|
| data = json.load(f)
|
|
|
|
|
| return self._reconstruct_canrun_result(data)
|
| except Exception as e:
|
| self.logger.warning(f"Failed to load cache file {cache_file}: {e}")
|
| return None
|
|
|
| def _reconstruct_canrun_result(self, data: Dict[str, Any]) -> CanRunResult:
|
| """Reconstruct CanRunResult from dictionary data."""
|
|
|
| hardware_specs = PrivacyAwareHardwareSpecs(**data['hardware_specs'])
|
| game_requirements = GameRequirements(**data['game_requirements'])
|
|
|
|
|
| compat_data = data['compatibility_analysis'].copy()
|
| if 'component_analyses' in compat_data:
|
| component_analyses = []
|
| for comp_data in compat_data['component_analyses']:
|
| if isinstance(comp_data, dict):
|
|
|
| component_value = comp_data['component']
|
| if isinstance(component_value, str):
|
|
|
| if '.' in component_value:
|
| component_value = component_value.split('.')[-1]
|
| try:
|
| component_type = ComponentType[component_value]
|
| except KeyError:
|
| component_type = ComponentType(component_value)
|
| else:
|
| component_type = component_value
|
|
|
|
|
| component_analyses.append(ComponentAnalysis(
|
| component=component_type,
|
| meets_minimum=comp_data['meets_minimum'],
|
| meets_recommended=comp_data['meets_recommended'],
|
| score=comp_data['score'],
|
| bottleneck_factor=comp_data['bottleneck_factor'],
|
| details=comp_data['details'],
|
| upgrade_suggestion=comp_data.get('upgrade_suggestion')
|
| ))
|
| else:
|
|
|
| component_analyses.append(comp_data)
|
| compat_data['component_analyses'] = component_analyses
|
|
|
|
|
| if isinstance(compat_data.get('overall_compatibility'), str):
|
| compat_data['overall_compatibility'] = CompatibilityLevel(compat_data['overall_compatibility'])
|
|
|
|
|
| if 'bottlenecks' in compat_data:
|
| bottlenecks = []
|
| for bottleneck in compat_data['bottlenecks']:
|
| if isinstance(bottleneck, str):
|
|
|
| if '.' in bottleneck:
|
| bottleneck = bottleneck.split('.')[-1]
|
| try:
|
| bottlenecks.append(ComponentType[bottleneck])
|
| except KeyError:
|
| bottlenecks.append(ComponentType(bottleneck))
|
| else:
|
| bottlenecks.append(bottleneck)
|
| compat_data['bottlenecks'] = bottlenecks
|
|
|
| compatibility_analysis = CompatibilityAnalysis(**compat_data)
|
| performance_prediction = PerformanceAssessment(**data['performance_prediction'])
|
|
|
|
|
| llm_analysis = None
|
| if data.get('llm_analysis'):
|
| llm_analysis = {}
|
| for key, value in data['llm_analysis'].items():
|
| llm_analysis[key] = LLMAnalysisResult(**value)
|
|
|
| return CanRunResult(
|
| game_name=data['game_name'],
|
| timestamp=data['timestamp'],
|
| hardware_specs=hardware_specs,
|
| game_requirements=game_requirements,
|
| compatibility_analysis=compatibility_analysis,
|
| performance_prediction=performance_prediction,
|
| llm_analysis=llm_analysis,
|
| cache_used=data.get('cache_used', True),
|
| analysis_time_ms=data.get('analysis_time_ms', 0)
|
| )
|
|
|
| def _save_cached_result(self, game_name: str, result: CanRunResult) -> None:
|
| """Save analysis result to cache using normalized game name."""
|
|
|
|
|
|
|
| normalized_name = self.fuzzy_matcher.normalize_game_name(game_name)
|
| cache_file = os.path.join(self.cache_dir, f"{normalized_name}.json")
|
|
|
|
|
| os.makedirs(self.cache_dir, exist_ok=True)
|
|
|
| try:
|
|
|
| result_dict = asdict(result)
|
|
|
|
|
| result_dict['game_name'] = normalized_name
|
|
|
| with open(cache_file, "w", encoding="utf-8") as f:
|
| json.dump(result_dict, f, indent=2, default=str)
|
|
|
| self.logger.debug(f"Cached result for '{game_name}' as '{normalized_name}'")
|
| except Exception as e:
|
| self.logger.warning(f"Failed to save cache for {game_name}: {e}")
|
|
|
| async def _perform_llm_analysis(self, compatibility_analysis: CompatibilityAnalysis,
|
| performance_prediction: PerformanceAssessment,
|
| hardware_specs: PrivacyAwareHardwareSpecs) -> Optional[Dict[str, LLMAnalysisResult]]:
|
| """Perform LLM analysis if G-Assist is available."""
|
| if not self.llm_analyzer:
|
| return None
|
|
|
| try:
|
|
|
| context = {
|
| 'compatibility': compatibility_analysis,
|
| 'performance': performance_prediction,
|
| 'hardware': hardware_specs
|
| }
|
|
|
|
|
| llm_result = await self.llm_analyzer.analyze_bottlenecks(context)
|
|
|
| return {'analysis': llm_result} if llm_result else None
|
|
|
| except Exception as e:
|
| self.logger.warning(f"LLM analysis failed: {e}")
|
| return None
|
|
|
| def _create_error_result(self, game_name: str, error_message: str) -> CanRunResult:
|
| """Create an error result for failed analysis."""
|
| from datetime import datetime
|
|
|
|
|
| error_hardware = PrivacyAwareHardwareSpecs(
|
| gpu_model="Unknown",
|
| gpu_vram_gb=0,
|
| cpu_name="Unknown",
|
| cpu_cores=0,
|
| cpu_threads=0,
|
| ram_gb=0,
|
| is_nvidia_gpu=False,
|
| supports_rtx=False,
|
| supports_dlss=False,
|
| nvidia_driver_version="Unknown",
|
| os_name="Unknown",
|
| directx_version="Unknown"
|
| )
|
|
|
|
|
| error_requirements = GameRequirements(
|
| game_name=game_name,
|
| minimum_cpu="Unknown",
|
| minimum_gpu="Unknown",
|
| minimum_ram_gb=0,
|
| minimum_vram_gb=0,
|
| minimum_storage_gb=0,
|
| recommended_cpu="Unknown",
|
| recommended_gpu="Unknown",
|
| recommended_ram_gb=0,
|
| recommended_vram_gb=0,
|
| recommended_storage_gb=0,
|
| supports_rtx=False,
|
| supports_dlss=False,
|
| directx_version="Unknown"
|
| )
|
|
|
|
|
| error_compatibility = CompatibilityAnalysis(
|
| game_name=game_name,
|
| overall_compatibility="incompatible",
|
| cpu_compatibility="error",
|
| gpu_compatibility="error",
|
| ram_compatibility="error",
|
| vram_compatibility="error",
|
| storage_compatibility="error",
|
| overall_score=0,
|
| bottlenecks=[f"Error: {error_message}"],
|
| recommendations=[]
|
| )
|
|
|
|
|
| error_performance = PerformanceAssessment(
|
| score=0,
|
| tier=PerformanceTier.F,
|
| tier_description="Error occurred during analysis",
|
| expected_fps=0,
|
| recommended_settings="Unable to determine",
|
| recommended_resolution="Unknown",
|
| bottlenecks=[],
|
| upgrade_suggestions=["Please retry the analysis"]
|
| )
|
|
|
| return CanRunResult(
|
| game_name=game_name,
|
| timestamp=datetime.now().isoformat(),
|
| hardware_specs=error_hardware,
|
| game_requirements=error_requirements,
|
| compatibility_analysis=error_compatibility,
|
| performance_prediction=error_performance,
|
| llm_analysis=None,
|
| cache_used=False,
|
| analysis_time_ms=0
|
| )
|
|
|
| def _parse_ram_value(self, ram_str: str) -> int:
|
| """Parse RAM value from string to integer GB."""
|
| if not ram_str or ram_str == "Unknown":
|
| return 0
|
|
|
|
|
| ram_str = str(ram_str).upper()
|
|
|
|
|
| match = re.search(r'(\d+)\s*(GB|MB|G|M)?', ram_str)
|
| if match:
|
| value = int(match.group(1))
|
| unit = match.group(2) or 'GB'
|
|
|
|
|
| if unit in ['MB', 'M']:
|
| value = max(1, value // 1024)
|
|
|
| return value
|
|
|
| return 0
|
|
|
| async def analyze_multiple_games(self, game_names: List[str], use_cache: bool = True) -> Dict[str, Optional[CanRunResult]]:
|
| """Analyze multiple games and convert the results to a dictionary format expected by tests.
|
|
|
| Args:
|
| game_names: List of game names to analyze
|
| use_cache: Whether to use cached results
|
|
|
| Returns:
|
| Dictionary containing compatibility and performance analysis in the format expected by tests
|
| """
|
| results = {}
|
|
|
| for game_name in game_names:
|
| try:
|
| result = await self.check_game_compatibility(game_name, use_cache)
|
| results[game_name] = result
|
| except Exception as e:
|
| self.logger.error(f"Failed to analyze {game_name}: {e}")
|
| results[game_name] = None
|
|
|
|
|
| return results
|
|
|
| async def get_system_info(self) -> Dict[str, Any]:
|
| """Get comprehensive system information."""
|
| hardware_specs = await self._get_hardware_specs()
|
|
|
| return {
|
| 'cpu': {
|
| 'name': hardware_specs.cpu_name,
|
| 'cores': hardware_specs.cpu_cores,
|
| 'threads': hardware_specs.cpu_threads
|
| },
|
| 'gpu': {
|
| 'name': hardware_specs.gpu_model,
|
| 'vram_gb': hardware_specs.gpu_vram_gb,
|
| 'supports_rtx': hardware_specs.supports_rtx,
|
| 'supports_dlss': hardware_specs.supports_dlss,
|
| 'driver_version': hardware_specs.nvidia_driver_version
|
| },
|
| 'memory': {
|
| 'total': hardware_specs.ram_gb
|
| },
|
| 'system': {
|
| 'os': hardware_specs.os_name,
|
| 'directx': hardware_specs.directx_version
|
| }
|
| }
|
|
|
| async def get_optimization_suggestions(self, game_name: str, settings: str, resolution: str) -> List[Dict[str, str]]:
|
| """Get optimization suggestions for specific game and settings."""
|
| try:
|
|
|
| hardware_specs = await self._get_hardware_specs()
|
| game_requirements = await self._fetch_game_requirements(game_name)
|
|
|
| if not game_requirements:
|
| return [{'type': 'error', 'description': f'Game requirements not found for {game_name}'}]
|
|
|
|
|
| compatibility_analysis = await self._analyze_compatibility(
|
| game_name, hardware_specs, game_requirements
|
| )
|
|
|
|
|
| optimizations = []
|
| for rec in compatibility_analysis.recommendations:
|
| optimizations.append({
|
| 'type': 'settings',
|
| 'description': rec
|
| })
|
|
|
|
|
| if resolution == '4K':
|
| optimizations.append({
|
| 'type': 'resolution',
|
| 'description': 'Consider using DLSS Quality mode for better 4K performance'
|
| })
|
| elif resolution == '1440p':
|
| optimizations.append({
|
| 'type': 'resolution',
|
| 'description': 'DLSS Balanced mode recommended for optimal 1440p experience'
|
| })
|
|
|
|
|
| if hardware_specs.supports_rtx and game_requirements.supports_rtx:
|
| optimizations.append({
|
| 'type': 'rtx',
|
| 'description': 'Enable RTX ray tracing for enhanced visual quality'
|
| })
|
|
|
| return optimizations
|
|
|
| except Exception as e:
|
| self.logger.error(f"Failed to get optimization suggestions: {e}")
|
| return [{'type': 'error', 'description': str(e)}]
|
|
|
| async def analyze_game_compatibility(self, game_name: str, settings: str = "Medium", resolution: str = "System Default") -> Optional[Dict[str, Any]]:
|
| """Legacy method for backward compatibility with tests."""
|
| try:
|
| result = await self.check_game_compatibility(game_name)
|
|
|
| if not result:
|
| return None
|
|
|
|
|
| if isinstance(result, dict):
|
|
|
| return result
|
|
|
|
|
| llm_estimates = {}
|
| if self.llm_analyzer:
|
| try:
|
|
|
| llm_estimates = await self.llm_analyzer.estimate_compatibility_metrics(
|
| game_name,
|
| result.hardware_specs,
|
| result.compatibility_analysis,
|
| result.performance_prediction
|
| )
|
| except Exception as e:
|
| self.logger.warning(f"LLM estimation failed, using fallback: {e}")
|
|
|
|
|
| return {
|
| 'compatibility': {
|
| 'compatibility_level': result.compatibility_analysis.overall_compatibility,
|
| 'overall_score': result.compatibility_analysis.overall_score,
|
| 'bottlenecks': result.compatibility_analysis.bottlenecks,
|
| 'component_analysis': {
|
| 'cpu': {
|
| 'status': next(('Excellent' if comp.meets_recommended else 'Good' if comp.meets_minimum else 'Poor'
|
| for comp in result.compatibility_analysis.component_analyses
|
| if comp.component.name.lower() == 'cpu'), 'Unknown'),
|
| 'score': llm_estimates.get('cpu_score', next((int(comp.score * 100)
|
| for comp in result.compatibility_analysis.component_analyses
|
| if comp.component.name.lower() == 'cpu'), 75))
|
| },
|
| 'gpu': {
|
| 'status': next(('Excellent' if comp.meets_recommended else 'Good' if comp.meets_minimum else 'Poor'
|
| for comp in result.compatibility_analysis.component_analyses
|
| if comp.component.name.lower() == 'gpu'), 'Unknown'),
|
| 'score': llm_estimates.get('gpu_score', 80)
|
| },
|
| 'memory': {
|
| 'status': next(('Excellent' if comp.meets_recommended else 'Good' if comp.meets_minimum else 'Poor'
|
| for comp in result.compatibility_analysis.component_analyses
|
| if comp.component.name.lower() == 'ram'), 'Unknown'),
|
| 'score': llm_estimates.get('memory_score', 85)
|
| },
|
| 'storage': {
|
| 'status': next(('Excellent' if comp.meets_recommended else 'Good' if comp.meets_minimum else 'Poor'
|
| for comp in result.compatibility_analysis.component_analyses
|
| if comp.component.name.lower() == 'storage'), 'Unknown'),
|
| 'score': llm_estimates.get('storage_score', 90)
|
| }
|
| }
|
| },
|
| 'performance': {
|
| 'fps': result.performance_prediction.expected_fps if hasattr(result.performance_prediction, 'expected_fps') else 0,
|
| 'performance_level': result.performance_prediction.tier.value if hasattr(result.performance_prediction, 'tier') else 'Unknown',
|
| 'stability': llm_estimates.get('stability', 'stable'),
|
| 'optimization_suggestions': result.performance_prediction.upgrade_suggestions if hasattr(result.performance_prediction, 'upgrade_suggestions') else []
|
| },
|
| 'optimization_suggestions': result.performance_prediction.upgrade_suggestions if hasattr(result.performance_prediction, 'upgrade_suggestions') else [],
|
| 'hardware_analysis': {
|
| 'gpu_tier': llm_estimates.get('gpu_tier', 'high-end'),
|
| 'bottleneck_analysis': result.compatibility_analysis.bottlenecks
|
| }
|
| }
|
|
|
| except Exception as e:
|
| self.logger.error(f"Legacy compatibility analysis failed: {e}")
|
| return None
|
|
|
| def _parse_ram_value(self, ram_str: str) -> int:
|
| """Parse RAM value from string to integer GB with proper unit handling."""
|
| if not ram_str or ram_str == "Unknown":
|
| return 0
|
|
|
|
|
| ram_str = str(ram_str).upper()
|
|
|
|
|
| if 'MB' in ram_str:
|
|
|
| mb_match = re.search(r'(\d+\.?\d*)\s*MB', ram_str)
|
| if mb_match:
|
|
|
| mb_value = float(mb_match.group(1))
|
| if mb_value < 512:
|
| return 0.5
|
| else:
|
| return max(1, int(mb_value / 1024))
|
|
|
|
|
| gb_match = re.search(r'(\d+\.?\d*)\s*G?B?', ram_str)
|
| if gb_match:
|
| return int(float(gb_match.group(1)))
|
|
|
|
|
| number_match = re.search(r'(\d+\.?\d*)', ram_str)
|
| if number_match:
|
| return int(float(number_match.group(1)))
|
|
|
| return 0
|
|
|