dd-timer/tests/regression/gauge-recharge.test.ts
POL Mickaël 203c423f19 test: 302 tests unitaires + 20 E2E Playwright (couverture 94%)
- Unit : domain (GaugeCalculator, Enclos, Dragodinde, XpTable, Race, Tier...)
- Unit : application (commands, queries, CommandBus)
- Fonctionnel : breeding-workflow, enclos-management, timer-workflow
- Régression : gauge-tier, gauge-recharge, xp-timer, level-target, breeding
- E2E Playwright + Electron : navigation, timer, recharge jauge,
  accouplement, persistance des données

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 05:43:29 +02:00

143 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect } from 'vitest';
import { computeGaugeState } from '@domain/services/GaugeCalculator';
import type { GaugeRecharge } from '@domain/services/GaugeCalculator';
import { gainedIn, timeToGain } from '@domain/services/GaugeCalculator';
import { xpForLevel, levelFromXp } from '@domain/value-objects/XpTable';
/**
* Tests de régression pour la fonctionnalité de recharge de jauge en cours de session.
*
* Scénario typique : le joueur recharge la mangeoire pendant le timer car elle s'est vidée.
* Sans recharge prise en compte, le countdown reste bloqué à ∞ ou 0 au lieu de recalculer.
*/
describe('Régression: recharge de jauge en cours de timer', () => {
describe('Mangeoire rechargée une fois (scénario XP principal)', () => {
// Contexte :
// - Mangeoire départ = 50 000 (tier 2, rate 20/tick)
// - DD level 1, cible level 50
// - Après 1h (3600 sec), la mangeoire est vide → XP gagnée jusqu'ici
// - Le joueur recharge à 80 000 (tier 3)
// - On simule 1h supplémentaire après recharge
const startGl = 50000;
const xpCible = xpForLevel(50) - xpForLevel(1); // 34 365 XP
it('sans recharge : XP limité par la jauge qui se vide', () => {
// 50 000 pts de jauge → max 50 000 XP disponible (tier 2 → tier 1)
// Mais XP cible = 34 365 → la jauge suffira-t-elle ?
// timeToGain(50000, 34365) :
// tier2 (40000-50000) : 10000 pts, s += ceil(10000/20)*10=5000, rem=24365
// tier1 (0-40000) : 40000 pts, s += ceil(24365/10)*10=24370, rem=0
// total = 29370 sec ≈ 8h 9m
const sec = timeToGain(startGl, xpCible);
expect(sec).toBe(29370);
});
it('avec recharge à t=5000 sec : XP total = seg1 + seg2', () => {
// Seg1 : 50000 (tier2→tier1), 5000 sec → gainedIn(50000, 5000) = ?
// tier2 (40000-50000) : a=10000, m=500, u=500 ticks → 10000 pts, tl=0
// → gained seg1 = 10000
// Recharge à 80000 à t=5000
// Seg2 : 80000 (tier3), 5000 sec → gainedIn(80000, 5000) = ?
// tier3 (70000-80000) : a=10000, m=333, u=333 ticks → 9990, tl=167
// tier2 (40000-70000) : a=30000, m=1500, u=167 ticks → 3340
// → gained seg2 = 13330
// Total = 23330 XP
const recharges: GaugeRecharge[] = [{ atSec: 5000, level: 80000 }];
const { gained } = computeGaugeState(startGl, recharges, Infinity, 10000);
const expectedSeg1 = gainedIn(50000, 5000);
const expectedSeg2 = gainedIn(80000, 5000);
expect(gained).toBe(expectedSeg1 + expectedSeg2);
});
it('la recharge fait redémarrer le countdown vers la cible', () => {
// Après recharge : XP restante = xpCible - gained_so_far
// Le countdown doit utiliser curGl (niveau après recharge et dépletion seg2)
const rechargeEl = 5000; // 5000 sec = quand le joueur recharge
const totalEl = 10000;
const recharges: GaugeRecharge[] = [{ atSec: rechargeEl, level: 80000 }];
const { gained: gainedSoFar, curGl } = computeGaugeState(startGl, recharges, Infinity, totalEl);
const xpRestante = Math.max(0, xpCible - gainedSoFar);
// curGl doit refléter la jauge après seg2 (pas celle d'origine à 50000)
expect(curGl).toBeGreaterThan(0);
expect(curGl).toBeLessThan(80000); // la jauge a déjà décru depuis la recharge
// timeToGain depuis curGl doit être < timeToGain depuis startGl (grâce à la recharge)
const secAvecRecharge = timeToGain(curGl, xpRestante);
const secSansRecharge = timeToGain(gainedIn(startGl, 5000) > 0 ? 0 : startGl, xpRestante);
expect(secAvecRecharge).toBeLessThan(secSansRecharge === Infinity ? Infinity : secSansRecharge + 1);
});
});
describe('Baffeur rechargé (sérenité)', () => {
// Baffeur 70000 (tier2 → cap à -5000 séren.)
// Séren. départ = 0, cap absolu = 5000 pts à gagner
// À t=3000 sec, cap atteint → gel. Recharge à t=4000 → ne change rien (déjà gelé).
const startGl = 70000;
const ptsToAbsCap = 5000; // 0 - (-5000)
it('gel au cap absolu même avec recharge après le gel', () => {
const recharges: GaugeRecharge[] = [{ atSec: 4000, level: 90000 }]; // recharge après gel
const { gained, effectiveEl } = computeGaugeState(startGl, recharges, ptsToAbsCap, 9999);
// Gel = timeToGain(70000, 5000) :
// tier2 (40000-70000) : 5000 pts → ceil(5000/20)*10 = 2500 sec
expect(gained).toBe(5000);
expect(effectiveEl).toBe(2500);
// La recharge à t=4000 est APRÈS le gel à t=2500 → ignorée
});
it('recharge AVANT le gel allonge le temps de traitement', () => {
// Recharge à t=1000 (avant gel naturel à t=2500)
// Seg1 : 70000, 1000 sec → gainedIn(70000, 1000) = 100 ticks
// tier3 (70000-90000): a=0 (70000 non > 70000... wait 70000>70000=false)
// Hmm: 70000 > 70000 = false, donc tier3 skip
// tier2 (40000-70000): a=30000, m=1500, u=min(1500,100)=100, out=2000
// gained seg1 = 2000, jauge = 70000 - 100*20 = 68000
// Recharge à 90000 à t=1000
// Seg2 : 90000, besoin encore 3000 pts, timeToGain(90000,3000) :
// tier3 (70000-90000): 20000 pts, 3000/30=100 ticks=1000 sec
// effectiveEl = 1000 + 1000 = 2000
const recharges: GaugeRecharge[] = [{ atSec: 1000, level: 90000 }];
const { gained, effectiveEl } = computeGaugeState(70000, recharges, 5000, 9999);
expect(gained).toBe(5000);
expect(effectiveEl).toBe(2000); // plus rapide qu'à 2500 grâce au tier3
});
});
describe('Deux recharges successives (mangeoire longue session)', () => {
it('accumule correctement trois segments', () => {
// Seg1 : 30000 (tier1), 500 sec → 50 ticks × 10 = 500 pts
// Recharge 1 à t=500 → 80000
// Seg2 : 80000 (tier2→tier3), 500 sec → gainedIn(80000, 500)
// Recharge 2 à t=1000 → 95000
// Seg3 : 95000 (tier4), 500 sec → gainedIn(95000, 500)
const recharges: GaugeRecharge[] = [
{ atSec: 500, level: 80000 },
{ atSec: 1000, level: 95000 },
];
const { gained } = computeGaugeState(30000, recharges, Infinity, 1500);
const expected = gainedIn(30000, 500) + gainedIn(80000, 500) + gainedIn(95000, 500);
expect(gained).toBe(expected);
});
it('le niveau XP estimé est cohérent avec les points accumulés', () => {
const startXp = 1; // level 1
const startGl = 30000;
const recharges: GaugeRecharge[] = [
{ atSec: 500, level: 80000 },
{ atSec: 1000, level: 95000 },
];
const { gained } = computeGaugeState(startGl, recharges, Infinity, 1500);
const estLevel = levelFromXp(xpForLevel(startXp) + gained);
// Avec 3 segments : ~500 + ~gainedIn(80000,500) + ~gainedIn(95000,500) pts d'XP
// ça devrait faire progresser significativement le niveau
expect(estLevel).toBeGreaterThan(startXp);
});
});
});