0.1.9 : debug potion V2

This commit is contained in:
Cedric
2026-06-11 17:50:42 +02:00
parent eed0336ec1
commit beca5a06b6
3 changed files with 166 additions and 49 deletions
+41 -15
View File
@@ -29,15 +29,21 @@ function rollDice(n, faces) {
function resolveAttack(attacker, defender, opts = {}) {
const att = rollDice(attacker.att, 6);
const esq = rollDice(defender.esq, 6);
const attFlat = attacker.attFlat || 0;
const esqFlat = defender.esqFlat || 0;
const attTotal = att.total + attFlat;
const esqTotal = esq.total + esqFlat;
const result = {
attRoll: att.total, esqRoll: esq.total,
attRoll: attTotal, esqRoll: esqTotal,
attDice: attacker.att, esqDice: defender.esq,
hit: opts.autoHit || att.total > esq.total, damage: 0, rawDamage: 0,
critical: !opts.autoHit && att.total >= esq.total * 2, // coup critique MH : dégâts doublés
attFlat, esqFlat,
hit: opts.autoHit || attTotal > esqTotal, damage: 0, rawDamage: 0,
critical: !opts.autoHit && attTotal >= esqTotal * 2, // coup critique MH : dégâts doublés
};
if (result.hit) {
const deg = rollDice(attacker.deg, 3);
result.rawDamage = (deg.total + (attacker.degBonus || 0)) * (result.critical ? 2 : 1);
const degFlat = attacker.degFlat || 0;
result.rawDamage = (deg.total + (attacker.degBonus || 0) + degFlat) * (result.critical ? 2 : 1);
// armure : réduction fixe (équipement) + armure naturelle en D3 (achetée en PI)
const armor = opts.ignoreArmor ? 0
: (defender.armor || 0) + (defender.armorDice ? rollDice(defender.armorDice, 3).total : 0);
@@ -528,8 +534,14 @@ function adjustPX(n) {
/* Jets de toucher au format MH dans le détail. */
function cdAttackRolls(r) {
cdLine(`Votre jet d'Attaque est de : <span class="cd-val">${r.attRoll}</span> (${r.attDice}D6)`);
cdLine(`Le jet d'Esquive de votre adversaire est de : <span class="cd-val">${r.esqRoll}</span> (${r.esqDice}D6)`);
const attLbl = r.attFlat
? `${r.attDice}D6 ${r.attFlat > 0 ? "+" : ""}${r.attFlat}`
: `${r.attDice}D6`;
const esqLbl = r.esqFlat
? `${r.esqDice}D6 ${r.esqFlat > 0 ? "+" : ""}${r.esqFlat}`
: `${r.esqDice}D6`;
cdLine(`Votre jet d'Attaque est de : <span class="cd-val">${r.attRoll}</span> (${attLbl})`);
cdLine(`Le jet d'Esquive de votre adversaire est de : <span class="cd-val">${r.esqRoll}</span> (${esqLbl})`);
if (r.hit) {
cdLine(r.critical
? `Vous avez donc <span class="cd-good">TOUCHÉ</span> votre adversaire par un <span class="cd-good">coup critique</span>.`
@@ -714,7 +726,10 @@ function useComp() {
if (!tryTalent(t.comp, 90, comp.cost, "🥋 Botte Secrète", "comp")) { cdFlush(); afterAction(); return; }
t.compUsed = true;
cdLine(`Vous portez une <b>Botte Secrète</b> à <b>${m.name}</b>.`);
const pseudo = { att: Math.max(1, Math.floor(te.att * 2 / 3)), deg: Math.max(1, Math.floor(te.att / 2)), degBonus: te.degBonus };
const pseudo = {
att: Math.max(1, Math.floor(te.att * 2 / 3)), deg: Math.max(1, Math.floor(te.att / 2)),
degBonus: te.degBonus, attFlat: te.attFlat, degFlat: te.degFlat,
};
const r = resolveAttack(pseudo, effMonster(m));
cdAttackRolls(r);
if (r.hit) {
@@ -810,7 +825,10 @@ function useSort() {
if (!m) { log("Aucun monstre adjacent.", "info"); return; }
if (!tryTalent(t.sort, 80, sort.cost, "🔮 Vampirisme", "sort")) { cdFlush(); afterAction(); return; }
cdLine(`Vous avez attaqué <b>${m.name}</b> grâce à un sortilège.`);
const pseudo = { att: Math.max(1, Math.floor(te.deg * 2 / 3)), deg: te.deg, degBonus: te.degBonus };
const pseudo = {
att: Math.max(1, Math.floor(te.deg * 2 / 3)), deg: te.deg,
degBonus: te.degBonus, attFlat: te.attFlat, degFlat: te.degFlat,
};
const r = resolveAttack(pseudo, effMonster(m), { ignoreArmor: true });
cdAttackRolls(r);
if (r.hit) {
@@ -859,7 +877,7 @@ function useSort() {
if (!m) { log("Aucun monstre adjacent.", "info"); return; }
if (!tryTalent(t.sort, 80, sort.cost, "🔮 Siphon des Âmes", "sort")) { cdFlush(); afterAction(); return; }
cdLine(`Vous avez attaqué <b>${m.name}</b> grâce à un sortilège.`);
const pseudo = { att: te.att, deg: te.reg, degBonus: 0 };
const pseudo = { att: te.att, deg: te.reg, degBonus: 0, attFlat: te.attFlat, degFlat: te.degFlat };
const r = resolveAttack(pseudo, effMonster(m), { ignoreArmor: true });
cdAttackRolls(r);
if (r.hit) {
@@ -1025,8 +1043,10 @@ function passDLA() {
const te = effTroll(t);
if (t.pv < t.pvMax && t.pv > 0) {
const r = rollDice(te.reg, 3);
t.pv = Math.min(t.pvMax, t.pv + r.total);
log(`💤 Tour n°${t.tour} (DLA n°${t.dla}) : tu régénères ${r.total} PV (${te.reg}D3).`, "info");
const heal = r.total + (te.regFlat || 0);
t.pv = Math.min(t.pvMax, t.pv + heal);
const regLbl = te.regFlat ? `${te.reg}D3 +${te.regFlat}` : `${te.reg}D3`;
log(`💤 Tour n°${t.tour} (DLA n°${t.dla}) : tu régénères ${heal} PV (${regLbl}).`, "info");
} else {
log(`💤 Tour n°${t.tour} (DLA n°${t.dla}).`, "info");
}
@@ -1167,16 +1187,22 @@ function renderPanels() {
$("troll-title").textContent = `${RACES[t.race].emoji} ${t.name}, ${t.race} niv. ${levelFromTotalPI(t.totalPI)}`;
const te = effTroll(t);
const fmtDice = (n, faces, flat, extra = 0) => {
let s = `${n}D${faces}`;
const bonus = (flat || 0) + (extra || 0);
if (bonus) s += bonus > 0 ? ` +${bonus}` : ` ${bonus}`;
return s;
};
const pct = Math.max(0, t.pv / t.pvMax);
const hpClass = pct > 0.6 ? "high" : pct > 0.3 ? "mid" : "";
$("stats").innerHTML = `
<div><span>Tour</span><span class="stat-val">${t.tour}</span></div>
<div><span>PV</span><span class="stat-val">${t.pv} / ${t.pvMax}</span></div>
<div class="hp-bar-wrap"><div class="hp-bar ${hpClass}" style="width:${pct * 100}%"></div></div>
<div><span>Attaque</span><span class="stat-val">${te.att}D6</span></div>
<div><span>Esquive</span><span class="stat-val">${te.esq}D6</span></div>
<div><span>Dégâts</span><span class="stat-val">${te.deg}D3${te.degBonus ? "+" + te.degBonus : ""}</span></div>
<div><span>Régénération</span><span class="stat-val">${te.reg}D3</span></div>
<div><span>Attaque</span><span class="stat-val">${fmtDice(te.att, 6, te.attFlat)}</span></div>
<div><span>Esquive</span><span class="stat-val">${fmtDice(te.esq, 6, te.esqFlat)}</span></div>
<div><span>Dégâts</span><span class="stat-val">${fmtDice(te.deg, 3, te.degFlat, te.degBonus)}</span></div>
<div><span>Régénération</span><span class="stat-val">${fmtDice(te.reg, 3, te.regFlat)}</span></div>
<div><span>Armure</span><span class="stat-val">${te.armor}${t.armorDice ? "+" + t.armorDice + "D3" : ""}</span></div>
<div><span>Vue</span><span class="stat-val">${te.vue}</span></div>
<div><span>${RACES[t.race].comp.name}</span><span class="stat-val">${t.comp.pct} %</span></div>
+106 -34
View File
@@ -25,9 +25,20 @@ function corruptionYZ(x) {
return { y: potRand(16, 30), z: potRand(16, 30) };
}
/* Jet de modificateur potion : X D6/D3 → bonus/malus fixe sur les jets (pas sur le nb de dés). */
function rollMod(rollDice, count, faces, negative = false) {
const r = rollDice(count, faces);
const value = negative ? -r.total : r.total;
const sign = negative ? "" : "+";
return {
value,
line: `${sign}${count}D${faces}${value >= 0 ? "+" : ""}${value}`,
};
}
/* Fabrique un effet actif (durée en tours restants). */
function makeEffect(name, emoji, turnsLeft, mods = {}) {
return { name, emoji, turnsLeft, ...mods };
function makeEffect(name, emoji, turnsLeft, mods = {}, modLines = []) {
return { name, emoji, turnsLeft, modLines, ...mods };
}
const POTION_DEFS = {
@@ -71,13 +82,16 @@ const POTION_DEFS = {
name: "Elixir de Corruption", emoji: "☠️", color: "#4a2858",
duration: 5,
rollPower: () => potRand(3, 7),
build(_t, p) {
build(_t, p, rollDice) {
const { y, z } = corruptionYZ(p);
const attR = rollMod(rollDice, p, 3, false);
const esqR = rollMod(rollDice, p, 3, true);
const lines = [`ATT ${attR.line}`, `ESQ ${esqR.line}`, `DEG +${p}`, `REG ${p}`, `VUE ${p}`, `Armure +${p}`];
return {
log: `Elixir de Corruption : malus multiples (X=${p}, RM ${y} %, MM ${z} %) pendant 5 tours.`,
log: `Elixir de Corruption : ${lines.join(", ")} ; RM ${y} %, MM ${z} % (5 tours).`,
effects: [makeEffect("Elixir de Corruption", "☠️", 5, {
att: p, esq: -p, deg: p, reg: -p, vue: -p, armor: p, rmPct: -y, mmPct: -z,
})],
attFlat: attR.value, esqFlat: esqR.value, deg: p, reg: -p, vue: -p, armor: p, rmPct: -y, mmPct: -z,
}, lines)],
};
},
},
@@ -85,10 +99,12 @@ const POTION_DEFS = {
name: "Elixir de Fertilité", emoji: "🌱", color: "#5d9a48",
duration: 5,
rollPower: () => potRand(3, 7),
build(_t, p) {
build(_t, p, rollDice) {
const attR = rollMod(rollDice, p, 3, false);
const lines = [`ATT ${attR.line}`, `DEG +${p}`];
return {
log: `Elixir de Fertilité : ATT +${p}D6, DEG +${p} pendant 5 tours.`,
effects: [makeEffect("Elixir de Fertilité", "🌱", 5, { att: p, deg: p })],
log: `Elixir de Fertilité : ${lines.join(", ")} pendant 5 tours.`,
effects: [makeEffect("Elixir de Fertilité", "🌱", 5, { attFlat: attR.value, deg: p }, lines)],
};
},
},
@@ -96,10 +112,12 @@ const POTION_DEFS = {
name: "Elixir de Feu", emoji: "🔥", color: "#e85830",
duration: 5,
rollPower: () => potRand(3, 7),
build(_t, p) {
build(_t, p, rollDice) {
const esqR = rollMod(rollDice, p, 3, false);
const lines = [`ESQ ${esqR.line}`, `VUE +${p}`];
return {
log: `Elixir de Feu : ESQ +${p}D6, VUE +${p} pendant 5 tours.`,
effects: [makeEffect("Elixir de Feu", "🔥", 5, { esq: p, vue: p })],
log: `Elixir de Feu : ${lines.join(", ")} pendant 5 tours.`,
effects: [makeEffect("Elixir de Feu", "🔥", 5, { esqFlat: esqR.value, vue: p }, lines)],
};
},
},
@@ -168,10 +186,15 @@ const POTION_DEFS = {
name: "Rhume en Conserve", emoji: "🤧", color: "#c8d8a8",
duration: 3,
rollPower: () => potRand(1, 2),
build(_t, p) {
build(_t, p, rollDice) {
const attR = rollMod(rollDice, p, 3, true);
const esqR = rollMod(rollDice, p, 3, true);
const lines = [`ATT ${attR.line}`, `ESQ ${esqR.line}`, `DEG ${p}`, `REG ${p}`];
return {
log: `Rhume en Conserve : ATT/ESQ/DEG/REG ${p} pendant 3 tours.`,
effects: [makeEffect("Rhume en Conserve", "🤧", 3, { att: -p, esq: -p, deg: -p, reg: -p })],
log: `Rhume en Conserve : ${lines.join(", ")} pendant 3 tours.`,
effects: [makeEffect("Rhume en Conserve", "🤧", 3, {
attFlat: attR.value, esqFlat: esqR.value, deg: -p, reg: -p,
}, lines)],
};
},
},
@@ -179,10 +202,15 @@ const POTION_DEFS = {
name: "Grippe en Conserve", emoji: "🤒", color: "#d8a848",
duration: 3,
rollPower: () => potRand(3, 4),
build(_t, p) {
build(_t, p, rollDice) {
const attR = rollMod(rollDice, p, 3, true);
const esqR = rollMod(rollDice, p, 3, true);
const lines = [`ATT ${attR.line}`, `ESQ ${esqR.line}`, `DEG ${p}`, `REG ${p}`];
return {
log: `Grippe en Conserve : ATT/ESQ/DEG/REG ${p} pendant 3 tours.`,
effects: [makeEffect("Grippe en Conserve", "🤒", 3, { att: -p, esq: -p, deg: -p, reg: -p })],
log: `Grippe en Conserve : ${lines.join(", ")} pendant 3 tours.`,
effects: [makeEffect("Grippe en Conserve", "🤒", 3, {
attFlat: attR.value, esqFlat: esqR.value, deg: -p, reg: -p,
}, lines)],
};
},
},
@@ -190,10 +218,15 @@ const POTION_DEFS = {
name: "Pneumonie en Conserve", emoji: "😷", color: "#a87878",
duration: 3,
rollPower: () => 5,
build(_t, _p) {
build(_t, _p, rollDice) {
const attR = rollMod(rollDice, 5, 3, true);
const esqR = rollMod(rollDice, 5, 3, true);
const lines = [`ATT ${attR.line}`, `ESQ ${esqR.line}`, "DEG 5", "REG 5"];
return {
log: "Pneumonie en Conserve : ATT/ESQ/DEG/REG 5 pendant 3 tours.",
effects: [makeEffect("Pneumonie en Conserve", "😷", 3, { att: -5, esq: -5, deg: -5, reg: -5 })],
log: `Pneumonie en Conserve : ${lines.join(", ")} pendant 3 tours.`,
effects: [makeEffect("Pneumonie en Conserve", "😷", 3, {
attFlat: attR.value, esqFlat: esqR.value, deg: -5, reg: -5,
}, lines)],
};
},
},
@@ -259,11 +292,17 @@ const POTION_DEFS = {
duration: 3,
rollPower: () => potRand(0, 2),
build(troll, p, rollDice) {
const attR = p > 0 ? rollMod(rollDice, p, 6, true) : { value: 0, line: "ATT ±0" };
const esqR = p > 0 ? rollMod(rollDice, p, 6, true) : { value: 0, line: "ESQ ±0" };
const y = p >= 2 ? rollDice(2, 3).total : 0;
if (y > 0) troll.pv = Math.max(1, troll.pv - y);
const lines = [`ATT ${attR.line}`, `ESQ ${esqR.line}`, `VUE ${p + 1}`];
if (y) lines.push(`PV ${y} (${y} = 2D3)`);
return {
log: `PufPuff : ATT/ESQ ${p}D6, VUE ${p + 1}${y ? `, ${y} PV` : ""} pendant 3 tours.`,
effects: [makeEffect("PufPuff", "💨", 3, { att: -p, esq: -p, vue: -(p + 1) })],
log: `PufPuff : ${lines.join(", ")} pendant 3 tours.`,
effects: [makeEffect("PufPuff", "💨", 3, {
attFlat: attR.value, esqFlat: esqR.value, vue: -(p + 1),
}, lines)],
};
},
},
@@ -271,10 +310,15 @@ const POTION_DEFS = {
name: "Sang de Toh Réroh", emoji: "🩸", color: "#a83030",
duration: 4,
rollPower: () => potRand(1, 5),
build(_t, p) {
build(_t, p, rollDice) {
const attR = rollMod(rollDice, p, 6, false);
const esqR = rollMod(rollDice, p, 6, false);
const lines = [`ATT ${attR.line}`, `ESQ ${esqR.line}`, `VUE +${p}`];
return {
log: `Sang de Toh Réroh : ATT +${p}D6, ESQ +${p}D6, VUE +${p} pendant 4 tours.`,
effects: [makeEffect("Sang de Toh Réroh", "🩸", 4, { att: p, esq: p, vue: p })],
log: `Sang de Toh Réroh : ${lines.join(", ")} pendant 4 tours.`,
effects: [makeEffect("Sang de Toh Réroh", "🩸", 4, {
attFlat: attR.value, esqFlat: esqR.value, vue: p,
}, lines)],
};
},
},
@@ -317,11 +361,16 @@ const POTION_DEFS = {
duration: 3,
rollPower: () => potRand(1, 5),
build(troll, p, rollDice) {
const attR = rollMod(rollDice, p, 3, true);
const esqR = rollMod(rollDice, p, 3, true);
const heal = rollDice(p, 3).total;
troll.pv = Math.min(troll.pvMax, troll.pv + heal);
const lines = [`ATT ${attR.line}`, `ESQ ${esqR.line}`, `VUE ${p}`, `PV +${heal} (${p}D3)`];
return {
log: `Zet Crakdedand : ATT/ESQ/VUE ${p}, +${heal} PV (${p}D3) pendant 3 tours.`,
effects: [makeEffect("Zet Crakdedand", "🦷", 3, { att: -p, esq: -p, vue: -p })],
log: `Zet Crakdedand : ${lines.join(", ")} pendant 3 tours.`,
effects: [makeEffect("Zet Crakdedand", "🦷", 3, {
attFlat: attR.value, esqFlat: esqR.value, vue: -p,
}, lines)],
};
},
},
@@ -330,6 +379,7 @@ const POTION_DEFS = {
function sumPotionMods(effects) {
const m = {
att: 0, esq: 0, deg: 0, reg: 0, vue: 0, armor: 0,
attFlat: 0, esqFlat: 0, degFlat: 0, regFlat: 0,
mmPct: 0, rmPct: 0, concentrationPct: 0, dlaBonusPA: 0,
};
for (const e of effects || []) {
@@ -344,13 +394,17 @@ function effTroll(troll) {
const m = sumPotionMods(troll.potionEffects);
return {
att: Math.max(1, troll.att + m.att),
attFlat: m.attFlat,
esq: Math.max(1, troll.esq + m.esq),
esqFlat: m.esqFlat,
deg: Math.max(1, troll.deg + m.deg),
degFlat: m.degFlat,
reg: Math.max(1, troll.reg + m.reg),
regFlat: m.regFlat,
vue: Math.max(1, troll.vue + m.vue),
armor: Math.max(0, troll.armor + m.armor),
armorDice: troll.armorDice,
degBonus: troll.degBonus,
degBonus: troll.degBonus || 0,
pvMax: troll.pvMax,
mmPct: m.mmPct,
rmPct: m.rmPct,
@@ -423,16 +477,21 @@ function tickPotionTurns(troll, logFn) {
}
const EFFECT_MOD_LABELS = {
att: ["ATT", "D6"], esq: ["ESQ", "D6"], deg: ["DEG", "D3"], reg: ["REG", "D3"],
att: ["ATT", "dés"], esq: ["ESQ", "dés"], deg: ["DEG", "dés"], reg: ["REG", "dés"],
vue: ["VUE", ""], armor: ["Armure", ""],
};
function formatEffectMods(effect) {
if (effect.modLines?.length) return [...effect.modLines];
const parts = [];
if (effect.attFlat) parts.push(`ATT ${effect.attFlat > 0 ? "+" : ""}${effect.attFlat}`);
if (effect.esqFlat) parts.push(`ESQ ${effect.esqFlat > 0 ? "+" : ""}${effect.esqFlat}`);
if (effect.degFlat) parts.push(`DEG ${effect.degFlat > 0 ? "+" : ""}${effect.degFlat}`);
if (effect.regFlat) parts.push(`REG ${effect.regFlat > 0 ? "+" : ""}${effect.regFlat}`);
for (const [key, [label, suffix]] of Object.entries(EFFECT_MOD_LABELS)) {
const v = effect[key];
if (!v) continue;
parts.push(`${label} ${v > 0 ? "+" : ""}${v}${suffix}`);
parts.push(`${label} ${v > 0 ? "+" : ""}${v}${suffix ? " " + suffix : ""}`);
}
if (effect.mmPct) parts.push(`MM ${effect.mmPct > 0 ? "+" : ""}${effect.mmPct} %`);
if (effect.rmPct) parts.push(`RM ${effect.rmPct > 0 ? "+" : ""}${effect.rmPct} %`);
@@ -487,12 +546,25 @@ function renderEffectsPanel(troll) {
}
const total = sumPotionMods(troll.potionEffects);
const totalLines = formatEffectMods(total);
const totalLines = [];
if (total.attFlat) totalLines.push(`ATT ${total.attFlat > 0 ? "+" : ""}${total.attFlat}`);
if (total.esqFlat) totalLines.push(`ESQ ${total.esqFlat > 0 ? "+" : ""}${total.esqFlat}`);
if (total.degFlat) totalLines.push(`DEG ${total.degFlat > 0 ? "+" : ""}${total.degFlat}`);
if (total.regFlat) totalLines.push(`REG ${total.regFlat > 0 ? "+" : ""}${total.regFlat}`);
for (const [key, [label, suffix]] of Object.entries(EFFECT_MOD_LABELS)) {
const v = total[key];
if (!v) continue;
totalLines.push(`${label} ${v > 0 ? "+" : ""}${v}${suffix ? " " + suffix : ""}`);
}
if (total.mmPct) totalLines.push(`MM ${total.mmPct > 0 ? "+" : ""}${total.mmPct} %`);
if (total.rmPct) totalLines.push(`RM ${total.rmPct > 0 ? "+" : ""}${total.rmPct} %`);
if (total.concentrationPct) totalLines.push(`Concentration ${total.concentrationPct > 0 ? "+" : ""}${total.concentrationPct} %`);
if (total.dlaBonusPA) totalLines.push(`DLA +${total.dlaBonusPA} PA/tour`);
if (troll.blockCamoTurns > 0) totalLines.push("Camouflage bloqué");
let html = "";
if (totalLines.length) {
html += `<div class="fx-total"><div class="fx-total-title">Total des modificateurs</div>`;
html += `<div class="fx-total"><div class="fx-total-title">Total des modificateurs (jets inclus)</div>`;
html += totalLines.map(m => `<span class="fx-mod">${m}</span>`).join("");
html += "</div>";
}
@@ -519,6 +591,6 @@ if (typeof module !== "undefined" && module.exports) {
POTION_IDS, POTION_DEFS, sumPotionMods, effTroll, formatPotionItem,
makeRandomPotion, makePotionItem, drinkPotion, tickPotionTurns,
describeActiveEffects, renderEffectsPanel, countActiveEffects, formatEffectMods,
talentPctWithPotions, corruptionYZ,
rollMod, talentPctWithPotions, corruptionYZ,
};
}
+19
View File
@@ -166,12 +166,31 @@ assert.strictEqual(p.corruptionYZ(3).y, 0);
p.drinkPotion(troll, item, g.rollDice, () => {});
assert.strictEqual(troll.potionEffects.length, 1);
assert.strictEqual(p.effTroll(troll).deg, 8);
assert.strictEqual(p.effTroll(troll).attFlat, 0, "Bonne Bouffe ne modifie pas ATT");
const fixedRoll = () => ({ total: 12, rolls: [4, 4, 4] });
const t2 = { att: 3, esq: 3, deg: 3, reg: 1, vue: 3, armor: 0, armorDice: 0, degBonus: 0, pvMax: 30, pv: 20, potionEffects: [], blockCamoTurns: 0, tour: 1 };
p.drinkPotion(t2, p.makePotionItem("fertilite", 5), fixedRoll, () => {});
assert.strictEqual(p.effTroll(t2).attFlat, 12, "Fertilité +5D3 → +12 sur le jet");
assert.strictEqual(p.effTroll(t2).att, 3);
const t3 = { att: 3, esq: 3, deg: 3, reg: 1, vue: 3, armor: 0, armorDice: 0, degBonus: 0, pvMax: 30, pv: 20, potionEffects: [], blockCamoTurns: 0, tour: 1 };
p.drinkPotion(t3, p.makePotionItem("sangToh", 2), fixedRoll, () => {});
assert.strictEqual(p.effTroll(t3).attFlat, 12, "2D6 fixé → +12 sur le jet d'attaque");
assert.strictEqual(p.effTroll(t3).esqFlat, 12);
assert.strictEqual(p.effTroll(t3).att, 3);
p.tickPotionTurns(troll, () => {});
assert.strictEqual(troll.tour, 2);
troll.potionEffects[0].turnsLeft = 0;
p.tickPotionTurns(troll, () => {});
assert.strictEqual(troll.potionEffects.length, 0);
assert.strictEqual(p.effTroll(troll).deg, 3);
assert.strictEqual(p.effTroll(troll).attFlat, 0);
}
// Jet d'attaque : bonus potion ajouté au total des D6
{
const r = g.resolveAttack({ att: 3, attFlat: 12, deg: 2, degBonus: 0 }, { esq: 1 });
assert.strictEqual(r.attFlat, 12);
assert(r.attRoll > 12, "attRoll = somme des D6 + bonus potion");
assert(r.attRoll <= 12 + 18, "attRoll borné (3D6 max 18 + 12)");
}
const sword = g.itemFromSpec({ x: 1, y: 1, kind: "weapon", idx: 2 });
assert.strictEqual(sword.kind, "gear");