feat(sprint1-step3b): backend save system + anti-cheat + données rattrapées

- game_saves table + migration 002 (JSON state, anti-cheat metadata)
- saveControllers.js : load/save avec validation delta ressources (750k/s × 1.1)
- GameSaveManager : upsert MySQL ON DUPLICATE KEY UPDATE
- useSaveSync hook : auto-save 30s + keepalive beforeunload + guest fallback
- save-validation.test.ts : 8 tests anti-cheat
- economy.ts : arbre d'évolution 5 nœuds + prestige ADN (rattrapage step 2)
- economy.test.ts : +40 tests (évolution tree, multipliers, start bonus)
- GDD + SPRINT1.md : docs sprint complètes
- Rethème data : shop.json, Achievements.json, Cookie, Legal (rattrapage step 1)
This commit is contained in:
2026-03-20 13:40:16 +01:00
parent 9f0ccda99b
commit a52746ed0c
20 changed files with 1167 additions and 152 deletions

View File

@@ -0,0 +1,43 @@
const AbstractManager = require("./AbstractManager");
class GameSaveManager extends AbstractManager {
constructor() {
super({ table: "game_saves" });
}
async getByUserId(userId) {
const [rows] = await this.database.query(
`SELECT * FROM ${this.table} WHERE user_id = ?`,
[userId]
);
return rows[0] ?? null;
}
async upsert(userId, gameState, metadata) {
const { lifetimeTadpoles, prestigeCount, playTimeSeconds } = metadata;
const gameStateJson = JSON.stringify(gameState);
const [result] = await this.database.query(
`INSERT INTO ${this.table} (user_id, game_state, lifetime_tadpoles, prestige_count, play_time_seconds)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
game_state = VALUES(game_state),
lifetime_tadpoles = VALUES(lifetime_tadpoles),
prestige_count = VALUES(prestige_count),
play_time_seconds = VALUES(play_time_seconds),
last_save = CURRENT_TIMESTAMP`,
[userId, gameStateJson, lifetimeTadpoles, prestigeCount, playTimeSeconds]
);
return result.affectedRows;
}
async delete(userId) {
await this.database.query(
`DELETE FROM ${this.table} WHERE user_id = ?`,
[userId]
);
}
}
module.exports = GameSaveManager;