feat: offline gains — courbe inversée 2h, cap 25%, écran résumé
offlineEfficiency() : 100% (0-15min) → 25% (1h) → 0% (2h). computeOfflineGains() intègre la courbe par tranches de 1min. GameState.lastOnline ajouté, store hydrate avec offline report. OfflineReport.tsx affiché au retour si absence > 60s. 13 nouveaux tests (66 total, tous passent).
This commit is contained in:
@@ -26,6 +26,7 @@ export interface GameState {
|
||||
clickMultiplier: number;
|
||||
generators: Generator[];
|
||||
lastTick: number; // timestamp ms — lazy calc reference
|
||||
lastOnline: number; // timestamp ms — dernière activité réelle (tick actif)
|
||||
prestigeCount: number;
|
||||
prestigeMultiplier: number; // legacy — conservé pour compat, remplacé par arbre
|
||||
ancestralDna: number;
|
||||
@@ -95,6 +96,52 @@ export function getStartBonusFromTree(tree: EvolutionNode[]): number {
|
||||
.reduce((sum, n) => sum + n.value, 0);
|
||||
}
|
||||
|
||||
// --- Offline gains (courbe inversée) ---
|
||||
|
||||
const OFFLINE_THRESHOLD = 60_000; // 60s — en-dessous = idle normal, au-dessus = offline
|
||||
const OFFLINE_FULL_MS = 15 * 60_000; // 0-15min : 100%
|
||||
const OFFLINE_DECAY_END_MS = 60 * 60_000; // 15min-1h : 100% → 25%
|
||||
const OFFLINE_ZERO_MS = 2 * 60 * 60_000; // 1h-2h : 25% → 0%
|
||||
const OFFLINE_FLOOR = 0.25; // plancher de la phase de decay
|
||||
|
||||
// Retourne le multiplicateur d'efficacité offline (1.0 → 0.0)
|
||||
// basé sur le temps d'absence en ms
|
||||
export function offlineEfficiency(elapsedMs: number): number {
|
||||
if (elapsedMs <= OFFLINE_THRESHOLD) return 1; // pas offline
|
||||
if (elapsedMs <= OFFLINE_FULL_MS) return 1; // 0-15min : 100%
|
||||
if (elapsedMs <= OFFLINE_DECAY_END_MS) {
|
||||
// 15min-1h : linéaire 1.0 → 0.25
|
||||
const t = (elapsedMs - OFFLINE_FULL_MS) / (OFFLINE_DECAY_END_MS - OFFLINE_FULL_MS);
|
||||
return 1 - t * (1 - OFFLINE_FLOOR);
|
||||
}
|
||||
if (elapsedMs <= OFFLINE_ZERO_MS) {
|
||||
// 1h-2h : linéaire 0.25 → 0.0
|
||||
const t = (elapsedMs - OFFLINE_DECAY_END_MS) / (OFFLINE_ZERO_MS - OFFLINE_DECAY_END_MS);
|
||||
return OFFLINE_FLOOR * (1 - t);
|
||||
}
|
||||
return 0; // >2h : rien
|
||||
}
|
||||
|
||||
// Calcule les gains offline avec la courbe dégressive
|
||||
// Intègre la courbe par tranches de 1 minute pour plus de précision
|
||||
export function computeOfflineGains(state: GameState, now: number): number {
|
||||
const elapsed = now - state.lastTick;
|
||||
if (elapsed <= OFFLINE_THRESHOLD) return computeIdleGains(state, now);
|
||||
|
||||
const pps = totalProductionPerSecond(state);
|
||||
if (pps <= 0) return 0;
|
||||
|
||||
// Intégration par tranches de 60s
|
||||
const STEP = 60_000;
|
||||
let total = 0;
|
||||
for (let t = 0; t < elapsed; t += STEP) {
|
||||
const chunk = Math.min(STEP, elapsed - t);
|
||||
const eff = offlineEfficiency(t + chunk / 2); // milieu de la tranche
|
||||
total += pps * (chunk / 1000) * eff;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// --- Core economy (mis à jour pour intégrer l'arbre) ---
|
||||
|
||||
// Coût d'achat du N-ième générateur : baseCost × 1.15^owned
|
||||
@@ -183,6 +230,7 @@ export function applyPrestige(state: GameState): GameState {
|
||||
ancestralDna: state.ancestralDna + dnaGained,
|
||||
lifetimeTadpoles: 0,
|
||||
lastTick: Date.now(),
|
||||
lastOnline: Date.now(),
|
||||
// evolutionTree persiste — jamais reset
|
||||
};
|
||||
}
|
||||
@@ -201,6 +249,7 @@ export const DEFAULT_STATE: GameState = {
|
||||
clickMultiplier: 1,
|
||||
generators: DEFAULT_GENERATORS,
|
||||
lastTick: Date.now(),
|
||||
lastOnline: Date.now(),
|
||||
prestigeCount: 0,
|
||||
prestigeMultiplier: 1,
|
||||
ancestralDna: 0,
|
||||
|
||||
Reference in New Issue
Block a user