fix: câbler tous les effets arbre + cleanup dette Sprint 2

- double_click_chance + crit_click_chance câblés dans applyClick (RNG)
- auto_click câblé dans le tick (auto-pontes/s)
- unlock_generator (Résilience) → 1 Lac Mystique gratuit au prestige
- ponte_critique requires double_ponte (fix branche morte)
- achievement_scaling retiré (nœud absent), full_tree + symbiose fixés
- Particule feedback coloré (crit=ambre, double=violet)
- 99 tests (tous passent)
This commit is contained in:
2026-03-28 12:41:12 +01:00
parent 2a242e97cc
commit 2c924c1e4a
6 changed files with 185 additions and 30 deletions

View File

@@ -17,6 +17,7 @@ import {
getStartBonusFromTree,
getPrestigeDnaBonus,
getCostReduction,
getAutoClicksPerSecond,
offlineEfficiency,
computeOfflineGains,
DEFAULT_STATE,
@@ -173,13 +174,15 @@ describe("computeIdleGains (lazy calculation)", () => {
});
});
// --- Click ---
// --- Click (avec double + crit) ---
describe("applyClick", () => {
it("augmente les ressources du clickMultiplier × prestigeMultiplier", () => {
const state = { ...DEFAULT_STATE, clickMultiplier: 3, prestigeMultiplier: 2 };
const result = applyClick(state);
expect(result.resources).toBe(6);
const result = applyClick(state, 0.99); // rng high → no double, no crit
expect(result.state.resources).toBe(6);
expect(result.isDouble).toBe(false);
expect(result.isCrit).toBe(false);
});
it("applique le multiplicateur click de l'arbre", () => {
@@ -191,14 +194,58 @@ describe("applyClick", () => {
n.id === "ponte_amelioree" ? { ...n, unlocked: true } : n
),
};
const result = applyClick(state);
expect(result.resources).toBe(2); // ×2 depuis Ponte Améliorée
const result = applyClick(state, 0.99);
expect(result.state.resources).toBe(2);
});
it("incrémente lifetimeTadpoles", () => {
const state = { ...DEFAULT_STATE, clickMultiplier: 5, prestigeMultiplier: 1 };
const result = applyClick(state);
expect(result.lifetimeTadpoles).toBe(5);
const result = applyClick(state, 0.99);
expect(result.state.lifetimeTadpoles).toBe(5);
});
it("double ponte x2 quand rng < doubleClickChance", () => {
const state = {
...DEFAULT_STATE,
clickMultiplier: 1,
prestigeMultiplier: 1,
evolutionTree: DEFAULT_STATE.evolutionTree.map((n) =>
n.id === "double_ponte" ? { ...n, unlocked: true } : n
),
};
// double_ponte = 10% chance, rng=0.05 < 0.10 → double
const result = applyClick(state, 0.05);
expect(result.isDouble).toBe(true);
expect(result.gain).toBe(2);
});
it("pas de double ponte quand rng > doubleClickChance", () => {
const state = {
...DEFAULT_STATE,
clickMultiplier: 1,
evolutionTree: DEFAULT_STATE.evolutionTree.map((n) =>
n.id === "double_ponte" ? { ...n, unlocked: true } : n
),
};
const result = applyClick(state, 0.50);
expect(result.isDouble).toBe(false);
expect(result.gain).toBe(1);
});
it("crit x10 quand critRng < critClickChance", () => {
const state = {
...DEFAULT_STATE,
clickMultiplier: 1,
prestigeMultiplier: 1,
evolutionTree: DEFAULT_STATE.evolutionTree.map((n) =>
n.id === "ponte_critique" ? { ...n, unlocked: true } : n
),
};
// ponte_critique = 5% chance, need critRng = (rng * 7.13) % 1 < 0.05
// rng = 0.007 → critRng = 0.04991 < 0.05 → crit!
const result = applyClick(state, 0.007);
expect(result.isCrit).toBe(true);
expect(result.gain).toBe(10);
});
});
@@ -444,6 +491,46 @@ describe("Evolution Tree (3 branches)", () => {
});
});
describe("unlock_generator (Résilience)", () => {
it("prestige avec Résilience donne 1 Lac Mystique", () => {
const state = {
...DEFAULT_STATE,
resources: 2_000_000,
generators: DEFAULT_STATE.generators.map((g) => ({ ...g, owned: 5 })),
evolutionTree: DEFAULT_STATE.evolutionTree.map((n) =>
n.id === "resilience" ? { ...n, unlocked: true } : n
),
};
const result = applyPrestige(state);
const lac = result.generators.find((g) => g.id === "lac");
expect(lac!.owned).toBe(1);
});
it("prestige sans Résilience donne 0 Lac Mystique", () => {
const state = {
...DEFAULT_STATE,
resources: 2_000_000,
generators: DEFAULT_STATE.generators.map((g) => ({ ...g, owned: 5 })),
};
const result = applyPrestige(state);
const lac = result.generators.find((g) => g.id === "lac");
expect(lac!.owned).toBe(0);
});
});
describe("auto_click (getAutoClicksPerSecond)", () => {
it("retourne 0 si auto_ponte non débloqué", () => {
expect(getAutoClicksPerSecond(DEFAULT_EVOLUTION_TREE)).toBe(0);
});
it("retourne 1 si auto_ponte débloqué", () => {
const tree = DEFAULT_EVOLUTION_TREE.map((n) =>
n.id === "auto_ponte" ? { ...n, unlocked: true } : n
);
expect(getAutoClicksPerSecond(tree)).toBe(1);
});
});
describe("prestige threshold reduction", () => {
it("Transcendance réduit le seuil de 50%", () => {
const state = {