feat: Introduce Debugging and Testing Guide with enhanced startup options

- Added a new document, E5-debugging-guide.md, providing comprehensive strategies for debugging and testing the `yaze` application, including logging practices and testing frameworks.
- Updated E2-development-guide.md to include a new section on debugging and testing, detailing quick debugging methods using command-line flags for specific editors and UI cards.
- Enhanced the main application to support command-line flags for opening specific editors and cards on startup, improving the developer experience.
- Refactored the Controller class to handle startup editor and card initialization based on command-line inputs.
This commit is contained in:
scawful
2025-10-09 17:19:36 -04:00
parent b3c6fd6e12
commit e769cea1d9
32 changed files with 1044 additions and 413 deletions

View File

@@ -162,60 +162,81 @@ void SettingsEditor::DrawThemeSettings() {
void SettingsEditor::DrawEditorBehavior() {
using namespace ImGui;
if (!user_settings_) {
Text("No user settings available");
return;
}
Text("%s Editor Behavior Settings", ICON_MD_TUNE);
Separator();
// Autosave settings
if (CollapsingHeader(ICON_MD_SAVE " Auto-Save", ImGuiTreeNodeFlags_DefaultOpen)) {
static bool autosave_enabled = true;
Checkbox("Enable Auto-Save", &autosave_enabled);
if (Checkbox("Enable Auto-Save", &user_settings_->prefs().autosave_enabled)) {
user_settings_->Save();
}
if (autosave_enabled) {
static int autosave_interval = 300;
SliderInt("Interval (seconds)", &autosave_interval, 60, 600);
if (user_settings_->prefs().autosave_enabled) {
int interval = static_cast<int>(user_settings_->prefs().autosave_interval);
if (SliderInt("Interval (seconds)", &interval, 60, 600)) {
user_settings_->prefs().autosave_interval = static_cast<float>(interval);
user_settings_->Save();
}
static bool backup_before_save = true;
Checkbox("Create Backup Before Save", &backup_before_save);
if (Checkbox("Create Backup Before Save", &user_settings_->prefs().backup_before_save)) {
user_settings_->Save();
}
}
}
// Recent files
if (CollapsingHeader(ICON_MD_HISTORY " Recent Files")) {
static int recent_files_limit = 10;
SliderInt("Recent Files Limit", &recent_files_limit, 5, 50);
if (SliderInt("Recent Files Limit", &user_settings_->prefs().recent_files_limit, 5, 50)) {
user_settings_->Save();
}
}
// Editor defaults
if (CollapsingHeader(ICON_MD_EDIT " Default Editor")) {
Text("Editor to open on ROM load:");
static int default_editor = 0;
const char* editors[] = { "None", "Overworld", "Dungeon", "Graphics" };
Combo("##DefaultEditor", &default_editor, editors, IM_ARRAYSIZE(editors));
if (Combo("##DefaultEditor", &user_settings_->prefs().default_editor, editors, IM_ARRAYSIZE(editors))) {
user_settings_->Save();
}
}
}
void SettingsEditor::DrawPerformanceSettings() {
using namespace ImGui;
if (!user_settings_) {
Text("No user settings available");
return;
}
Text("%s Performance Settings", ICON_MD_SPEED);
Separator();
// Graphics settings
if (CollapsingHeader(ICON_MD_IMAGE " Graphics", ImGuiTreeNodeFlags_DefaultOpen)) {
static bool vsync = true;
Checkbox("V-Sync", &vsync);
if (Checkbox("V-Sync", &user_settings_->prefs().vsync)) {
user_settings_->Save();
}
static int target_fps = 60;
SliderInt("Target FPS", &target_fps, 30, 144);
if (SliderInt("Target FPS", &user_settings_->prefs().target_fps, 30, 144)) {
user_settings_->Save();
}
}
// Memory settings
if (CollapsingHeader(ICON_MD_MEMORY " Memory")) {
static int cache_size = 512;
SliderInt("Cache Size (MB)", &cache_size, 128, 2048);
if (SliderInt("Cache Size (MB)", &user_settings_->prefs().cache_size_mb, 128, 2048)) {
user_settings_->Save();
}
static int undo_size = 50;
SliderInt("Undo History", &undo_size, 10, 200);
if (SliderInt("Undo History", &user_settings_->prefs().undo_history_size, 10, 200)) {
user_settings_->Save();
}
}
Separator();
@@ -226,46 +247,67 @@ void SettingsEditor::DrawPerformanceSettings() {
void SettingsEditor::DrawAIAgentSettings() {
using namespace ImGui;
if (!user_settings_) {
Text("No user settings available");
return;
}
Text("%s AI Agent Configuration", ICON_MD_SMART_TOY);
Separator();
// Provider selection
if (CollapsingHeader(ICON_MD_CLOUD " AI Provider", ImGuiTreeNodeFlags_DefaultOpen)) {
static int provider = 0;
const char* providers[] = { "Ollama (Local)", "Gemini (Cloud)", "Mock (Testing)" };
Combo("Provider", &provider, providers, IM_ARRAYSIZE(providers));
if (Combo("Provider", &user_settings_->prefs().ai_provider, providers, IM_ARRAYSIZE(providers))) {
user_settings_->Save();
}
Spacing();
if (provider == 0) { // Ollama
static char ollama_url[256] = "http://localhost:11434";
InputText("URL", ollama_url, IM_ARRAYSIZE(ollama_url));
} else if (provider == 1) { // Gemini
static char api_key[128] = "";
InputText("API Key", api_key, IM_ARRAYSIZE(api_key), ImGuiInputTextFlags_Password);
if (user_settings_->prefs().ai_provider == 0) { // Ollama
char url_buffer[256];
strncpy(url_buffer, user_settings_->prefs().ollama_url.c_str(), sizeof(url_buffer) - 1);
url_buffer[sizeof(url_buffer) - 1] = '\0';
if (InputText("URL", url_buffer, IM_ARRAYSIZE(url_buffer))) {
user_settings_->prefs().ollama_url = url_buffer;
user_settings_->Save();
}
} else if (user_settings_->prefs().ai_provider == 1) { // Gemini
char key_buffer[128];
strncpy(key_buffer, user_settings_->prefs().gemini_api_key.c_str(), sizeof(key_buffer) - 1);
key_buffer[sizeof(key_buffer) - 1] = '\0';
if (InputText("API Key", key_buffer, IM_ARRAYSIZE(key_buffer), ImGuiInputTextFlags_Password)) {
user_settings_->prefs().gemini_api_key = key_buffer;
user_settings_->Save();
}
}
}
// Model parameters
if (CollapsingHeader(ICON_MD_TUNE " Model Parameters")) {
static float temperature = 0.7f;
SliderFloat("Temperature", &temperature, 0.0f, 2.0f);
if (SliderFloat("Temperature", &user_settings_->prefs().ai_temperature, 0.0f, 2.0f)) {
user_settings_->Save();
}
TextDisabled("Higher = more creative");
static int max_tokens = 2048;
SliderInt("Max Tokens", &max_tokens, 256, 8192);
if (SliderInt("Max Tokens", &user_settings_->prefs().ai_max_tokens, 256, 8192)) {
user_settings_->Save();
}
}
// Agent behavior
if (CollapsingHeader(ICON_MD_PSYCHOLOGY " Behavior")) {
static bool proactive = true;
Checkbox("Proactive Suggestions", &proactive);
if (Checkbox("Proactive Suggestions", &user_settings_->prefs().ai_proactive)) {
user_settings_->Save();
}
static bool auto_learn = true;
Checkbox("Auto-Learn Preferences", &auto_learn);
if (Checkbox("Auto-Learn Preferences", &user_settings_->prefs().ai_auto_learn)) {
user_settings_->Save();
}
static bool multimodal = true;
Checkbox("Enable Vision/Multimodal", &multimodal);
if (Checkbox("Enable Vision/Multimodal", &user_settings_->prefs().ai_multimodal)) {
user_settings_->Save();
}
}
// z3ed CLI logging settings
@@ -273,21 +315,12 @@ void SettingsEditor::DrawAIAgentSettings() {
Text("Configure z3ed command-line logging behavior");
Spacing();
// Declare all static variables first
static int log_level = 1; // 0=Debug, 1=Info, 2=Warning, 3=Error, 4=Fatal
static bool log_to_file = false;
static char log_file_path[512] = "";
static bool log_ai_requests = true;
static bool log_rom_operations = true;
static bool log_gui_automation = true;
static bool log_proposals = true;
// Log level selection
const char* log_levels[] = { "Debug (Verbose)", "Info (Normal)", "Warning (Quiet)", "Error (Critical)", "Fatal Only" };
if (Combo("Log Level", &log_level, log_levels, IM_ARRAYSIZE(log_levels))) {
if (Combo("Log Level", &user_settings_->prefs().log_level, log_levels, IM_ARRAYSIZE(log_levels))) {
// Apply log level immediately using existing LogManager
util::LogLevel level;
switch (log_level) {
switch (user_settings_->prefs().log_level) {
case 0: level = util::LogLevel::YAZE_DEBUG; break;
case 1: level = util::LogLevel::INFO; break;
case 2: level = util::LogLevel::WARNING; break;
@@ -298,13 +331,14 @@ void SettingsEditor::DrawAIAgentSettings() {
// Get current categories
std::set<std::string> categories;
if (log_ai_requests) categories.insert("AI");
if (log_rom_operations) categories.insert("ROM");
if (log_gui_automation) categories.insert("GUI");
if (log_proposals) categories.insert("Proposals");
if (user_settings_->prefs().log_ai_requests) categories.insert("AI");
if (user_settings_->prefs().log_rom_operations) categories.insert("ROM");
if (user_settings_->prefs().log_gui_automation) categories.insert("GUI");
if (user_settings_->prefs().log_proposals) categories.insert("Proposals");
// Reconfigure with new level
util::LogManager::instance().configure(level, std::string(log_file_path), categories);
util::LogManager::instance().configure(level, user_settings_->prefs().log_file_path, categories);
user_settings_->Save();
Text("✓ Log level applied");
}
TextDisabled("Controls verbosity of YAZE and z3ed output");
@@ -312,36 +346,41 @@ void SettingsEditor::DrawAIAgentSettings() {
Spacing();
// Logging targets
if (Checkbox("Log to File", &log_to_file)) {
if (log_to_file) {
if (Checkbox("Log to File", &user_settings_->prefs().log_to_file)) {
if (user_settings_->prefs().log_to_file) {
// Set default path if empty
if (strlen(log_file_path) == 0) {
if (user_settings_->prefs().log_file_path.empty()) {
const char* home = std::getenv("HOME");
if (home) {
snprintf(log_file_path, sizeof(log_file_path), "%s/.yaze/logs/yaze.log", home);
user_settings_->prefs().log_file_path = std::string(home) + "/.yaze/logs/yaze.log";
}
}
// Enable file logging
std::set<std::string> categories;
util::LogLevel level = static_cast<util::LogLevel>(log_level);
util::LogManager::instance().configure(level, std::string(log_file_path), categories);
util::LogLevel level = static_cast<util::LogLevel>(user_settings_->prefs().log_level);
util::LogManager::instance().configure(level, user_settings_->prefs().log_file_path, categories);
} else {
// Disable file logging
std::set<std::string> categories;
util::LogLevel level = static_cast<util::LogLevel>(log_level);
util::LogLevel level = static_cast<util::LogLevel>(user_settings_->prefs().log_level);
util::LogManager::instance().configure(level, "", categories);
}
user_settings_->Save();
}
if (log_to_file) {
if (user_settings_->prefs().log_to_file) {
Indent();
if (InputText("Log File", log_file_path, IM_ARRAYSIZE(log_file_path))) {
char path_buffer[512];
strncpy(path_buffer, user_settings_->prefs().log_file_path.c_str(), sizeof(path_buffer) - 1);
path_buffer[sizeof(path_buffer) - 1] = '\0';
if (InputText("Log File", path_buffer, IM_ARRAYSIZE(path_buffer))) {
// Update log file path
user_settings_->prefs().log_file_path = path_buffer;
std::set<std::string> categories;
util::LogLevel level = static_cast<util::LogLevel>(log_level);
util::LogManager::instance().configure(level, std::string(log_file_path), categories);
util::LogLevel level = static_cast<util::LogLevel>(user_settings_->prefs().log_level);
util::LogManager::instance().configure(level, user_settings_->prefs().log_file_path, categories);
user_settings_->Save();
}
TextDisabled("Log file path (supports ~ for home directory)");
@@ -358,40 +397,43 @@ void SettingsEditor::DrawAIAgentSettings() {
bool categories_changed = false;
categories_changed |= Checkbox("AI API Requests", &log_ai_requests);
categories_changed |= Checkbox("ROM Operations", &log_rom_operations);
categories_changed |= Checkbox("GUI Automation", &log_gui_automation);
categories_changed |= Checkbox("Proposal Generation", &log_proposals);
categories_changed |= Checkbox("AI API Requests", &user_settings_->prefs().log_ai_requests);
categories_changed |= Checkbox("ROM Operations", &user_settings_->prefs().log_rom_operations);
categories_changed |= Checkbox("GUI Automation", &user_settings_->prefs().log_gui_automation);
categories_changed |= Checkbox("Proposal Generation", &user_settings_->prefs().log_proposals);
if (categories_changed) {
// Rebuild category set
std::set<std::string> categories;
if (log_ai_requests) categories.insert("AI");
if (log_rom_operations) categories.insert("ROM");
if (log_gui_automation) categories.insert("GUI");
if (log_proposals) categories.insert("Proposals");
if (user_settings_->prefs().log_ai_requests) categories.insert("AI");
if (user_settings_->prefs().log_rom_operations) categories.insert("ROM");
if (user_settings_->prefs().log_gui_automation) categories.insert("GUI");
if (user_settings_->prefs().log_proposals) categories.insert("Proposals");
// Reconfigure LogManager
util::LogLevel level = static_cast<util::LogLevel>(log_level);
util::LogManager::instance().configure(level, log_to_file ? std::string(log_file_path) : "", categories);
util::LogLevel level = static_cast<util::LogLevel>(user_settings_->prefs().log_level);
util::LogManager::instance().configure(level,
user_settings_->prefs().log_to_file ? user_settings_->prefs().log_file_path : "",
categories);
user_settings_->Save();
}
Spacing();
// Quick actions
if (Button(ICON_MD_DELETE " Clear Logs")) {
if (log_to_file && strlen(log_file_path) > 0) {
std::filesystem::path path(log_file_path);
if (user_settings_->prefs().log_to_file && !user_settings_->prefs().log_file_path.empty()) {
std::filesystem::path path(user_settings_->prefs().log_file_path);
if (std::filesystem::exists(path)) {
std::filesystem::remove(path);
LOG_DEBUG("Settings", "Log file cleared: %s", log_file_path);
LOG_DEBUG("Settings", "Log file cleared: %s", user_settings_->prefs().log_file_path.c_str());
}
}
}
SameLine();
if (Button(ICON_MD_FOLDER_OPEN " Open Log Directory")) {
if (log_to_file && strlen(log_file_path) > 0) {
std::filesystem::path path(log_file_path);
if (user_settings_->prefs().log_to_file && !user_settings_->prefs().log_file_path.empty()) {
std::filesystem::path path(user_settings_->prefs().log_file_path);
std::filesystem::path dir = path.parent_path();
// Platform-specific command to open directory

View File

@@ -4,6 +4,7 @@
#include "absl/status/status.h"
#include "app/editor/editor.h"
#include "app/rom.h"
#include "editor/system/user_settings.h"
#include "imgui/imgui.h"
namespace yaze {
@@ -207,7 +208,8 @@ static void ShowExampleAppPropertyEditor(bool* p_open) {
class SettingsEditor : public Editor {
public:
explicit SettingsEditor(Rom* rom = nullptr) : rom_(rom) {
explicit SettingsEditor(Rom* rom = nullptr, UserSettings* user_settings = nullptr)
: rom_(rom), user_settings_(user_settings) {
type_ = EditorType::kSettings;
}
@@ -215,6 +217,8 @@ class SettingsEditor : public Editor {
absl::Status Load() override;
absl::Status Save() override { return absl::UnimplementedError("Save"); }
absl::Status Update() override;
void set_user_settings(UserSettings* settings) { user_settings_ = settings; }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
@@ -232,6 +236,7 @@ class SettingsEditor : public Editor {
private:
Rom* rom_;
UserSettings* user_settings_;
void DrawGeneralSettings();
void DrawKeyboardShortcuts();
void DrawThemeSettings();

View File

@@ -1,24 +1,177 @@
#include "app/editor/system/user_settings.h"
#include <fstream>
#include <sstream>
#include "absl/strings/str_format.h"
#include "app/gui/style.h"
#include "imgui/imgui.h"
#include "util/file_util.h"
#include "util/log.h"
#include "util/platform_paths.h"
namespace yaze {
namespace editor {
UserSettings::UserSettings() {
// TODO: Get platform-specific settings path
settings_file_path_ = "yaze_settings.json";
auto config_dir_status = util::PlatformPaths::GetConfigDirectory();
if (config_dir_status.ok()) {
settings_file_path_ = (*config_dir_status / "yaze_settings.ini").string();
} else {
LOG_WARN("UserSettings", "Could not determine config directory. Using local.");
settings_file_path_ = "yaze_settings.ini";
}
}
absl::Status UserSettings::Load() {
// TODO: Load from JSON file
try {
auto data = util::LoadFile(settings_file_path_);
if (data.empty()) {
return absl::OkStatus(); // No settings file yet, use defaults.
}
std::istringstream ss(data);
std::string line;
while (std::getline(ss, line)) {
size_t eq_pos = line.find('=');
if (eq_pos == std::string::npos) continue;
std::string key = line.substr(0, eq_pos);
std::string val = line.substr(eq_pos + 1);
// General
if (key == "font_global_scale") {
prefs_.font_global_scale = std::stof(val);
} else if (key == "backup_rom") {
prefs_.backup_rom = (val == "1");
} else if (key == "save_new_auto") {
prefs_.save_new_auto = (val == "1");
} else if (key == "autosave_enabled") {
prefs_.autosave_enabled = (val == "1");
} else if (key == "autosave_interval") {
prefs_.autosave_interval = std::stof(val);
} else if (key == "recent_files_limit") {
prefs_.recent_files_limit = std::stoi(val);
} else if (key == "last_rom_path") {
prefs_.last_rom_path = val;
} else if (key == "last_project_path") {
prefs_.last_project_path = val;
} else if (key == "show_welcome_on_startup") {
prefs_.show_welcome_on_startup = (val == "1");
} else if (key == "restore_last_session") {
prefs_.restore_last_session = (val == "1");
}
// Editor Behavior
else if (key == "backup_before_save") {
prefs_.backup_before_save = (val == "1");
} else if (key == "default_editor") {
prefs_.default_editor = std::stoi(val);
}
// Performance
else if (key == "vsync") {
prefs_.vsync = (val == "1");
} else if (key == "target_fps") {
prefs_.target_fps = std::stoi(val);
} else if (key == "cache_size_mb") {
prefs_.cache_size_mb = std::stoi(val);
} else if (key == "undo_history_size") {
prefs_.undo_history_size = std::stoi(val);
}
// AI Agent
else if (key == "ai_provider") {
prefs_.ai_provider = std::stoi(val);
} else if (key == "ollama_url") {
prefs_.ollama_url = val;
} else if (key == "gemini_api_key") {
prefs_.gemini_api_key = val;
} else if (key == "ai_temperature") {
prefs_.ai_temperature = std::stof(val);
} else if (key == "ai_max_tokens") {
prefs_.ai_max_tokens = std::stoi(val);
} else if (key == "ai_proactive") {
prefs_.ai_proactive = (val == "1");
} else if (key == "ai_auto_learn") {
prefs_.ai_auto_learn = (val == "1");
} else if (key == "ai_multimodal") {
prefs_.ai_multimodal = (val == "1");
}
// CLI Logging
else if (key == "log_level") {
prefs_.log_level = std::stoi(val);
} else if (key == "log_to_file") {
prefs_.log_to_file = (val == "1");
} else if (key == "log_file_path") {
prefs_.log_file_path = val;
} else if (key == "log_ai_requests") {
prefs_.log_ai_requests = (val == "1");
} else if (key == "log_rom_operations") {
prefs_.log_rom_operations = (val == "1");
} else if (key == "log_gui_automation") {
prefs_.log_gui_automation = (val == "1");
} else if (key == "log_proposals") {
prefs_.log_proposals = (val == "1");
}
}
ImGui::GetIO().FontGlobalScale = prefs_.font_global_scale;
} catch (const std::exception& e) {
return absl::InternalError(
absl::StrFormat("Failed to load user settings: %s", e.what()));
}
return absl::OkStatus();
}
absl::Status UserSettings::Save() {
// TODO: Save to JSON file
try {
std::ostringstream ss;
// General
ss << "font_global_scale=" << prefs_.font_global_scale << "\n";
ss << "backup_rom=" << (prefs_.backup_rom ? 1 : 0) << "\n";
ss << "save_new_auto=" << (prefs_.save_new_auto ? 1 : 0) << "\n";
ss << "autosave_enabled=" << (prefs_.autosave_enabled ? 1 : 0) << "\n";
ss << "autosave_interval=" << prefs_.autosave_interval << "\n";
ss << "recent_files_limit=" << prefs_.recent_files_limit << "\n";
ss << "last_rom_path=" << prefs_.last_rom_path << "\n";
ss << "last_project_path=" << prefs_.last_project_path << "\n";
ss << "show_welcome_on_startup=" << (prefs_.show_welcome_on_startup ? 1 : 0) << "\n";
ss << "restore_last_session=" << (prefs_.restore_last_session ? 1 : 0) << "\n";
// Editor Behavior
ss << "backup_before_save=" << (prefs_.backup_before_save ? 1 : 0) << "\n";
ss << "default_editor=" << prefs_.default_editor << "\n";
// Performance
ss << "vsync=" << (prefs_.vsync ? 1 : 0) << "\n";
ss << "target_fps=" << prefs_.target_fps << "\n";
ss << "cache_size_mb=" << prefs_.cache_size_mb << "\n";
ss << "undo_history_size=" << prefs_.undo_history_size << "\n";
// AI Agent
ss << "ai_provider=" << prefs_.ai_provider << "\n";
ss << "ollama_url=" << prefs_.ollama_url << "\n";
ss << "gemini_api_key=" << prefs_.gemini_api_key << "\n";
ss << "ai_temperature=" << prefs_.ai_temperature << "\n";
ss << "ai_max_tokens=" << prefs_.ai_max_tokens << "\n";
ss << "ai_proactive=" << (prefs_.ai_proactive ? 1 : 0) << "\n";
ss << "ai_auto_learn=" << (prefs_.ai_auto_learn ? 1 : 0) << "\n";
ss << "ai_multimodal=" << (prefs_.ai_multimodal ? 1 : 0) << "\n";
// CLI Logging
ss << "log_level=" << prefs_.log_level << "\n";
ss << "log_to_file=" << (prefs_.log_to_file ? 1 : 0) << "\n";
ss << "log_file_path=" << prefs_.log_file_path << "\n";
ss << "log_ai_requests=" << (prefs_.log_ai_requests ? 1 : 0) << "\n";
ss << "log_rom_operations=" << (prefs_.log_rom_operations ? 1 : 0) << "\n";
ss << "log_gui_automation=" << (prefs_.log_gui_automation ? 1 : 0) << "\n";
ss << "log_proposals=" << (prefs_.log_proposals ? 1 : 0) << "\n";
util::SaveFile(settings_file_path_, ss.str());
} catch (const std::exception& e) {
return absl::InternalError(
absl::StrFormat("Failed to save user settings: %s", e.what()));
}
return absl::OkStatus();
}
} // namespace editor
} // namespace yaze

View File

@@ -13,6 +13,7 @@ namespace editor {
class UserSettings {
public:
struct Preferences {
// General
float font_global_scale = 1.0f;
bool backup_rom = false;
bool save_new_auto = true;
@@ -23,6 +24,35 @@ class UserSettings {
std::string last_project_path;
bool show_welcome_on_startup = true;
bool restore_last_session = true;
// Editor Behavior
bool backup_before_save = true;
int default_editor = 0; // 0=None, 1=Overworld, 2=Dungeon, 3=Graphics
// Performance
bool vsync = true;
int target_fps = 60;
int cache_size_mb = 512;
int undo_history_size = 50;
// AI Agent
int ai_provider = 0; // 0=Ollama, 1=Gemini, 2=Mock
std::string ollama_url = "http://localhost:11434";
std::string gemini_api_key;
float ai_temperature = 0.7f;
int ai_max_tokens = 2048;
bool ai_proactive = true;
bool ai_auto_learn = true;
bool ai_multimodal = true;
// CLI Logging
int log_level = 1; // 0=Debug, 1=Info, 2=Warning, 3=Error, 4=Fatal
bool log_to_file = false;
std::string log_file_path;
bool log_ai_requests = true;
bool log_rom_operations = true;
bool log_gui_automation = true;
bool log_proposals = true;
};
UserSettings();