diff --git a/AGENTS.md b/AGENTS.md index 97a6efd..a4a0a2b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ npm install # puppeteer-core npm test # = node test.js ``` - Ждать все PASS и exit 0. Сейчас проверок 115. + Ждать все PASS и exit 0. Сейчас проверок 117. 3. Браузерный плейтест (вкладка встроенного браузера Orca, через Orca CLI): ```bash node tests/orca-playtest.js # из каталога репо; требует ЗАПУЩЕННЫЙ Orca app и orca-ide (или ORCA_CLI_COMMAND) @@ -29,7 +29,7 @@ ## Где крутить -- Баланс (рыба/удилища/приманки): константы `SPECIES`, `RODS`, `LURES` в начале скрипта (базы ×5: удочки 1750/4500/10000, наживки 1250/3000/7500). Апгрейды снастей (уровни 1..4): цена перехода — `upgCost(type,i,lv)`: id 0 — фикс `ROD_UPG_COST`/`LURE_UPG_COST` (250/750/1800 и 200/550/1400), id>0 — база×`UPG_MULT` (0.2/0.6/1.2, округление до 10); подписи уровней `ROD_UPG_NEXT`/`LURE_UPG_NEXT`, уровни `save.rodLv`/`save.lureLv`, эффективные статы — `effRod()`/`effLure()` (глобалы `rod()`/`lure()` отдают эффективные), гейтинг открытия — в `itemRow()`/`shopClick()`. +- Баланс (рыба/удилища/приманки): константы `SPECIES`, `RODS`, `LURES` в начале скрипта (базы: удочки 875/4500/15000, наживки 625/3000/11250). Апгрейды снастей (уровни 1..4): цена перехода — `upgCost(type,i,lv)`: id 0 — фикс `ROD_UPG_COST`/`LURE_UPG_COST` (70/210/500 и 60/160/400), id>0 — база×`UPG_MULT` (0.2/0.6/1.2, округление до 10); подписи уровней `ROD_UPG_NEXT`/`LURE_UPG_NEXT`, уровни `save.rodLv`/`save.lureLv`, эффективные статы — `effRod()`/`effLure()` (глобалы `rod()`/`lure()` отдают эффективные), гейтинг открытия — в `itemRow()`/`shopClick()`. - Ночь (M3): длительность суток `DAY_LENGTH` (сек), ночная таблица рыб `SPECIES_NIGHT`, фонарик — апгрейд 1 любого удилища (`save.rodLv[save.rod]>=2`, проверка `hasLight()` в ветке WAIT, секция «M3: Цикл дня/ночи»). - Босс (M4): тест-форс появления `forceBoss`, фазы боя `stateBoss`/`bossPhase`, босс-ветки в `updateReel()` (обрыв фазы) и `SNAP` в `updateGame()` (авто-переход), награда ×5 в `catchFish()`. - Варианты рыб (M6): таблица `VARIANTS` (порядок ролла: gold → silver → rainbow, иначе normal), `variantChance()` — единственная точка вероятностей (дождь M8 уже здесь — ×2,5, см. строку «Погода (M8)»; gold — живая `SHINY_CHANCE`), тест-форс `forceVariant`, босс всегда normal. Рыбу нельзя улучшать: «Улучшить вариант» (M6) убрано в этапе 21 — карточка улова только бейдж/оформление по варианту. diff --git a/README.md b/README.md index 6f3ca39..f4095d7 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ - Варианты рыб: серебряная ×2, золотая ×3, радужная ×5 — бейдж и оформление карточки улова - Погода: дождь (🌧️) — шанс серебряной/золотой/радужной ×2,5; туман (🌫️) — клёв ×1,5; событие идёт 120 с, следующее — раз в 2–4 минуты — ![дождь](screenshots/shot-weather-rain.png) - Скины лодки и поплавка 🎨 — вкладка «Скины» в лавке: лодка «Красная лодка» 800 🪙 / «Лодка-домик» 2000 🪙, поплавок «Красный» 500 🪙 / «Неоновый» 1500 🪙 — чистая косметика (без эффектов на геймплей) -- Локации: Озёра (25 000 🪙) — судак, хариус, гигантский рак; свой состав днём/ночью, свой цвет воды; покупка и переключение во вкладке 🗺️ лавки — ![озёра](screenshots/shot-lake.png) +- Локации: Озёра (100 000 🪙) — судак, хариус, гигантский рак; свой состав днём/ночью, свой цвет воды; покупка и переключение во вкладке 🗺️ лавки — ![озёра](screenshots/shot-lake.png) - Квесты дня (3 шт, генерация по дню), стрик входов со «щитом», 18 достижений с бонусами - Дневник рыбака — коллекция, максимальный улов по каждому виду, статистика - Магазин с вкладками: Снасти 🎣, Дневник 📔, Награды 🎯, Достижения 🏅, Скины 🎨, Локации 🗺️ diff --git a/index.html b/index.html index c0db757..532dff5 100644 --- a/index.html +++ b/index.html @@ -365,20 +365,20 @@ let forceWeather=null; // M8: тест-форс погоды ('rain'/'fog'/null const RODS=[ {id:0, name:'Дедова удочка', icon:'🧹', cost:0, maxW:3, line:100, drag:34, reel:1.0, size:0.00, desc:'До 3 кг · Нагрузка 100 · Drag 34 · Подсечка ×1.0'}, - {id:1, name:'Стеклопластик', icon:'🎣', cost:1750, maxW:8, line:140, drag:47, reel:1.3, size:0.08, desc:'До 8 кг · Нагрузка 140 · Drag 47 · Подсечка ×1.3'}, + {id:1, name:'Стеклопластик', icon:'🎣', cost:875, maxW:8, line:140, drag:47, reel:1.3, size:0.08, desc:'До 8 кг · Нагрузка 140 · Drag 47 · Подсечка ×1.3'}, {id:2, name:'Карбоновая', icon:'🏹', cost:4500, maxW:18, line:190, drag:63, reel:1.7, size:0.16, desc:'До 18 кг · Нагрузка 190 · Drag 63 · Подсечка ×1.7'}, - {id:3, name:'Титан', icon:'💪', cost:10000, maxW:40, line:260, drag:87, reel:2.3, size:0.28, desc:'До 40 кг · Нагрузка 260 · Drag 87 · Подсечка ×2.3'}, + {id:3, name:'Титан', icon:'💪', cost:15000, maxW:40, line:260, drag:87, reel:2.3, size:0.28, desc:'До 40 кг · Нагрузка 260 · Drag 87 · Подсечка ×2.3'}, ]; const LURES=[ {id:0, name:'Простой крючок', icon:'🪝', cost:0, cast:1.00, bite:1.0, rare:0.0, size:0.00, desc:'Надёжно, но рыба не в восторге'}, - {id:1, name:'Червь', icon:'🪱', cost:1250, cast:1.00, bite:1.3, rare:0.2, size:0.12, desc:'Клёв на 30% чаще, шанс крупной выше'}, + {id:1, name:'Червь', icon:'🪱', cost:625, cast:1.00, bite:1.3, rare:0.2, size:0.12, desc:'Клёв на 30% чаще, шанс крупной выше'}, {id:2, name:'Воблер', icon:'🐟', cost:3000, cast:1.15, bite:1.2, rare:0.8, size:0.28, desc:'Дальше бросок, приманивает крупных'}, - {id:3, name:'Золотая блёстка',icon:'✨', cost:7500, cast:1.20, bite:1.4, rare:2.0, size:0.50, desc:'Лучшее, что есть. К ней плывут легенды'}, + {id:3, name:'Золотая блёстка',icon:'✨', cost:11250, cast:1.20, bite:1.4, rare:2.0, size:0.50, desc:'Лучшее, что есть. К ней плывут легенды'}, ]; /* M5: апгрейды снастей — уровни 1..4 (макс). id 0 (база 0) — фиксированные цены переходов 1→2, 2→3, 3→4; снасти id>0 — доля UPG_MULT от их базовой стоимости */ -const ROD_UPG_COST=[250,750,1800]; -const LURE_UPG_COST=[200,550,1400]; +const ROD_UPG_COST=[70,210,500]; +const LURE_UPG_COST=[60,160,400]; const UPG_MULT=[0.2,0.6,1.2]; // доля базы: →2 ур ×0.2, →3 ур ×0.6, →4 ур ×1.2 /* Что даёт следующий уровень (индекс = текущий уровень − 1) — подпись в лавке */ const ROD_UPG_NEXT=['фонарик (ночь), леска ×1.15','тормоз ×1.15, катушка ×1.10','леска ещё ×1.15, крупные +0.10']; diff --git a/screenshots/shot-bite.png b/screenshots/shot-bite.png index d7a2f52..ee4f84b 100644 Binary files a/screenshots/shot-bite.png and b/screenshots/shot-bite.png differ diff --git a/screenshots/shot-boss.png b/screenshots/shot-boss.png index 549b07b..e906286 100644 Binary files a/screenshots/shot-boss.png and b/screenshots/shot-boss.png differ diff --git a/screenshots/shot-card.png b/screenshots/shot-card.png index 2d91b20..b55163d 100644 Binary files a/screenshots/shot-card.png and b/screenshots/shot-card.png differ diff --git a/screenshots/shot-charge.png b/screenshots/shot-charge.png index 8b13373..1611ef4 100644 Binary files a/screenshots/shot-charge.png and b/screenshots/shot-charge.png differ diff --git a/screenshots/shot-journal.png b/screenshots/shot-journal.png index 2230510..5b3d90e 100644 Binary files a/screenshots/shot-journal.png and b/screenshots/shot-journal.png differ diff --git a/screenshots/shot-lake.png b/screenshots/shot-lake.png index c1a955a..3845e2d 100644 Binary files a/screenshots/shot-lake.png and b/screenshots/shot-lake.png differ diff --git a/screenshots/shot-menu.png b/screenshots/shot-menu.png index 9f26d46..6d668fd 100644 Binary files a/screenshots/shot-menu.png and b/screenshots/shot-menu.png differ diff --git a/screenshots/shot-night.png b/screenshots/shot-night.png index 85bf31b..a2f2681 100644 Binary files a/screenshots/shot-night.png and b/screenshots/shot-night.png differ diff --git a/screenshots/shot-reel.png b/screenshots/shot-reel.png index c8c8142..eac4079 100644 Binary files a/screenshots/shot-reel.png and b/screenshots/shot-reel.png differ diff --git a/screenshots/shot-reset.png b/screenshots/shot-reset.png index 29c4308..5d7bedc 100644 Binary files a/screenshots/shot-reset.png and b/screenshots/shot-reset.png differ diff --git a/screenshots/shot-shop.png b/screenshots/shot-shop.png index 5ccc157..9c22972 100644 Binary files a/screenshots/shot-shop.png and b/screenshots/shot-shop.png differ diff --git a/screenshots/shot-wait.png b/screenshots/shot-wait.png index 2406368..280a933 100644 Binary files a/screenshots/shot-wait.png and b/screenshots/shot-wait.png differ diff --git a/screenshots/shot-weather-rain.png b/screenshots/shot-weather-rain.png index 6ce915a..e1adf9d 100644 Binary files a/screenshots/shot-weather-rain.png and b/screenshots/shot-weather-rain.png differ diff --git a/tests/test.js b/tests/test.js index 233473a..d61688c 100644 --- a/tests/test.js +++ b/tests/test.js @@ -191,11 +191,11 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); ok('у удилища указан предел веса', rodDesc.includes('кг')); await page.click('#shopBody [data-act="buy"][data-type="rod"]'); const rodAfter = await page.evaluate(() => ({ rod: save.rod, coins: save.coins, owned: save.rods.length })); - ok('куплено удилище', rodAfter.rod === 1 && rodAfter.coins === 8250 && rodAfter.owned === 2, JSON.stringify(rodAfter)); + ok('куплено удилище', rodAfter.rod === 1 && rodAfter.coins === 9125 && rodAfter.owned === 2, JSON.stringify(rodAfter)); await page.click('#shopBody [data-act="buy"][data-type="lure"]'); const lureAfter = await page.evaluate(() => ({ lure: save.lure, coins: save.coins })); - ok('куплена приманка', lureAfter.lure === 1 && lureAfter.coins === 7000, JSON.stringify(lureAfter)); + ok('куплена приманка', lureAfter.lure === 1 && lureAfter.coins === 8500, JSON.stringify(lureAfter)); await page.click('#tabJournal'); await sleep(100); @@ -210,7 +210,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); await page.reload({ waitUntil: 'load' }); await sleep(500); const saved = await page.evaluate(() => ({ coins: save.coins, rod: save.rod, lure: save.lure, casts: save.casts })); - ok('сохранение после reload', saved.coins === 7000 && saved.rod === 1 && saved.lure === 1 && saved.casts === 5, JSON.stringify(saved)); + ok('сохранение после reload', saved.coins === 8500 && saved.rod === 1 && saved.lure === 1 && saved.casts === 5, JSON.stringify(saved)); // --- 9. Сброс данных (играть сначала нужно зайти в игру) --- await page.click('#btnPlay'); @@ -222,7 +222,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); await page.click('#btnResetNo'); ok('отмена: диалог закрыт, данные не тронуты', await page.$eval('#resetDlg', el => el.classList.contains('hidden')) - && (await page.evaluate(() => save.coins)) === 7000); + && (await page.evaluate(() => save.coins)) === 8500); await page.click('#btnReset'); await sleep(100); await page.click('#btnResetYes'); @@ -565,8 +565,8 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); const row=items.find(el=>el.querySelector('[data-act="upgrade"][data-type="rod"]')); return row?{txt:row.textContent}:null; }); - ok('M5 лавка: фонарик = апгрейд 1 удилища (кнопка ⬆ 250 🪙, подпись «фонарик (ночь)»)', - m3shop && m3shop.txt.indexOf('250')>=0 && m3shop.txt.indexOf('фонарик')>=0 && m3shop.txt.indexOf('ноч')>=0, + ok('M5 лавка: фонарик = апгрейд 1 удилища (кнопка ⬆ 70 🪙, подпись «фонарик (ночь)»)', + m3shop && m3shop.txt.indexOf('70 🪙')>=0 && m3shop.txt.indexOf('фонарик')>=0 && m3shop.txt.indexOf('ноч')>=0, m3shop?m3shop.txt:'нет строки апгрейда'); await page.click('#btnCloseShop'); @@ -1038,8 +1038,8 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); }; return { r1: info(byName('Стеклопластик')), l1: info(byName('Червь')) }; }); - ok('M5 гейтинг лавки: после rodLv[0]=4 / lureLv[0]=4 — locked снят, кнопки buy «1750 🪙» / «1250 🪙»', - !m5g2.r1.locked && m5g2.r1.buyTxt === '1750 🪙' && !m5g2.l1.locked && m5g2.l1.buyTxt === '1250 🪙', + ok('M5 гейтинг лавки: после rodLv[0]=4 / lureLv[0]=4 — locked снят, кнопки buy «875 🪙» / «625 🪙»', + !m5g2.r1.locked && m5g2.r1.buyTxt === '875 🪙' && !m5g2.l1.locked && m5g2.l1.buyTxt === '625 🪙', JSON.stringify(m5g2)); // (8) двойная защита: buy-ветка shopClick при закрытом гейтинге не покупает @@ -1061,7 +1061,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); && m5d.toast.includes('Сначала улучши'), JSON.stringify(m5d)); - // (9) покупка апгрейда: 1000 🪙 → 750, rodLv[0]=2, фонарик включился + // (9) покупка апгрейда: 1000 🪙 → 930, rodLv[0]=2, фонарик включился await page.evaluate(() => { save.rods = [0]; save.lures = [0]; save.rod = 0; save.lure = 0; save.rodLv = { 0: 1 }; save.lureLv = { 0: 1 }; save.coins = 1000; @@ -1072,8 +1072,8 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); coins: save.coins, lv: save.rodLv[0], light: hasLight(), toast: document.getElementById('toast').textContent })); - ok('M5 покупка апгрейда: rod 0 1→2 ур. за 250 🪙 (1000→750), hasLight()=true, тост «Улучшено … → 2 ур»', - m5u.coins === 750 && m5u.lv === 2 && m5u.light === true + ok('M5 покупка апгрейда: rod 0 1→2 ур. за 70 🪙 (1000→930), hasLight()=true, тост «Улучшено … → 2 ур»', + m5u.coins === 930 && m5u.lv === 2 && m5u.light === true && m5u.toast.includes('Улучшено') && m5u.toast.includes('2 ур'), JSON.stringify(m5u)); @@ -1087,14 +1087,14 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); !m5x.hasUpg && m5x.txt.includes('●●●●') && m5x.txt.includes('4 ур.'), m5x.txt); // (11) не хватает монет: апгрейд не покупается - await page.evaluate(() => { save.rodLv[0] = 1; save.coins = 100; renderShop(); }); + await page.evaluate(() => { save.rodLv[0] = 1; save.coins = 50; renderShop(); }); await page.click('#shopBody [data-act="upgrade"][data-type="rod"][data-i="0"]'); const m5p = await page.evaluate(() => ({ coins: save.coins, lv: save.rodLv[0], toast: document.getElementById('toast').textContent })); - ok('M5 не хватает монет: 100 🪙 на апгрейд за 250 — coins=100, уровень не изменился, тост «Не хватает монет»', - m5p.coins === 100 && m5p.lv === 1 && m5p.toast.includes('Не хватает монет'), + ok('M5 не хватает монет: 50 🪙 на апгрейд за 70 — coins=50, уровень не изменился, тост «Не хватает монет»', + m5p.coins === 50 && m5p.lv === 1 && m5p.toast.includes('Не хватает монет'), JSON.stringify(m5p)); await page.click('#btnCloseShop'); @@ -1146,15 +1146,15 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); rods: save.rods.join(','), lv: save.rodLv[1], rod: save.rod, coins: save.coins })); - ok('M5 покупка разблокированной снасти: rod 1 за 1750 🪙 — rods=[0,1], rodLv[1]=1, снасть экипирована, coins=1250', - m5b.rods === '0,1' && m5b.lv === 1 && m5b.rod === 1 && m5b.coins === 1250, + ok('M5 покупка разблокированной снасти: rod 1 за 875 🪙 — rods=[0,1], rodLv[1]=1, снасть экипирована, coins=2125', + m5b.rods === '0,1' && m5b.lv === 1 && m5b.rod === 1 && m5b.coins === 2125, JSON.stringify(m5b)); - // (14b) апгрейд по формуле: rod 1 (база 1750) 1→2 ур. = 1750×0.2 = 350 + // (14b) апгрейд по формуле: rod 1 (база 875) 1→2 ур. = 875×0.2 = 175 → 180 (округление до 10) await page.click('#shopBody [data-act="upgrade"][data-type="rod"][data-i="1"]'); const m5bu = await page.evaluate(() => ({ lv: save.rodLv[1], coins: save.coins })); - ok('M5 апгрейд по формуле: rod 1 1→2 ур. за 350 🪙 (база 1750×0.2) — coins=1250→900, rodLv[1]=2', - m5bu.lv === 2 && m5bu.coins === 900, JSON.stringify(m5bu)); + ok('M5 апгрейд по формуле: rod 1 1→2 ур. за 180 🪙 (база 875×0.2 = 175 → округление до 10) — coins=2125→1945, rodLv[1]=2', + m5bu.lv === 2 && m5bu.coins === 1945, JSON.stringify(m5bu)); await page.click('#btnCloseShop'); // (15) пин цен апгрейдов: id 0 — фикс, id>0 — база×(0.2/0.6/1.2) @@ -1162,9 +1162,9 @@ const sleep = ms => new Promise(r => setTimeout(r, ms)); r: [0,1,2,3].map(i=>[1,2,3].map(lv=>upgCost('rod',i,lv)).join(',')), l: [0,1,2,3].map(i=>[1,2,3].map(lv=>upgCost('lure',i,lv)).join(',')) })); - ok('M5 цены апгрейдов: id 0 фикс (250/750/1800, 200/550/1400); id>0 = база×(0.2/0.6/1.2)', - m5c.r.join('|')==='250,750,1800|350,1050,2100|900,2700,5400|2000,6000,12000' - && m5c.l.join('|')==='200,550,1400|250,750,1500|600,1800,3600|1500,4500,9000', + ok('M5 цены апгрейдов: id 0 фикс (70/210/500, 60/160/400); id>0 = база×(0.2/0.6/1.2)', + m5c.r.join('|')==='70,210,500|180,530,1050|900,2700,5400|3000,9000,18000' + && m5c.l.join('|')==='60,160,400|130,380,750|600,1800,3600|2250,6750,13500', JSON.stringify(m5c)); // --- 10c. M6: варианты рыб (VARIANTS, forceVariant, карточка, full_variants) ---