W1/A2: add SimBridge.CONNECTED guard before send_input in toggle handler
W2/A1: assert PlatformInfo != null in HardwareDetector._ready();
add ordering comment in project.godot [autoload] section
W3: replace FileAccess.open() with get_file_as_string() in
read_benchmark_cache() — auto-closes, no leak
W4/A3: add _extract_bool_setting() helper in game_state.gd that handles
both {"Bool": true} and plain bool; push_warning on type mismatch
S5: sync _pre_battery_pref from GameState after load_ai_pref() in
HardwareDetector._ready() — closes race if system starts on battery
S6: separate "red" TPT status into its own arm with STATUS_RED dot and
label colour — three-colour mapping: green/yellow/red now distinct
A4: replace DebugConsole.PREFS_PATH compile-time dependency in
_save_ai_pref() with local SETTINGS_CFG_PATH constant
S7 (battery CI guard) already applied by Hoshe in test file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Platform abstraction:
- New PlatformInfo autoload (extends Node, registered first in project.godot).
Owns all OS queries: power state monitoring (30s poll), memory (on demand),
file path resolution (user_data_dir, config_dir, benchmark_cache_path,
install_dir, executable_path, cache_dir, model_dir).
- PowerProfile enum (FULL/BATTERY/POWER_SAVER), power_profile_changed signal.
Uses OS.callv("get_power_info") to defer resolution to runtime — avoids
compile errors on Godot 4.6 headless builds without power API.
HardwareDetector refactor:
- check_ram() now delegates to PlatformInfo.refresh_memory() + free_memory_mb.
All direct OS.get_memory_info() calls removed.
- classify_power_state(int) and should_suspend_inference(int) delegate to
PlatformInfo for single source of truth.
- check_power_state() → {power_state, classification, should_suspend}.
- Battery suspend/resume: on PowerProfile.BATTERY, stores pre-battery pref and
sends ChangeSettings(false) to server. On FULL restore, sends ChangeSettings
with saved pref if it was enabled. inference_suspended member tracks state.
Settings dialog:
- is_ai_inference_suspended() / set_ai_inference_suspended(bool) — testable API
per Hoshe's test contract. Reads HardwareDetector.inference_suspended on open.
- Toggle now disabled when battery-suspended OR hardware fails (not only fail).
- get_ai_dialogue_label_text() wired to UIStrings ("settings.ai_dialogue_toggle")
instead of hardcoded string.
Cleanup:
- Deleted ai_dialogue_detector.gd and .uid (orphaned duplicate, dead code).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the full AI-Enhanced Dialogue feature for Sprint 26:
- HardwareDetector autoload (extends Node): three-layer detection —
Layer 1 RAM classification (pass/marginal/fail, thresholds 2GB/1.6GB),
Layer 2 TPT benchmark cache (green/yellow/red, thresholds 6/3 t/s),
Layer 3 degradation monitoring (>40% drop → yellow). load_ai_pref()
restores toggle state from user://settings.cfg on startup.
- Settings dialog AI section: toggle, colored status dot, status message
label, testable API (get_ai_dialogue_label_text, set_ai_dialogue_
hardware_status, is_ai_dialogue_toggle_enabled). Only RAM "fail" greys
out toggle — player always overrides yellow/red recommendations (D-138).
- GameState.ai_enhanced_dialogue_enabled (default true, opt-out model).
GameState.settings_response (v20 one-shot settings dump from server).
apply_snapshot() hydrates ai_enhanced_dialogue_enabled from full dump.
- Protocol v19→v20, settings_response decoding in decode_snapshot().
Protocol converted to extends Node autoload (enables test has_method).
encode_change_settings() helper for test inspection.
- InputMapper: CHANGE_SETTINGS, REQUEST_ALL_SETTINGS, DELETE_SETTING.
SimBridge: wire mappings for all three. RequestAllSettings queued after
handshake to hydrate client state from server SQLite on connect.
settings_response carry-forward in receive_bytes().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>