extends Node ## Hardware detection autoload for AI-Enhanced Dialogue (D-138, #646). ## ## Exposes classification methods for Layer 1 (RAM), Layer 2 (TPT benchmark), ## and Layer 3 (ongoing TPT degradation monitoring). ## ## Loaded as HardwareDetector autoload. Tests access via get_node("/root/HardwareDetector"). ## Settings dialog reads classification strings to configure the toggle UI. const RAM_PASS_THRESHOLD_MB := 2000.0 const RAM_MARGINAL_THRESHOLD_MB := 1600.0 const TPT_GREEN_THRESHOLD := 6.0 # tokens/sec — enable silently const TPT_YELLOW_THRESHOLD := 3.0 # tokens/sec — partial pre-voicing note const TPT_DEGRADATION_THRESHOLD := 0.4 # fraction — >40% sustained drop → yellow const BENCHMARK_CACHE_PATH := "user://ai-dialogue-config.json" func _ready() -> void: load_ai_pref() ## Layer 1 — RAM classification. ## Accepts free MB as input; returns "pass" | "marginal" | "fail". func classify_ram(free_mb: float) -> String: if free_mb >= RAM_PASS_THRESHOLD_MB: return "pass" elif free_mb >= RAM_MARGINAL_THRESHOLD_MB: return "marginal" else: return "fail" ## Layer 2 — TPT benchmark classification. ## Accepts tokens/sec as input; returns "green" | "yellow" | "red". func classify_tpt(tps: float) -> String: if tps >= TPT_GREEN_THRESHOLD: return "green" elif tps >= TPT_YELLOW_THRESHOLD: return "yellow" else: return "red" ## Layer 3 — Ongoing degradation classification. ## D-138 §8: >40% sustained drop from baseline → yellow (not forced disable). ## Returns "ok" | "yellow". func classify_degradation(baseline_tps: float, current_avg_tps: float) -> String: if baseline_tps <= 0.0: return "ok" var drop := (baseline_tps - current_avg_tps) / baseline_tps return "yellow" if drop > TPT_DEGRADATION_THRESHOLD else "ok" ## Query the OS for free RAM and return the classification plus the raw MB value. ## Returns: {classification: String, free_mb: float} func check_ram() -> Dictionary: var mem := OS.get_memory_info() # "available" accounts for reclaimable pages (Linux MemAvailable / Windows ullAvailPhys). # Fall back to "free" on platforms that don't provide "available". var free_bytes: int = mem.get("available", mem.get("free", 0)) var free_mb: float = float(free_bytes) / (1024.0 * 1024.0) return {"classification": classify_ram(free_mb), "free_mb": free_mb} ## Read the cached TPT benchmark result written by the server on first model load. ## Returns: {tps: float, classification: String} or null if no cache file exists. func read_benchmark_cache() -> Variant: if not FileAccess.file_exists(BENCHMARK_CACHE_PATH): return null var f := FileAccess.open(BENCHMARK_CACHE_PATH, FileAccess.READ) if f == null: return null var json := JSON.new() if json.parse(f.get_as_text()) != OK: return null var data: Variant = json.get_data() if not data is Dictionary or not data.has("tps"): return null var tps: float = float(data["tps"]) return {"tps": tps, "classification": classify_tpt(tps)} ## Load AI dialogue preference from settings.cfg and apply to GameState. ## Called from _ready() to restore the toggle state across session restarts. ## Spec: D-138 §8 — toggle persists via ConfigFile (client-local) + server SQLite (#627). func load_ai_pref() -> void: var cfg := ConfigFile.new() if cfg.load("user://settings.cfg") != OK: return var enabled: Variant = cfg.get_value("ai_dialogue", "enabled", null) if enabled is bool: GameState.ai_enhanced_dialogue_enabled = enabled ## Compose a human-readable status message for the settings dialog. ## classification: "pass" | "marginal" | "fail" (RAM) or "green" | "yellow" | "red" (TPT). ## Returns empty string when no message is needed. func status_message(ram_classification: String, tpt_classification: String, free_mb: float, tps: float) -> String: if ram_classification == "fail": return "AI-Enhanced Dialogue requires 2 GB of free memory. Your system currently has %.0f MB available. Close other applications and try again, or leave the setting off — the game is complete either way." % free_mb if ram_classification == "marginal": return "Only %.0f MB free — performance may vary. You can still enable it." % free_mb match tpt_classification: "yellow": return "Running at %.0f t/s — pre-voicing will work for main characters and key scenes. Background NPCs may show base text until the queue catches up." % tps "red": return "Running very slowly at %.0f t/s — we recommend leaving this off, but the choice is yours." % tps return ""