diff --git a/docs/audit-game.md b/docs/audit-game.md index ede4c10..b1fe2e0 100644 --- a/docs/audit-game.md +++ b/docs/audit-game.md @@ -21,7 +21,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` | DONE | -| PATCH-04 | Reescriure silenciosament `config.yaml` al fallar parseig o versió | PENDING | +| PATCH-04 | Reescriure silenciosament `config.yaml` al fallar parseig o versió | DONE | | HC-01 | `SPEED_PX_PER_S`, caixa de col·lisió i rutes d'assets cablejades | PENDING | --- @@ -56,7 +56,7 @@ Els IDs es mantenen estables una vegada assignats. Cada entrada apunta al fitxer | PATCH-01 | `player.cpp:66-77` | Col·lisió per eix fent `AnimatedSprite::update` + revertir + reaplicar | Importante | PENDING | `tryMove(dx, dy, collisionFn)` a la classe base | | PATCH-02 | `player.cpp:13-15` | Comentari tipus TODO ("si més endavant volem sprint...") | Menor | PENDING | Treure o convertir en feature amb issue | | PATCH-03 | `map.cpp:13-15` | `overlapStrict` custom perquè SDL3 considera el toc com intersecció | Menor | PENDING | Si s'usa més, mourea `utils/` | -| PATCH-04 | `options.cpp:361-363,371-375,475-480,528-533` | Reescriure silenciosament `config.yaml` al fallar parseig o canviar versió | Crítico | PENDING | `.bak` + log abans de sobreescriure | +| PATCH-04 | `options.cpp:361-363,371-375,475-480,528-533` | Reescriure silenciosament `config.yaml` al fallar parseig o canviar versió | Crítico | DONE | Helper `backupFile()` que copia a `.bak` si existeix. Cridat abans de cada reescriptura forçada (versió mismatch + catch a `loadFromFile`, `presets:` absent/invàlid/buit + catch a `loadPresetsFromFile`). Primera execució no genera `.bak`. Missatge explícit per motiu de reescriptura | | PATCH-05 | `game_scene.cpp:51` | `if (Audio::get() != nullptr)` com a defensa sobre un singleton Meyers | Menor | PENDING | Assumir invariant, treure | | PATCH-06 | `game_scene.cpp:78-91` | Ordenació per `getFeetY()` amb 1 jugador i sense sarcs-sprite: cerimonial | Menor | PENDING | Comentar "reactivar quan hi haja sarcs-sprite" o treure | | PATCH-07 | `map.hpp:64` + `map.cpp:19` | `Map` declara ctor però tots els mètodes són `static` (transició a mitges) | Importante | PENDING | Decidir: namespace o instanciar | diff --git a/source/game/options.cpp b/source/game/options.cpp index db5d4ea..9289e00 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -76,6 +76,20 @@ namespace { return file.good(); } + // Còpia `` a `.bak` per protegir ediciones manuals de + // l'usuari quan estem a punt de reescriure el fitxer amb defaults per + // error de parseig o canvi de versió. Si el fitxer no existeix (primera + // execució) o no es pot obrir, no fa res: no hi ha res a salvar. + void backupFile(const std::string& path) { + if (path.empty()) { return; } + std::ifstream src(path, std::ios::binary); + if (!src) { return; } + std::ofstream dst(path + ".bak", std::ios::binary); + if (!dst) { return; } + dst << src.rdbuf(); + std::cerr << "Options: còpia de seguretat a " << path << ".bak\n"; + } + void parseWindow(const fkyaml::node& yaml) { if (!yaml.contains("window")) { return; } const auto& node = yaml["window"]; @@ -351,12 +365,15 @@ namespace { const std::string CONTENT = readFileOrEmpty(path); if (CONTENT.empty()) { + // Primera execució (no existia o era buit): no cal backup. presets = defaults_fn(); return save_fn(); } try { auto yaml = fkyaml::node::deserialize(CONTENT); if (!yaml.contains("presets") || !yaml["presets"].is_sequence()) { + std::cerr << "Options: " << kind_label << " manca `presets:`; reescrivint amb defaults.\n"; + backupFile(path); presets = defaults_fn(); return save_fn(); } @@ -367,6 +384,8 @@ namespace { presets.push_back(preset); } if (presets.empty()) { + std::cerr << "Options: " << kind_label << " sense presets; reescrivint amb defaults.\n"; + backupFile(path); presets = defaults_fn(); return save_fn(); } @@ -374,6 +393,7 @@ namespace { } 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"; + backupFile(path); presets = defaults_fn(); return save_fn(); } @@ -426,8 +446,14 @@ namespace Options { file_version = yaml["version"].get_value(); } // Sense migració per ara: si la versió no coincideix, reescrivim - // amb els defaults de la versió nova. - if (file_version != Texts::VERSION) { return saveToFile(); } + // amb els defaults de la versió nova. El fitxer original queda + // a `.bak` per si l'usuari volia preservar tuning manual. + if (file_version != Texts::VERSION) { + std::cerr << "Options: config.yaml versió \"" << file_version + << "\" != \"" << Texts::VERSION << "\"; reescrivint amb defaults.\n"; + backupFile(config_file_path); + return saveToFile(); + } config_version = file_version; if (yaml.contains("locale")) { Options::locale = yaml["locale"].get_value(); } @@ -438,6 +464,7 @@ namespace Options { } catch (const std::exception& e) { std::cerr << "Options: error parsejant config.yaml: " << e.what() << '\n'; std::cerr << "Options: reescrivint el fitxer amb defaults.\n"; + backupFile(config_file_path); return saveToFile(); } }