Багфикс: квест «забросы», HUD монет, нагрузка после босс-обрыва

- questHit('casts') вызывался без condFn: TypeError на каждом забросе
  с активным квестом — квест не рос, persist()/checkAchievements()
  в doCast() пропускались (счётчик забросов терялся при перезагрузке)
- награда квеста и утешение near-miss не обновляли счётчик монет в HUD
- фазовый обрыв босса теперь сбрасывает tension: «обрыв фазы без штрафа»
  больше не превращается в настоящий обрыв при зажатом пальце
- регресс-проверки в tests/test.js (секция 9l), итого 58
This commit is contained in:
2026-09-01 18:44:24 +03:00
parent 5c95309ce0
commit 5f557fca64
2 changed files with 94 additions and 2 deletions
+4 -2
View File
@@ -412,6 +412,7 @@ function questHit(tpl,condFn,delta){
if(q.prog>=q.goal){
q.prog=q.goal; q.done=true;
save.coins+=q.reward; save.questsDoneTotal++;
updateHUD(); // награда может прийти на забросе (вне catchFish) — счётчик монет обновить сразу
sfx('coin');
toast('Квест выполнен: '+questText(q)+' +'+q.reward+' 🪙');
checkAchievements(); // quests_5
@@ -668,7 +669,7 @@ function doCast(){
castT=0; state='CAST'; sfx('cast');
save.casts++;
newDayCheck();
questHit('casts');
questHit('casts',()=>true); // condFn обязателен: без него questHit падает с TypeError
checkAchievements();
persist();
}
@@ -722,7 +723,7 @@ function updateReel(dt){
stateBoss=false; bossPhase=0; // M4: босс тоже может уйти — босс-бой окончен
const nm=Math.floor(reel.w*sp.price*0.1);
if(nm>0){
save.coins+=nm; persist();
save.coins+=nm; persist(); updateHUD();
toast('Сбежала! '+sp.emoji+' '+sp.name+' '+reel.w.toFixed(1)+' кг · +'+nm+' 🪙');
}else{
toast('Сбежала! '+sp.emoji+' '+sp.name+' '+reel.w.toFixed(1)+' кг');
@@ -847,6 +848,7 @@ function updateGame(dt){
if(stateBoss && bossPhase<3){
// M4: босс-обрыв — не штраф: автоматом следующая (глубже) фаза, без ввода
reel.d=(bossPhase===1?0.6:1.0)*reel.d0; // фаза2: d=0.6·d0, фаза3: d=1.0·d0
reel.tension=0; // фазовый обрыв сбрасывает нагрузку, иначе «беспошлинный» обрыв ведёт к настоящему
bossPhase++;
state='REEL';
}else{
+90
View File
@@ -645,6 +645,96 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
pickSpecies = window.__m4Pick;
});
// --- 9l. Регрессии багфиксов спринта 3 ---
// (1) квест «Сделай N забросов»: questHit('casts') вызывался без condFn и падал
// TypeError на каждом забросе — квест не рос, а persist()/checkAchievements()
// в doCast() пропускались (счётчик забросов терялся при перезагрузке)
await page.evaluate(() => {
save.quests = { date: todayStr(), list: [{ tpl: 'casts', param: 0, goal: 1, prog: 0, done: false, reward: 60 }] };
save.streak = { last: todayStr(), days: 1, shieldWeek: null }; // стрик «сегодня» — не искажает монеты
save.coins = 100;
state = 'IDLE'; reel = null; stateBoss = false; bossPhase = 0; biteFish = null;
});
const castsQBefore = await page.evaluate(() => ({ casts: save.casts }));
const peBeforeCastsQ = pageErrors.length;
await page.mouse.move(195, 600);
await page.mouse.down(); await sleep(450); await page.mouse.up();
await waitState(page, 'WAIT');
const castsQ = await page.evaluate(() => ({
prog: save.quests.list[0].prog,
done: save.quests.list[0].done,
coins: save.coins,
casts: save.casts,
lsCasts: JSON.parse(localStorage.getItem('fishing-save-v1')).casts,
hud: document.getElementById('coins').textContent,
}));
ok('багфикс квест «забросы»: квест растёт и выполняется на забросе (+60 🪙), без TypeError, заброс записан в сейв',
castsQ.prog === 1 && castsQ.done === true && castsQ.coins === 160
&& castsQ.casts === castsQBefore.casts + 1 && castsQ.lsCasts === castsQ.casts
&& pageErrors.length === peBeforeCastsQ,
JSON.stringify({ castsQ, pe: pageErrors.length - peBeforeCastsQ }));
// (2) награда квеста на забросе — вне catchFish: HUD монет должен обновиться сразу
ok('багфикс HUD квеста: счётчик монет обновлён сразу после выполнения квеста на забросе',
castsQ.hud === '🪙 ' + castsQ.coins, castsQ.hud);
// (2b) near-miss: утешение +10% цены тоже начисляется вне catchFish — HUD актуален
await page.mouse.move(195, 600);
await page.mouse.down(); await sleep(450); await page.mouse.up();
await waitState(page, 'WAIT');
await page.evaluate(() => { biteTimer = 0.05; });
await waitState(page, 'BITE', 4000);
await page.mouse.down(); await page.mouse.up();
await waitState(page, 'REEL');
await page.evaluate(() => {
reel.w = 5; // фиксируем вес — утешение детерминированно и больше нуля
reel.phase = 'pull'; reel.phaseT = 9999;
reel.d = reel.d0 * 1.38;
});
await waitState(page, 'WAIT', 15000);
const nmHud = await page.evaluate(() => ({
coins: save.coins,
hud: document.getElementById('coins').textContent,
}));
ok('багфикс HUD near-miss: после «Сбежала! +N 🪙» счётчик монет актуален',
nmHud.hud === '🪙 ' + nmHud.coins, JSON.stringify(nmHud));
// (3) босс-бой: фазовый обрыв должен сбрасывать накопленную нагрузку —
// иначе «обрыв фазы без штрафа» с tension≈0.9·line тут же рвал шнур по-настоящему
await page.evaluate(() => {
forceBoss = true; SHINY_CHANCE = 0;
window.__pick2 = pickSpecies; pickSpecies = () => SPECIES.find(s => s.id === 'sturge');
state = 'IDLE'; reel = null; stateBoss = false; bossPhase = 0; biteFish = null;
save.quests = { date: todayStr(), list: [] };
});
await page.mouse.move(195, 600);
await page.mouse.down(); await sleep(450); await page.mouse.up();
await waitState(page, 'WAIT');
await page.evaluate(() => { biteTimer = 0.05; });
await waitState(page, 'BITE', 4000);
await page.mouse.down(); await page.mouse.up();
await waitState(page, 'REEL');
await page.evaluate(() => {
reel.d = 2; reel.phase = 'calm'; reel.phaseT = 9999;
reel.tension = reel.line * 0.9; // рыба тянула почти до предела
});
await page.mouse.down();
await waitState(page, 'SNAP', 5000);
// палец НЕ отпускаем: после авто-возврата в REEL нагрузка должна стартовать с нуля
const bossPh = await page.evaluate(() => new Promise(res => {
const iv = setInterval(() => {
if (state === 'REEL') { clearInterval(iv); res({ st: state, ph: bossPhase, boss: stateBoss, tension: reel.tension, line: reel.line }); }
else if (state === 'SNAP' && !stateBoss) { clearInterval(iv); res({ st: 'REAL SNAP' }); }
}, 10);
}));
await page.mouse.up();
ok('багфикс босс-обрыв фазы: после авто-возврата в REEL нагрузка сброшена (tension≈0), бой продолжается',
bossPh.st === 'REEL' && bossPh.boss === true && bossPh.ph === 2 && bossPh.tension < bossPh.line * 0.1,
JSON.stringify(bossPh));
await page.evaluate(() => {
forceBoss = false; SHINY_CHANCE = 0.03; pickSpecies = window.__pick2;
state = 'IDLE'; reel = null; stateBoss = false; bossPhase = 0; biteFish = null;
});
// --- 10. Ошибки консоли ---
ok('нет ошибок JS (pageerror)', pageErrors.length === 0, pageErrors.join(' | '));
ok('нет ошибок консоли', consoleErrors.length === 0, consoleErrors.join(' | '));