diff --git a/docs/audit-game.md b/docs/audit-game.md index b2fb3fa..ede4c10 100644 --- a/docs/audit-game.md +++ b/docs/audit-game.md @@ -20,7 +20,7 @@ Els IDs es mantenen estables una vegada assignats. Cada entrada apunta al fitxer |----|-------|-------| | HC-03 | Geometria del mapa cablejada a `map.hpp` (bloqueja "pirámide a pirámide") | DONE | | COUP-01 | `core/` inclou `game/ui/notification_presets.hpp` (dependència invertida) | DONE | -| DUP-01 | PostFX i CrtPi són dos pipelines duplicats a `options.cpp` | PENDING | +| DUP-01 | PostFX i CrtPi són dos pipelines duplicats a `options.cpp` | DONE | | PATCH-04 | Reescriure silenciosament `config.yaml` al fallar parseig o versió | PENDING | | HC-01 | `SPEED_PX_PER_S`, caixa de col·lisió i rutes d'assets cablejades | PENDING | @@ -30,7 +30,7 @@ Els IDs es mantenen estables una vegada assignats. Cada entrada apunta al fitxer | ID | Fitxer:línia | Troballa | Severitat | Estat | Notes | |----|--------------|----------|-----------|-------|-------| -| DUP-01 | `options.cpp:152-335,443-547` | PostFX i CrtPi tenen `default*Presets/parse*Preset/write*Preset/load*FromFile/save*ToFile/set*File` duplicats | Importante | PENDING | Template `PresetStore` amb serialitzadors per camp | +| DUP-01 | `options.cpp:152-335,443-547` | PostFX i CrtPi tenen `default*Presets/parse*Preset/write*Preset/load*FromFile/save*ToFile/set*File` duplicats | Importante | DONE | Afegits `loadPresetsFromFile` i `savePresetsToFile` a l'anonymous namespace; els 4 entry-points passen a ser one-liners que injecten parse/write/defaults per tipus. Els fitxers `postfx.yaml`/`crtpi.yaml` regenerats són idèntics a l'anterior. `std::cerr` manté el comportament — INCONS-02 el corregirà | | DUP-02 | `player.cpp:100-140` | `snapToHorizontalLane` i `snapToVerticalLane` són el mateix algorisme amb eixos permutats | Importante | PENDING | Unificar en `snapToLane(Axis, delta_time)` | | DUP-03 | `options.cpp:14-62` | Tres parelles `enumToString`/`enumFromString` amb la mateixa forma | Menor | PENDING | Taula `constexpr` + helpers genèrics | | DUP-04 | `options.cpp:79-149` | Cada `parse*` repeteix `if (node.contains) { ... get_value }` | Menor | PENDING | Helper `readField(node, key, dst)` | diff --git a/source/game/options.cpp b/source/game/options.cpp index cea9241..db5d4ea 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -334,6 +334,73 @@ namespace { out << " enable_sharper: " << (preset.enable_sharper ? "true" : "false") << "\n"; } + // Pipeline genèric de càrrega de presets: si el fitxer no existeix o és + // invàlid (no deserialitza, manca `presets:`, no és seqüència, queda + // buit), es restauren els defaults via `defaults_fn` i es reescriu via + // `save_fn`. Unifica PostFX i CrtPi — abans eren dues còpies del mateix + // codi que només diferien en el tipus concret de preset. + template + auto loadPresetsFromFile( + const std::string& path, + std::vector& presets, + std::vector (*defaults_fn)(), + void (*parse_fn)(const fkyaml::node&, T&), + bool (*save_fn)(), + const char* kind_label) -> bool { + if (path.empty()) { return false; } + + const std::string CONTENT = readFileOrEmpty(path); + if (CONTENT.empty()) { + presets = defaults_fn(); + return save_fn(); + } + try { + auto yaml = fkyaml::node::deserialize(CONTENT); + if (!yaml.contains("presets") || !yaml["presets"].is_sequence()) { + presets = defaults_fn(); + return save_fn(); + } + presets.clear(); + for (const auto& preset_node : yaml["presets"]) { + T preset; + parse_fn(preset_node, preset); + presets.push_back(preset); + } + if (presets.empty()) { + presets = defaults_fn(); + return save_fn(); + } + return true; + } catch (const std::exception& e) { + std::cerr << "Options: error parsejant " << kind_label << ": " << e.what() << '\n'; + std::cerr << "Options: reescrivint el fitxer amb presets per defecte.\n"; + presets = defaults_fn(); + return save_fn(); + } + } + + // Pipeline genèric d'escriptura: capçalera de comentari + `presets:` + + // una entrada per preset via `write_fn`. `config_key_note` és la segona + // línia de comentari que recorda amb quin camp de config.yaml han de + // coincidir els noms. + template + auto savePresetsToFile( + const std::string& path, + const std::vector& presets, + void (*write_fn)(std::ostringstream&, const T&), + const char* header_comment, + const char* config_key_note) -> bool { + if (path.empty()) { return false; } + std::ostringstream out; + out << "# " << header_comment << "\n"; + out << "# " << config_key_note << "\n"; + out << "presets:\n"; + for (const auto& preset : presets) { + write_fn(out, preset); + } + return writeFile(path, out.str()); + } + } // namespace namespace Options { @@ -445,52 +512,22 @@ namespace Options { } auto loadPostFXFromFile() -> bool { - if (postfx_file_path.empty()) { return false; } - - const std::string CONTENT = readFileOrEmpty(postfx_file_path); - if (CONTENT.empty()) { - postfx_presets = defaultPostFXPresets(); - return savePostFXToFile(); - } - - try { - auto yaml = fkyaml::node::deserialize(CONTENT); - if (!yaml.contains("presets") || !yaml["presets"].is_sequence()) { - postfx_presets = defaultPostFXPresets(); - return savePostFXToFile(); - } - - postfx_presets.clear(); - for (const auto& preset_node : yaml["presets"]) { - PostFXPreset preset; - parsePostFXPreset(preset_node, preset); - postfx_presets.push_back(preset); - } - - if (postfx_presets.empty()) { - postfx_presets = defaultPostFXPresets(); - return savePostFXToFile(); - } - return true; - } catch (const std::exception& e) { - std::cerr << "Options: error parsejant postfx.yaml: " << e.what() << '\n'; - std::cerr << "Options: reescrivint el fitxer amb presets per defecte.\n"; - postfx_presets = defaultPostFXPresets(); - return savePostFXToFile(); - } + return loadPresetsFromFile( + postfx_file_path, + postfx_presets, + &defaultPostFXPresets, + &parsePostFXPreset, + &savePostFXToFile, + "postfx.yaml"); } auto savePostFXToFile() -> bool { - if (postfx_file_path.empty()) { return false; } - - std::ostringstream out; - out << "# Presets PostFX per a AEE 2026. Editable a mà.\n"; - out << "# Els noms han de coincidir amb config.yaml > video.shader.current_postfx_preset.\n"; - out << "presets:\n"; - for (const auto& preset : postfx_presets) { - writePostFXPreset(out, preset); - } - return writeFile(postfx_file_path, out.str()); + return savePresetsToFile( + postfx_file_path, + postfx_presets, + &writePostFXPreset, + "Presets PostFX per a AEE 2026. Editable a mà.", + "Els noms han de coincidir amb config.yaml > video.shader.current_postfx_preset."); } void setCrtPiFile(const std::string& path) { @@ -498,52 +535,22 @@ namespace Options { } auto loadCrtPiFromFile() -> bool { - if (crtpi_file_path.empty()) { return false; } - - const std::string CONTENT = readFileOrEmpty(crtpi_file_path); - if (CONTENT.empty()) { - crtpi_presets = defaultCrtPiPresets(); - return saveCrtPiToFile(); - } - - try { - auto yaml = fkyaml::node::deserialize(CONTENT); - if (!yaml.contains("presets") || !yaml["presets"].is_sequence()) { - crtpi_presets = defaultCrtPiPresets(); - return saveCrtPiToFile(); - } - - crtpi_presets.clear(); - for (const auto& preset_node : yaml["presets"]) { - CrtPiPreset preset; - parseCrtPiPreset(preset_node, preset); - crtpi_presets.push_back(preset); - } - - if (crtpi_presets.empty()) { - crtpi_presets = defaultCrtPiPresets(); - return saveCrtPiToFile(); - } - return true; - } catch (const std::exception& e) { - std::cerr << "Options: error parsejant crtpi.yaml: " << e.what() << '\n'; - std::cerr << "Options: reescrivint el fitxer amb presets per defecte.\n"; - crtpi_presets = defaultCrtPiPresets(); - return saveCrtPiToFile(); - } + return loadPresetsFromFile( + crtpi_file_path, + crtpi_presets, + &defaultCrtPiPresets, + &parseCrtPiPreset, + &saveCrtPiToFile, + "crtpi.yaml"); } auto saveCrtPiToFile() -> bool { - if (crtpi_file_path.empty()) { return false; } - - std::ostringstream out; - out << "# Presets CRT-Pi per a AEE 2026. Editable a mà.\n"; - out << "# Els noms han de coincidir amb config.yaml > video.shader.current_crtpi_preset.\n"; - out << "presets:\n"; - for (const auto& preset : crtpi_presets) { - writeCrtPiPreset(out, preset); - } - return writeFile(crtpi_file_path, out.str()); + return savePresetsToFile( + crtpi_file_path, + crtpi_presets, + &writeCrtPiPreset, + "Presets CRT-Pi per a AEE 2026. Editable a mà.", + "Els noms han de coincidir amb config.yaml > video.shader.current_crtpi_preset."); } auto buildActiveShader() -> Rendering::ShaderType {