11 KiB
11 KiB
AGENTS.md
Игра — один файл index.html (HTML+CSS+JS, ~2200 строк): без сборки, зависимостей и бэкенда. Весь код в одном <script>, секции помечены комментариями. UI, комментарии и доки — на русском; при правках сохраняйте этот стиль.
Проверка (lint/typecheck отсутствуют)
- Синтаксис:
node tools/check.mjs— вынимает JS изindex.htmlи прогоняетnode --check(при ошибке показывает номер строки index.html). - Главный рубеж — плейтест:
cd tests npm install # puppeteer-core npm test # = node test.jsЖдать все PASS и exit 0. Сейчас проверок 115. - Браузерный плейтест (вкладка встроенного браузера Orca, через Orca CLI):
Проходит тот же геймплей «вживую» в браузере (не headless) и пишет
node tests/orca-playtest.js # из каталога репо; требует ЗАПУЩЕННЫЙ Orca app и orca-ide (или ORCA_CLI_COMMAND)orca-*.pngв каталог запуска. Если Orca app не запущен — пропустить и НЕ блокировать (главный рубеж — пункт 2). Подробности и подводные камни:docs/BROWSER-TESTING.md. - Скриншоты:
node tests/shots.jsпишетshot-*.pngв каталог запуска (запускать изtests/); README ссылается наscreenshots/в корне — свежие скопировать туда. В headless нужен шрифт emoji (Noto Color Emoji), иначе в скриншотах квадраты.
Подводные камни
tests/test.jsберёт браузер изprocess.env.CHROMIUM || '/usr/bin/chromium-browser', а URL игры резолвит динамически от каталога репо (тот же паттерн, что вshots.js). Если тест падает на старте — поправьте имя/путь chromium под свой чекаут (переменнаяCHROMIUM).- Граница полуночи: если прогон стартует в пределах 5 минут от 00:00, test.js сам ждёт безопасное окно (дата не должна смениться посреди прогона — падают date-ассерты квестов/стрика). Это нормально, не баг.
- Тесты гоняют игру через внутренние глобалы в
page.evaluate:state,save,biteTimer,reel,SPECIES,SPECIES_NIGHT,rod(),SHINY_CHANCE,forceBoss,forceBite,forceVariant,VARIANTS,rollVariant(),variantChance(),fishForce(),stateBoss,bossPhase,dayPhase,pickSpecies(),setDayTime(),todayStr(),dateShift(),isoWeek(),achBonus(),forceWeather,weather,weatherT,weatherGap,WEATHER,WEATHER_GAP_MIN,WEATHER_GAP_MAX,WEATHER_DURATION,weatherActive(),BOAT_SKINS,BOBBER_SKINS,LOCATIONS,RIVER_SPECIES,RIVER_SPECIES_NIGHT,LAKE_SPECIES,LAKE_SPECIES_NIGHT,SAVE_KEY,loadSave,ACHIEVEMENTS,checkAchievementsи имена состоянийMENU/IDLE/CHARGE/CAST/WAIT/APPROACH/BITE/REEL/SNAP/CARD. Переименование любого из них ломает тесты молча — имена держать, либо обновлять тест в том же коммите. - Ввод на Pointer Events (
pointerdown/pointerup), а не touch/mouse — так тест управляет игрой мышью. Не заменять на тач-обработчики. - Сейв: ключ localStorage
fishing-save-v1. При изменении формата — миграцию добавлять вloadSave()(старые сейвы обязаны оставаться читаемыми).
Где крутить
- Баланс (рыба/удилища/приманки): константы
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(). - Ночь (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 — карточка улова только бейдж/оформление по варианту. - Погода (M8): таблица
WEATHER(rain —variantMult×2,5; fog —biteMult×1,5), таймеры в реальном времени —updateWeather()вframe()(gapWEATHER_GAP_MIN..MAX120–240 с, длительностьWEATHER_DURATION120 с, под форсом не гоняются),weatherActive()— единственная точка чтения (форс приоритетнее), эффекты только вvariantChance()(rain) иbiteChance()(fog), тест-форсforceWeather('rain'/'fog'/null, паттернforceBoss); погода не персистится в сейв. Заметка: туман может дать p>1 → насыщение 100% клёва (осознанно). - Скины (M-skins): таблицы
BOAT_SKINS/BOBBER_SKINS(лодка: Обычная 0 / «Красная лодка» 800 / «Лодка-домик» 2000 🪙; поплавок: Обычный 0 / Красный 500 / Неоновый 1500 🪙), сейвsave.boatSkin(s)/save.bobberSkin(s)+ миграция вloadSave()(включая гарантию бесплатного скина 0), вкладка «Скины» 🎨 (#tabSkins, строкаskinRow(list,i,owned,equipped,group), ветки buyskin/select вshopClick(): покупка со списанием и авто-экипировкой, выбор — только купленное), отрисовкаdrawBoat()/drawBobber()с clamp 0..2 (скин 2: каюта у лодки, неоновое свечениеshadowBlurв save/restore у поплавка); чистая косметика — без эффектов на геймплей. - Локации (этап 5): таблица
LOCATIONS(Река 0 — бесплатно / «Озёра» 100000 🪙;water— 6 hex [dayTop,dayMid,dayBot,nightTop,nightMid,nightBot] для градиентаdrawWater(), nightMix-логика та же), сейвsave.locations[]/save.location+ миграция вloadSave()(гарантия бесплатной Реки, паттерн скинов), вкладка «Локации» 🗺️ (#tabLocations, строкаlocationRow(loc,i,owned,selected), ветки buylocation/selectlocation вshopClick(): покупка — списание, push, АВТО-ПЕРЕХОДsave.location, тост «Добро пожаловать на Озёра!»; выбор — только купленное). Таблицы рыб:RIVER_SPECIES(_NIGHT)— ТОЛЬКО 8 базовых видов (роллы реки не меняются: pin-тесты §9j(b)/M4/M6 завязаны на веса и порядок — новые виды в реку НЕ добавлять!),LAKE_SPECIES(_NIGHT)— все 11 видов (весаLAKE_DAY_WEIGHTS/LAKE_NIGHT_WEIGHTS);SPECIES/SPECIES_NIGHT— полный словарь 11 видов (новые: zander/chuch/crayfish, ночные веса 6/10/2.5).pickSpecies()выбирает таблицу поsave.locationи dayPhase (логика ролла та же); босс (sturge) есть в обеих локациях; ачивка species_8 — теперь «Поймай все виды» (все 11, id не менять — старые сейвы). - Сложность вываживания:
updateReel(). - Физика боя (спринт 4):
fishForce()(сила рыбы: рывок(16+34·st)·(0.5+0.9·ag)·(0.7+0.5·ov), затишье5+7·ag, уставшая ×0.45) иupdateReel()— выносливостьst(стартst0, флагtiredприst≤0.12: рывки ×0.45, втягивание быстрее до ×1.6), тормоз катушки (чертаdragвRODS: в рывке выше тормоза леска скользитover·0.22), натяжение (10+over·1.1+22·ag, ×0.35 в затишье), втягивание(11+3·reel)·(1+0.6·(1−st)), свободный бегF·0.32, escape-бар растёт только при затишье без удержания (отпускание в рывок безопасно), near-miss1.45·d0,d0=45+7·w. Пустые клёвы: роллbiteChance()в ветке WAIT (тест-форсforceBite). Тесты (§10) завязаны на эти константы — при ребалансе гонять Monte-Carlo точными формулами (docs/PHYSICS-RESEARCH.md, §4.7) и править тесты в том же коммите. - Награды (M2): шаблоны квестов
QUEST_TPLS, награды стрикаSTREAK_REWARDS, достиженияACHIEVEMENTS— секция «M2. Награды» после сейва.
Запуск и публикация
- Dev-сервер:
python3 -m http.server 8000; игра так же работает сfile://. - Процесс разработки, публикация в Gitea, гигиена секретов:
docs/DEVLOG.md.