feat(ui): PlatformInfo abstraction + battery detection + fixes (#646 D-138)

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>
This commit is contained in:
2026-03-13 10:41:07 +01:00
co-authored by Claude Sonnet 4.6
parent 01b0583265
commit d7a4e3268a
5 changed files with 298 additions and 30 deletions
+25 -4
View File
@@ -36,6 +36,7 @@ var _container: VBoxContainer = null
# #646: AI Dialogue hardware status and toggle node ref — used by testable API methods
var _ai_hw_status: String = "" # "pass" | "marginal" | "fail" | "" (not yet checked)
var _ai_check_node: CheckButton = null
var _ai_inference_suspended: bool = false # D-138 §8 Layer 3 battery auto-suspend state
signal closed
signal debug_console_toggled(enabled: bool) # #581: debug console enabled/disabled
@@ -179,6 +180,8 @@ func _build_ui() -> void:
# RAM passes — status follows TPT if benchmarked, else "pass"
hw_status = "pass" if tpt_cache == null else tpt_class
set_ai_dialogue_hardware_status(hw_status)
# D-138 §8 Layer 3: reflect current battery-suspension state from HardwareDetector.
_ai_inference_suspended = HardwareDetector.inference_suspended
var ai_hbox := HBoxContainer.new()
ai_hbox.custom_minimum_size = Vector2(0, ROW_HEIGHT)
@@ -211,7 +214,7 @@ func _build_ui() -> void:
_ai_check_node = CheckButton.new()
_ai_check_node.button_pressed = GameState.ai_enhanced_dialogue_enabled
_ai_check_node.disabled = (_ai_hw_status == "fail")
_ai_check_node.disabled = (_ai_hw_status == "fail") or _ai_inference_suspended
ai_hbox.add_child(_ai_check_node)
# Status message label — only shown when non-empty
@@ -274,8 +277,9 @@ func _destroy_ui() -> void:
# -- #646: AI Dialogue testable API -------------------------------------------
## Returns the canonical label text for the AI-Enhanced Dialogue toggle (D-138).
## Reads from UIStrings yaml (settings.ai_dialogue_toggle) for localization support.
func get_ai_dialogue_label_text() -> String:
return "AI-Enhanced Dialogue"
return UIStrings.get_text("settings.ai_dialogue_toggle")
## Set the hardware detection status — drives toggle enabled/disabled state.
@@ -289,9 +293,26 @@ func set_ai_dialogue_hardware_status(status: String) -> void:
## Returns true if the AI-Enhanced Dialogue toggle is currently enabled (not greyed out).
## Only "fail" (RAM below minimum) produces a disabled toggle.
## Disabled by RAM "fail" OR by active battery suspension (D-138 §8 Layer 3).
func is_ai_dialogue_toggle_enabled() -> bool:
return _ai_hw_status != "fail"
return _ai_hw_status != "fail" and not _ai_inference_suspended
## Returns true if AI inference is currently auto-suspended due to battery power (D-138 §8 Layer 3).
## Set by set_ai_inference_suspended() when HardwareDetector detects battery profile.
func is_ai_inference_suspended() -> bool:
return _ai_inference_suspended
## Update battery suspension display state.
## Called when PlatformInfo.power_profile_changed fires (via main.gd or HardwareDetector).
## Tests can inject suspended=true to verify UI reacts correctly.
func set_ai_inference_suspended(suspended: bool) -> void:
_ai_inference_suspended = suspended
# If the dialog is currently open and built, update the status label to reflect suspension.
# The next open() call will rebuild from scratch with the correct state anyway.
if _ai_check_node != null:
_ai_check_node.disabled = suspended or (_ai_hw_status == "fail")
## Persist the AI Dialogue enabled state to the local prefs file.