feat: Sprint 2 — moteur de combat PvE TetaRdPG

Moteur combat stateless (POST /api/combat/start résout le combat complet).
Formules GDD : Mêlée/Distance/Magie × 1.5, critique (5% + Chance×0.2%), esquive (5% + Chance×0.1%).
5 monstres seedés (Têtard Vase → Golem de Boue, level 1–9).
Level up : XP → seuil atteint → level++, +5 statPoints.
Persiste combat_logs (jsonb rounds). Validé : victoire, défaite, 401, 400, 404.
This commit is contained in:
2026-03-15 06:10:06 +01:00
parent da3237bf3f
commit 6d1230d16a
15 changed files with 806 additions and 0 deletions

126
SPRINT2.md Normal file
View File

@@ -0,0 +1,126 @@
# TetaRdPG — Brief Sprint 2
> Statut : 🔄 En cours
> Objectif : Moteur de combat PvE + retours textuels (logs)
> Stack : NestJS · PostgreSQL · TypeORM (synchronize dev)
> Prérequis : Sprint 1 livré ✅
---
## Scope Sprint 2
### ✅ In scope
- Entité `monsters` (5 monstres seedés, level 18)
- Moteur de combat PvE — résolution stateless côté serveur (un POST = combat complet)
- Formules GDD : Mêlée/Distance/Magie × 1.5 | Critique | Esquive
- Fin de combat : Victoire (XP + Or + +10% PV) / Défaite (20% PV + 50 endurance + perte or)
- Persistance des combats (`combat_logs`)
- Mise à jour character post-combat : hpCurrent, endurance, xp, gold, level, statPoints
- Level up basique : XP → seuil atteint → level++, +5 statPoints
- API : `GET /api/monsters`, `POST /api/combat/start`, `GET /api/combat/history`
### ❌ Out of scope
- Interactivité tour par tour (choix action par round) — Sprint 3
- Équipement / armure / arme → défense joueur = 0 Sprint 2
- Forge, artisanat, boutique
- Twitch, PvP
- Frontend React
---
## Décisions de design (game-designer)
| Décision | Valeur Sprint 2 | Justification |
|----------|----------------|---------------|
| Dégâts joueur | `Math.floor(stat × 1.5)` | Arme = 0 (pas d'équipement) |
| Défense joueur | 0 | Pas d'armure Sprint 2 |
| Crit | `5% + Chance × 0.2%` → dégâts ×1.5 | GDD |
| Esquive | `5% + Chance × 0.1%` → annule dégâts | GDD |
| Crit/Esquive monstres | Non (Sprint 2) | Simplification, fairness joueur |
| Défaite HP | 20% hpMax (retour auberge) | GDD "retour auberge" |
| Coût combat défaite | 10 (start) + 50 (peine) = 60 | GDD |
| Level up | `XP requise = round(100 × level^1.5)` | Formule GDD |
| Stat points / level | +5 par level franchi | GDD |
---
## Monstres seedés
| Nom | Level min | Level max | HP | Attaque | Défense | Type | XP | Or (min-max) |
|-----|-----------|-----------|-----|---------|---------|------|----|-------------|
| Têtard Vase | 1 | 2 | 40 | 5 | 0 | melee | 25 | 38 |
| Grenouille Boueuse | 2 | 4 | 65 | 8 | 1 | melee | 45 | 615 |
| Serpent des Marais | 3 | 6 | 90 | 11 | 2 | ranged | 70 | 1025 |
| Champi Vénéneux | 2 | 5 | 75 | 9 | 3 | magic | 60 | 820 |
| Golem de Boue | 6 | 9 | 150 | 16 | 5 | melee | 130 | 2560 |
---
## API Sprint 2
```
GET /api/monsters → liste tous les monstres
POST /api/combat/start → { monsterId, attackType: 'melee'|'ranged'|'magic' }
GET /api/combat/history → combats du personnage connecté
```
### Format réponse POST /api/combat/start
```json
{
"winner": "player",
"rounds": [
{
"round": 1,
"playerDamage": 6, "playerCrit": true, "monsterDodged": false,
"monsterDamage": 5, "playerDodged": false,
"playerHp": 95, "monsterHp": 34,
"log": ["Tetard frappe le Têtard Vase pour 6 dégâts (CRITIQUE) !", "Le Têtard Vase frappe Tetard pour 5 dégâts."]
}
],
"summary": "Victoire en 7 tours ! +25 XP, +5 Or.",
"rewards": { "xp": 25, "gold": 5, "levelUp": false },
"character": { "level": 1, "xp": 25, "gold": 5, "hpCurrent": 75, "enduranceCurrent": 90 }
}
```
---
## Architecture modules
```
src/
├── monster/
│ ├── monster.entity.ts
│ ├── monster.module.ts
│ ├── monster.service.ts
│ └── monster.controller.ts
├── combat/
│ ├── combat.engine.ts → fonctions pures (pas de dépendances NestJS)
│ ├── combat-log.entity.ts
│ ├── combat.service.ts
│ ├── combat.controller.ts
│ ├── combat.module.ts
│ └── dto/start-combat.dto.ts
└── database/
└── monsters-seed.ts
```
---
## Critères de validation integrator
- [ ] `GET /api/monsters` → liste 5 monstres
- [ ] `POST /api/combat/start` → combat résolu, log retourné
- [ ] Personnage level 1 peut vaincre Têtard Vase
- [ ] XP et or crédités après victoire
- [ ] `hpCurrent` mis à jour en DB après combat
- [ ] Endurance déduite (10) après combat
- [ ] Défaite : `hpCurrent` = 20% hpMax, endurance 60 total
- [ ] `GET /api/combat/history` → historique retourné
- [ ] Sans cookie → 401
- [ ] Endurance insuffisante (< 10) → 400
- [ ] Monster inexistant → 404
- [ ] Level up déclenché si XP seuil atteint