feat: sérialisation Party et migration des sauvegardes (#50) #60

Merged
Reliodas merged 1 commit from feature/50-party-serialization into develop 2026-06-22 03:17:32 +00:00
Owner

Closes #50.

Fait du groupe Party la source persistante de la sauvegarde, tout en conservant une migration explicite depuis les sauvegardes 0.15.0. Deuxième pierre du jalon 0.16.0, à la suite de #49 (modèle runtime). Le groupe — roster, actif, présents, leader, modes de contrôle, état complet des Creature — survit désormais au save/load, et une ancienne sauvegarde centrée sur le héros reste chargeable. Cette base débloque le recrutement (#51) et le suivi des compagnons en exploration (#52).

Changements

  • Deux DTO Resource dédiés sous scripts/party/PartyMemberSaveData (member_id, creature, control_mode) et PartySaveData (roster ordonné, active_ids, present_ids, leader_id) — distincts du modèle runtime RefCounted, car seules les ressources se sérialisent proprement en .tres.
  • Party.to_save_data() / Party.from_save_data() : la reconstruction rejoue chaque étape via l'API publique de Party, donc valide tous les invariants ; toute incohérence renvoie null (reconstruction atomique, jamais de demi-groupe).
  • GameSave gagne format_version (défaut 0 à dessein, pour distinguer une sauvegarde 0.15.0 sans ce champ) et party_data. GameState.SAVE_FORMAT_VERSION = 1 ; les nouvelles sauvegardes laissent le champ hérité hero à null.
  • GameState.apply_save() reconstruit et valide le groupe hors de l'état courant, ne remplace l'état qu'en cas de succès et renvoie un bool ; SaveManager.load_from_slot() le propage. Une sauvegarde corrompue (version ≥ 1 sans party_data) est refusée sans rien altérer.
  • Migration : une sauvegarde version 0 avec hero non nul devient un protagoniste joueur, actif et leader.

Critères d'acceptation

  • une sauvegarde 0.15.0 valide crée un Party contenant l'ancien héros ;
  • save/load restaure les mêmes instances logiques, PV, ressources et contrôle ;
  • les membres hors groupe actif ne perdent pas leur état ;
  • les positions de chaque membre présent sont restaurées ;
  • une donnée invalide échoue proprement ou applique un repli documenté ;
  • tests couvrent aller-retour complet et migration.

Tests

Suite GUT complète verte : 223 tests / 724 assertions (depuis 213 / 687).

  • tests/unit/test_party_save.gd : aller-retour DTO (ordre roster/actif/présents, leader, modes de contrôle, état des Creature y compris hors groupe actif), refus des données invalides (id dupliqué, actif inconnu, leader/présent hors actif, format récent sans party_data), migration 0.15.0, et round-trip disque complet avec compagnon via SaveManager.
  • tests/unit/test_world_foundations.gd : tests de sauvegarde 0.15.0 réalignés sur le nouveau contrat (héros via party_data).

Note de périmètre

Les positions individuelles par membre (member_pos_by_map) évoquées au plan sont différées à #52, où World introduit des positions distinctes pour le leader et les suiveurs. En 0.16.0/#50, le seul membre présent est le protagoniste, dont la position reste portée par player_pos_by_map (round-trip déjà couvert) : le critère « positions de chaque membre présent restaurées » est satisfait sans introduire de seconde carte de positions à moitié câblée.

Closes #50. Fait du groupe `Party` la source persistante de la sauvegarde, tout en conservant une migration explicite depuis les sauvegardes 0.15.0. Deuxième pierre du jalon 0.16.0, à la suite de #49 (modèle runtime). Le groupe — roster, actif, présents, leader, modes de contrôle, état complet des `Creature` — survit désormais au save/load, et une ancienne sauvegarde centrée sur le héros reste chargeable. Cette base débloque le recrutement (#51) et le suivi des compagnons en exploration (#52). ## Changements - Deux DTO `Resource` dédiés sous `scripts/party/` — **`PartyMemberSaveData`** (`member_id`, `creature`, `control_mode`) et **`PartySaveData`** (roster ordonné, `active_ids`, `present_ids`, `leader_id`) — distincts du modèle runtime `RefCounted`, car seules les ressources se sérialisent proprement en `.tres`. - **`Party.to_save_data()`** / **`Party.from_save_data()`** : la reconstruction **rejoue chaque étape via l'API publique** de `Party`, donc valide tous les invariants ; toute incohérence renvoie `null` (reconstruction atomique, jamais de demi-groupe). - **`GameSave`** gagne `format_version` (**défaut 0** à dessein, pour distinguer une sauvegarde 0.15.0 sans ce champ) et `party_data`. `GameState.SAVE_FORMAT_VERSION = 1` ; les nouvelles sauvegardes laissent le champ hérité `hero` à `null`. - **`GameState.apply_save()`** reconstruit et valide le groupe **hors de l'état courant**, ne remplace l'état qu'en cas de succès et renvoie un `bool` ; `SaveManager.load_from_slot()` le propage. Une sauvegarde corrompue (version ≥ 1 sans `party_data`) est refusée sans rien altérer. - **Migration** : une sauvegarde version 0 avec `hero` non nul devient un protagoniste joueur, actif et leader. ## Critères d'acceptation - [x] une sauvegarde 0.15.0 valide crée un Party contenant l'ancien héros ; - [x] save/load restaure les mêmes instances logiques, PV, ressources et contrôle ; - [x] les membres hors groupe actif ne perdent pas leur état ; - [x] les positions de chaque membre présent sont restaurées ; - [x] une donnée invalide échoue proprement ou applique un repli documenté ; - [x] tests couvrent aller-retour complet et migration. ## Tests Suite GUT complète verte : **223 tests / 724 assertions** (depuis 213 / 687). - `tests/unit/test_party_save.gd` : aller-retour DTO (ordre roster/actif/présents, leader, modes de contrôle, état des `Creature` y compris hors groupe actif), refus des données invalides (id dupliqué, actif inconnu, leader/présent hors actif, format récent sans `party_data`), migration 0.15.0, et **round-trip disque complet avec compagnon** via `SaveManager`. - `tests/unit/test_world_foundations.gd` : tests de sauvegarde 0.15.0 réalignés sur le nouveau contrat (héros via `party_data`). ### Note de périmètre Les positions individuelles par membre (`member_pos_by_map`) évoquées au plan sont **différées à #52**, où `World` introduit des positions distinctes pour le leader et les suiveurs. En 0.16.0/#50, le seul membre présent est le protagoniste, dont la position reste portée par `player_pos_by_map` (round-trip déjà couvert) : le critère « positions de chaque membre présent restaurées » est satisfait sans introduire de seconde carte de positions à moitié câblée.
feat: sérialise Party et migre les sauvegardes 0.15.0 (#50)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
d5c5e3dfe7
Introduit les DTO de transport PartyMemberSaveData et PartySaveData
(scripts/party/) et les conversions Party.to_save_data /
Party.from_save_data, qui rejouent l'état via l'API publique en validant
tous les invariants (reconstruction atomique : null si incohérent).

GameSave gagne format_version (défaut 0, pour distinguer les sauvegardes
0.15.0) et party_data ; les nouvelles sauvegardes (version 1) laissent le
champ hérité hero à null. GameState.apply_save reconstruit et valide le
groupe hors de l'état courant puis ne remplace qu'en cas de succès, et
renvoie un booléen propagé par SaveManager.load_from_slot. Une sauvegarde
0.15.0 (héros seul) est migrée en protagoniste joueur, actif et leader.

Tests : test_party_save.gd (aller-retour DTO, refus des données
invalides, migration 0.15.0, round-trip disque avec compagnon) ; tests
de sauvegarde 0.15.0 réalignés sur le nouveau contrat. Suite 223 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reliodas merged commit ffb7dba26e into develop 2026-06-22 03:17:32 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: jeux/beaulieu-sur-brume#60
No description provided.