Физика: спринт 4 P0 — stamina рыбы, drag катушки, честный баланс сил, пустые клёвы + 7 автотестов (68/68)
@@ -192,6 +192,8 @@ button{font-family:inherit;cursor:pointer}
|
||||
<p style="margin-top:8px">У каждой рыбы свой <b>вес</b>, продажа — по цене за кг.<br>
|
||||
У удочек есть <b>предел веса</b>: перевесишь — шнур лопнет! <br>
|
||||
Лучшие снасти = больше шансов на трофей 🏆</p>
|
||||
<p style="margin-top:8px">Не каждый заброс — с клёвом: если молчит — подожди.<br>
|
||||
У удилищ есть <b>тормоз (drag)</b>: рывок сильнее — леска скользит!</p>
|
||||
<button id="btnPlay" class="big">Играть</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -263,11 +265,14 @@ let SHINY_CHANCE=0.03, SHINY_MULT=3;
|
||||
/* M4: босс-рыба (Осётр-босс) — тест-форс появления (let — автотест, как SHINY_CHANCE) */
|
||||
let forceBoss=false;
|
||||
|
||||
/* Спринт 4: пустые клёвы — тест-форс клёва (let — автотест, паттерн forceBoss) */
|
||||
let forceBite=false;
|
||||
|
||||
const RODS=[
|
||||
{id:0, name:'Дедова удочка', cost:0, maxW:3, line:100, reel:1.0, size:0.00, desc:'До 3 кг · Нагрузка 100 · Подсечка ×1.0'},
|
||||
{id:1, name:'Стеклопластик', cost:250, maxW:8, line:140, reel:1.3, size:0.08, desc:'До 8 кг · Нагрузка 140 · Подсечка ×1.3'},
|
||||
{id:2, name:'Карбоновая', cost:700, maxW:18, line:190, reel:1.7, size:0.16, desc:'До 18 кг · Нагрузка 190 · Подсечка ×1.7'},
|
||||
{id:3, name:'Титан', cost:1600, maxW:40, line:260, reel:2.3, size:0.28, desc:'До 40 кг · Нагрузка 260 · Подсечка ×2.3'},
|
||||
{id:0, name:'Дедова удочка', cost:0, maxW:3, line:100, drag:34, reel:1.0, size:0.00, desc:'До 3 кг · Нагрузка 100 · Drag 34 · Подсечка ×1.0'},
|
||||
{id:1, name:'Стеклопластик', cost:250, maxW:8, line:140, drag:47, reel:1.3, size:0.08, desc:'До 8 кг · Нагрузка 140 · Drag 47 · Подсечка ×1.3'},
|
||||
{id:2, name:'Карбоновая', cost:700, maxW:18, line:190, drag:63, reel:1.7, size:0.16, desc:'До 18 кг · Нагрузка 190 · Drag 63 · Подсечка ×1.7'},
|
||||
{id:3, name:'Титан', cost:1600, maxW:40, line:260, drag:87, reel:2.3, size:0.28, desc:'До 40 кг · Нагрузка 260 · Drag 87 · Подсечка ×2.3'},
|
||||
];
|
||||
const LURES=[
|
||||
{id:0, name:'Простой крючок', cost:0, cast:1.00, bite:1.0, rare:0.0, size:0.00, desc:'Надёжно, но рыба не в восторге'},
|
||||
@@ -694,6 +699,13 @@ function biteWindow(sp){
|
||||
if(sp.boss) return rand(0.25,0.5); // M4: босс клюёт реже — окно подсечки короче
|
||||
return Math.max(0.35, 0.8 - sp.rarity*0.08 + save.rod*0.04 + achBonus().windowS);
|
||||
}
|
||||
/* Спринт 4: шанс клёва на таймер — не каждый заброс даёт рыбу (дальше = чаще, ночь тише) */
|
||||
function biteChance(){
|
||||
const dist=Math.min(1,(castTo.x-(boatX+30))/Math.max(1,W-boatX-70));
|
||||
let p=0.65+0.25*dist;
|
||||
if(dayPhase==='night') p*=0.85;
|
||||
return p;
|
||||
}
|
||||
/* Ролл веса рыбы: сдвиг в крупную сторону зависит от удочки и приманки */
|
||||
function rollWeight(sp){
|
||||
const B=Math.min(0.95, rod().size + lure().size);
|
||||
@@ -727,7 +739,14 @@ function hook(){
|
||||
const aggr=Math.min(1, 0.35*str + 0.75*Math.min(1.25,ov)/1.25); // «злость» рыбы
|
||||
const d0=40+3.5*w; // чем крупнее, тем дальше от лодки
|
||||
const line=rod().line+achBonus().line; // прочность с бонусом достижений
|
||||
reel={d:d0, d0:d0, tension:0, escape:0, holding:false, phase:'calm', phaseT:rand(0.8,1.5), sp:sp, w:w, ov:ov, aggr:aggr, tick:0, line:line, boss:!!sp.boss};
|
||||
// Спринт 4: выносливость — крупная рыба выносливее, босс свежий
|
||||
const st0=sp.boss?1:0.65+0.35*Math.min(1,w/sp.wMax);
|
||||
reel={d:d0, d0:d0, tension:0, escape:0, holding:false, phase:'calm', phaseT:rand(0.8,1.5), sp:sp, w:w, ov:ov, aggr:aggr, tick:0, line:line, boss:!!sp.boss, st:st0, tired:false};
|
||||
}
|
||||
/* Спринт 4: сила рыбы (перетягивание с drag) — глобальная, автотесты вызывают */
|
||||
function fishForce(r,ag,pull){
|
||||
const base=pull?(16+34*r.st)*(0.5+0.9*ag)*(0.7+0.5*Math.min(1.5,r.ov)):5+7*ag;
|
||||
return r.tired?0.45*base:base;
|
||||
}
|
||||
function doCast(){
|
||||
const p=Math.max(0.12,power);
|
||||
@@ -751,23 +770,32 @@ function updateReel(dt){
|
||||
reel.phaseT-=dt;
|
||||
if(reel.phaseT<=0){
|
||||
if(reel.phase==='calm'){
|
||||
reel.phase='pull'; reel.phaseT=0.3+ag*rand(0.4,0.9);
|
||||
// Спринт 4: рывок длиннее у свежей рыбы, короче у уставшей
|
||||
reel.phase='pull'; reel.phaseT=(0.8+2.2*reel.st)*(0.6+0.7*ag)*rand(0.8,1.25);
|
||||
sfx('pull'); vib(40);
|
||||
}else{
|
||||
reel.phase='calm'; reel.phaseT=Math.max(0.35,rand(0.7,1.7)-0.3*ag); vib(15);
|
||||
// Спринт 4: пауза растёт по мере усталости
|
||||
reel.phase='calm'; reel.phaseT=Math.max(0.35,(1.3-0.8*reel.st)*rand(0.8,1.3)); vib(15);
|
||||
}
|
||||
}
|
||||
const pulling=reel.phase==='pull';
|
||||
// Спринт 4: выносливость — расход по фазам и удержанию (свободный бег тоже утомляет)
|
||||
const drain=(pulling?0.05+0.16*ag:0.008)+(reel.holding?0.035:0.004)+(pulling&&!reel.holding?0.05:0);
|
||||
reel.st=Math.max(0,reel.st-drain*dt);
|
||||
if(reel.st<=0.12) reel.tired=true; // устала: рывки слабее, втягивается быстрее
|
||||
const F=fishForce(reel,ag,pulling);
|
||||
if(reel.holding){
|
||||
reel.d-=30*r.reel*dt;
|
||||
// перегруз (ov>1) — нагрузка растёт кратно: шнур лопнет очень быстро
|
||||
reel.tension+=(pulling?(14+30*ag)*(0.6+0.9*ov):9)*dt;
|
||||
// Спринт 4: перетягивание через drag — рывок сильнее тормоза: леска скользит
|
||||
const over=F-r.drag;
|
||||
reel.tension+=(8+Math.max(0,over)*1.1+14*ag*(pulling?1:0.35))*dt;
|
||||
if(over>0) reel.d+=over*0.22*dt;
|
||||
else reel.d-=(22+8*r.reel)*(1+0.35*(1-reel.st))*dt; // усталую втягивают быстрее
|
||||
reel.tick-=dt;
|
||||
if(reel.tick<=0){ sfx('tick'); reel.tick=0.12; }
|
||||
if(Math.random()<0.3) splash(castTo.x,waterY,1,40);
|
||||
}else{
|
||||
reel.tension-=38*dt;
|
||||
if(pulling) reel.d+=(3+4*ag)*(0.5+0.4*ov)*dt; // крупная/тяжело несёт шнур
|
||||
if(pulling) reel.d+=F*0.4*dt; // свободный бег: рыба реально отходит
|
||||
}
|
||||
reel.tension=Math.max(0,reel.tension);
|
||||
// escape: рыба ускользает если долго не тянешь
|
||||
@@ -894,8 +922,11 @@ function updateGame(dt){
|
||||
if(dayPhase==='night'&&!save.lures.includes(4)){
|
||||
// M3: ночью без фонарика клёва нет — таймер тикает вхолостую
|
||||
if(!nightNoLureToast){ toast('Ночью нужен фонарик'); nightNoLureToast=true; }
|
||||
}else{
|
||||
}else if(forceBite||Math.random()<biteChance()){
|
||||
startBite();
|
||||
}else{
|
||||
// Спринт 4: пустой клёв — поплавок молчит, попробуем ещё раз
|
||||
biteTimer=rand(2.5,5)/lure().bite;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1283,16 +1314,17 @@ function drawLineAndBobber(){
|
||||
function drawHookedFish(){
|
||||
if(state!=='REEL'||!reel) return;
|
||||
const sp=reel.sp;
|
||||
const x=castTo.x+Math.sin(time*7)*4;
|
||||
const y=waterY+26+Math.sin(time*5)*3;
|
||||
const dir=Math.sin(time*2.5)>0?1:-1;
|
||||
const k=reel.tired?0.35:1; // Спринт 4: уставшая рыба вялая — медленнее и амплитуда ниже
|
||||
const x=castTo.x+Math.sin(time*7*k)*4*k;
|
||||
const y=waterY+26+Math.sin(time*5*k)*3*k;
|
||||
const dir=Math.sin(time*2.5*k)>0?1:-1;
|
||||
const fs=8+(reel.w/sp.wMax)*10+sp.rarity;
|
||||
if(reel.boss){ // босс: золотая аура (визуал)
|
||||
const gg=ctx.createRadialGradient(x,y,4,x,y,fs*2.6);
|
||||
gg.addColorStop(0,'rgba(255,215,94,0.35)'); gg.addColorStop(1,'rgba(255,215,94,0)');
|
||||
ctx.fillStyle=gg; ctx.fillRect(x-fs*2.6,y-fs*2.6,fs*5.2,fs*5.2);
|
||||
}
|
||||
drawFishShape(x,y,reel.boss?fs*1.5:fs,dir,sp.hue,0.75,Math.sin(time*9)*0.3); // силуэт крупнее у тяжёлых, босс ×1.5
|
||||
drawFishShape(x,y,reel.boss?fs*1.5:fs,dir,sp.hue,0.75,Math.sin(time*9*k)*0.3*k); // силуэт крупнее у тяжёлых, босс ×1.5
|
||||
}
|
||||
function drawParticles(){
|
||||
for(const p of particles){
|
||||
@@ -1341,8 +1373,14 @@ function drawMeters(){
|
||||
}
|
||||
ctx.strokeStyle='rgba(255,255,255,0.6)'; ctx.lineWidth=1.5;
|
||||
ctx.beginPath(); ctx.moveTo(bx-3,by); ctx.lineTo(bx+bw+3,by); ctx.stroke();
|
||||
// Спринт 4: черта drag — выше неё при удержании леска скользит
|
||||
const dgy=by+bh*(1-Math.min(1,r.drag/reel.line));
|
||||
ctx.strokeStyle='rgba(255,255,255,0.9)'; ctx.lineWidth=2;
|
||||
ctx.beginPath(); ctx.moveTo(bx-2,dgy); ctx.lineTo(bx+bw+2,dgy); ctx.stroke();
|
||||
const Fm=fishForce(reel,reel.aggr,pulling);
|
||||
const slipping=reel.holding&&Fm-r.drag>0;
|
||||
ctx.font='16px system-ui'; ctx.textAlign='center';
|
||||
ctx.fillText(pulling?'⚡':'💪',bx+bw/2,by-8);
|
||||
ctx.fillText(slipping?'🌀':pulling?'⚡':(reel.holding?'💪':'·'),bx+bw/2,by-8);
|
||||
// --- дистанция (снизу) ---
|
||||
const dw=W-96, dx=22, dy=H-52, dh=13;
|
||||
ctx.fillStyle='rgba(0,0,0,0.35)'; rr(dx,dy,dw,dh,7); ctx.fill();
|
||||
@@ -1366,6 +1404,17 @@ function drawMeters(){
|
||||
ctx.fillText('🐟',fx,dy-6+Math.sin(time*10)*1.5);
|
||||
ctx.font='18px system-ui';
|
||||
ctx.fillText('⛵',dx+4,dy+dh+18);
|
||||
// Спринт 4: выносливость — тонкая полоска под дистанцией (затухает к нулю)
|
||||
const sdh=5, sdy=dy+dh+3;
|
||||
ctx.fillStyle='rgba(0,0,0,0.3)'; rr(dx,sdy,dw,sdh,2.5); ctx.fill();
|
||||
if(reel.st>0.01){
|
||||
ctx.fillStyle=reel.tired?'#7d5ba6':'#b07cc6';
|
||||
rr(dx+1.5,sdy+1.5,Math.max(1.5,(dw-3)*reel.st),sdh-3,2); ctx.fill();
|
||||
}
|
||||
if(reel.tired){
|
||||
ctx.font='13px system-ui'; ctx.textAlign='left';
|
||||
ctx.fillText('💤',dx+dw+6,sdy+sdh-1);
|
||||
}
|
||||
// статус
|
||||
ctx.font='700 15px system-ui'; ctx.textAlign='center';
|
||||
if(pulling){
|
||||
@@ -1373,6 +1422,11 @@ function drawMeters(){
|
||||
ctx.shadowColor='rgba(255,60,40,'+(0.5+0.4*Math.sin(time*10)).toFixed(3)+')'; ctx.shadowBlur=8;
|
||||
ctx.fillText('⚡ РЫБА ТЯНЕТ — ОТПУСТИ! ⚡',W/2,dy-30);
|
||||
ctx.shadowBlur=0;
|
||||
}else if(reel.tired){
|
||||
ctx.fillStyle='#c9a0f5';
|
||||
ctx.shadowColor='rgba(120,60,180,0.6)'; ctx.shadowBlur=6;
|
||||
ctx.fillText('💤 Рыба устала — втягивай!',W/2,dy-30);
|
||||
ctx.shadowBlur=0;
|
||||
}else{
|
||||
ctx.fillStyle='#7ef0a8';
|
||||
ctx.shadowColor='rgba(0,0,0,0.6)'; ctx.shadowBlur=4;
|
||||
|
||||
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 354 KiB |
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 318 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 432 KiB After Width: | Height: | Size: 437 KiB |
|
Before Width: | Height: | Size: 380 KiB After Width: | Height: | Size: 448 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 277 KiB |
|
Before Width: | Height: | Size: 493 KiB After Width: | Height: | Size: 503 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 131 KiB |
@@ -160,7 +160,7 @@ async function castToWait() {
|
||||
// (~0.3-0.5 c) длиннее окна подсечки (0.35-0.8 c), а на ПЕРВОПЛАНАРНОЙ вкладке
|
||||
// игра идёт в реальном времени.
|
||||
async function forceBite() {
|
||||
await evalPage('biteTimer = 0.05; "ok"');
|
||||
await evalPage('forceBite = true; biteTimer = 0.05; "ok"');
|
||||
const t0 = Date.now();
|
||||
let last = '';
|
||||
while (Date.now() - t0 < 30000) {
|
||||
|
||||
@@ -84,7 +84,7 @@ async function snap(page, name) {
|
||||
await waitState(page, 'WAIT');
|
||||
await waitPaint(page);
|
||||
await snap(page, 'wait');
|
||||
await page.evaluate(() => { biteTimer = 0.05; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await waitPaint(page);
|
||||
await snap(page, 'bite');
|
||||
@@ -124,7 +124,7 @@ async function snap(page, name) {
|
||||
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 page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
|
||||
@@ -57,7 +57,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
await page.screenshot({ path: 'shot-wait.png' });
|
||||
|
||||
// --- 3. Клёв и подсечка ---
|
||||
await page.evaluate(() => { biteTimer = 0.05; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
ok('клев: state=BITE', true);
|
||||
await page.screenshot({ path: 'shot-bite.png' });
|
||||
@@ -91,7 +91,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
// --- 4b. Перегруз: тяжёлая рыба на слабой удочке рвёт шнур быстро ---
|
||||
await page.mouse.down(); await sleep(450); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -100,16 +100,16 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
reel.d = reel.d0 = 150;
|
||||
});
|
||||
await page.mouse.down();
|
||||
await waitState(page, 'SNAP', 4000);
|
||||
await waitState(page, 'SNAP', 6000);
|
||||
await page.mouse.up();
|
||||
await waitState(page, 'IDLE', 3000);
|
||||
await waitState(page, 'IDLE', 6000); // новая физика: худший кейс (calm→pull) ≈ 2.6 с + SNAP 1 с
|
||||
ok('перегруз: 12 кг на удочке 3 кг = быстрый обрыв', true);
|
||||
|
||||
// --- 4c. Рекорд: улов ниже лучшего — бейджа нет ---
|
||||
await page.evaluate(() => { for (const s of SPECIES) save.caught[s.id] = { n: 1, best: 9999 }; });
|
||||
await page.mouse.down(); await sleep(450); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -125,7 +125,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
// --- 5. Бросок 2: обрыв линии ---
|
||||
await page.mouse.down(); await sleep(450); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -139,7 +139,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
// --- 6. Бросок 3: срыв рыбы (не тянем) ---
|
||||
await page.mouse.down(); await sleep(600); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -220,7 +220,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
const coinsBeforeShiny = await page.evaluate(() => save.coins);
|
||||
await page.mouse.down(); await sleep(450); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; SHINY_CHANCE = 1; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; SHINY_CHANCE = 1; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -280,7 +280,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
const coinsBeforeNm = await page.evaluate(() => save.coins);
|
||||
await page.mouse.down(); await sleep(600); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -322,7 +322,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
const m2qBefore = await page.evaluate(() => save.coins);
|
||||
await page.mouse.down(); await sleep(450); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -410,7 +410,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
});
|
||||
await page.mouse.down(); await sleep(450); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; });
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -498,7 +498,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
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 page.evaluate(() => { forceBite=true; biteTimer=0.05; });
|
||||
await sleep(600);
|
||||
const m3nb = await page.evaluate(() => ({
|
||||
st:state, bf:!!biteFish, bt:biteTimer,
|
||||
@@ -515,7 +515,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
await page.evaluate(() => { setDayTime(0.8); save.lures=[0,4]; });
|
||||
await page.mouse.down(); await sleep(450); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer=0.05; });
|
||||
await page.evaluate(() => { forceBite=true; biteTimer=0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
ok('M3 ночь с фонариком (save.lures включает 4): клёв работает (BITE)', true);
|
||||
|
||||
@@ -570,7 +570,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
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 page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -681,7 +681,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
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 page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -709,7 +709,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
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 page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
@@ -742,7 +742,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
await sleep(500);
|
||||
await page.mouse.up(); // бросок
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; }); // ускоряем клёв
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; }); // ускоряем клёв
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up(); // подсечка
|
||||
await waitState(page, 'REEL');
|
||||
@@ -778,7 +778,96 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
state = 'IDLE'; reel = null; biteFish = null;
|
||||
});
|
||||
|
||||
// --- 10. Ошибки консоли ---
|
||||
// --- 10. Спринт 4: физика (stamina, drag, пустые клёвы) ---
|
||||
// Выводим рыбу на крючок и держим один бой для тестов (1)-(4),
|
||||
// между тестами вручную возвращаем reel в безопасное состояние (d посередине,
|
||||
// tension=0, line велик — чтобы бой не закончился ловлей/обрывом в окне замера).
|
||||
await page.mouse.move(195, 600);
|
||||
await page.mouse.down(); await sleep(450); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { forceBite = true; biteTimer = 0.05; });
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up();
|
||||
await waitState(page, 'REEL');
|
||||
|
||||
// (1) stamina: reel.st есть и убывает в бою
|
||||
const st0 = await page.evaluate(() => {
|
||||
reel.d0 = 200; reel.d = 100; reel.tension = 0; reel.line = 500;
|
||||
reel.phase = 'calm'; reel.phaseT = 9999; reel.holding = false;
|
||||
return reel.st;
|
||||
});
|
||||
ok('stamina: reel.st инициализируется (>0)', st0 > 0, 'st0=' + st0.toFixed(3));
|
||||
await page.mouse.down(); // holding=true — удерживаем
|
||||
await sleep(2000);
|
||||
const st1 = await page.evaluate(() => reel.st);
|
||||
ok('stamina: reel.st убывает в бою (st<st0 после 2 с удержания)', st1 < st0,
|
||||
'st0=' + st0.toFixed(3) + ' st1=' + st1.toFixed(3));
|
||||
|
||||
// (2) stamina: усталая рыба слабее — fishForce глобальна, tired лачится при st<=0.12
|
||||
const f = await page.evaluate(() => {
|
||||
const r = { st: 1, tired: false, ov: 1 };
|
||||
const F1 = fishForce(r, 0.5, true);
|
||||
r.st = 0.05;
|
||||
const F2 = fishForce(r, 0.5, true);
|
||||
return { F1, F2 };
|
||||
});
|
||||
ok('stamina: слабая/уставшая рыба тянет слабее (F(st=0.05) < 0.6·F(st=1))',
|
||||
f.F2 < f.F1 * 0.6, 'F1=' + f.F1.toFixed(1) + ' F2=' + f.F2.toFixed(1));
|
||||
await page.evaluate(() => {
|
||||
reel.d0 = 200; reel.d = 100; reel.tension = 0; reel.line = 500;
|
||||
reel.phase = 'calm'; reel.phaseT = 9999; reel.st = 0.1; reel.tired = false;
|
||||
});
|
||||
await sleep(200); // кадры updateReel зафиксируют tired при st<=0.12
|
||||
const tiredFlag = await page.evaluate(() => reel.tired);
|
||||
ok('stamina: при st=0.1 рыба устала (reel.tired===true)', tiredFlag === true,
|
||||
'tired=' + tiredFlag);
|
||||
|
||||
// (3) drag: рывок сильнее drag — леска скользит (d уходит, tension за чертой)
|
||||
await page.evaluate(() => {
|
||||
reel.w = 12; reel.ov = 4; reel.aggr = 1; reel.st = 1; reel.tired = false;
|
||||
reel.phase = 'pull'; reel.phaseT = 9999; reel.tension = 0; reel.line = 500;
|
||||
reel.d0 = 100; reel.d = 100;
|
||||
});
|
||||
await sleep(1000); // палец всё ещё нажат (holding=true)
|
||||
const slip = await page.evaluate(() => ({ d: reel.d, d0: reel.d0, tension: reel.tension, drag: rod().drag }));
|
||||
ok('drag: рывок сильнее drag — леска скользит (d растёт, tension>drag)',
|
||||
slip.d > slip.d0 && slip.tension > slip.drag,
|
||||
JSON.stringify(slip));
|
||||
|
||||
// (4) drag: рывок слабее drag — спокойно втягиваем (d падает, tension под чертой)
|
||||
await page.evaluate(() => {
|
||||
reel.w = 0.3; reel.ov = 0.1; reel.aggr = 0.2; reel.st = 0.9; reel.tired = false;
|
||||
reel.phase = 'pull'; reel.phaseT = 9999; reel.tension = 0; reel.line = 500;
|
||||
reel.d0 = 100; reel.d = 100;
|
||||
});
|
||||
await sleep(1000);
|
||||
const reelIn = await page.evaluate(() => ({ d: reel.d, d0: reel.d0, tension: reel.tension, drag: rod().drag }));
|
||||
ok('drag: рывок слабее drag — втягивание (d падает, tension<drag)',
|
||||
reelIn.d < reelIn.d0 && reelIn.tension < reelIn.drag,
|
||||
JSON.stringify(reelIn));
|
||||
await page.mouse.up(); // отпустили палец
|
||||
await page.evaluate(() => { state = 'IDLE'; reel = null; biteFish = null; forceBite = false; });
|
||||
|
||||
// (5) пустой клёв: без forceBite и с Math.random=0.99+ клёва нет
|
||||
await page.evaluate(() => { setDayTime(0.3); state = 'IDLE'; reel = null; biteFish = null; forceBite = false; });
|
||||
await page.mouse.move(195, 600);
|
||||
await page.mouse.down(); await sleep(450); await page.mouse.up();
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => {
|
||||
window.__mr = Math.random;
|
||||
Math.random = () => 0.999; // выше любого biteChance (макс ~0.9 днём)
|
||||
biteTimer = 0.05;
|
||||
forceBite = false;
|
||||
});
|
||||
await sleep(700);
|
||||
const empty = await page.evaluate(() => {
|
||||
Math.random = window.__mr;
|
||||
return { st: state, bf: !!biteFish };
|
||||
});
|
||||
ok('пустой клёв: без forceBite и Math.random=0.99+ — клёва нет (WAIT, нет biteFish)',
|
||||
empty.st === 'WAIT' && !empty.bf, JSON.stringify(empty));
|
||||
|
||||
// --- 11. Ошибки консоли ---
|
||||
ok('нет ошибок JS (pageerror)', pageErrors.length === 0, pageErrors.join(' | '));
|
||||
ok('нет ошибок консоли', consoleErrors.length === 0, consoleErrors.join(' | '));
|
||||
|
||||
|
||||