- 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>
143 lines
6.9 KiB
TypeScript
143 lines
6.9 KiB
TypeScript
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);
|
||
});
|
||
});
|
||
});
|