Files
fishing-game/AGENTS.md
T

8.6 KiB
Raw Blame History

AGENTS.md

Игра — один файл index.html (HTML+CSS+JS, ~1700 строк): без сборки, зависимостей и бэкенда. Весь код в одном <script>, секции помечены комментариями. UI, комментарии и доки — на русском; при правках сохраняйте этот стиль.

Проверка (lint/typecheck отсутствуют)

  1. Синтаксис: node tools/check.mjs — вынимает JS из index.html и прогоняет node --check (при ошибке показывает номер строки index.html).
  2. Главный рубеж — плейтест:
    cd tests
    npm install   # puppeteer-core
    npm test      # = node test.js
    
    Ждать все PASS и exit 0. Сейчас проверок 107.
    
  3. Браузерный плейтест (вкладка встроенного браузера Orca, через Orca CLI):
    node tests/orca-playtest.js   # из каталога репо; требует ЗАПУЩЕННЫЙ Orca app и orca-ide (или ORCA_CLI_COMMAND)
    
    Проходит тот же геймплей «вживую» в браузере (не headless) и пишет orca-*.png в каталог запуска. Если Orca app не запущен — пропустить и НЕ блокировать (главный рубеж — пункт 2). Подробности и подводные камни: docs/BROWSER-TESTING.md.
  4. Скриншоты: 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).
  • Тесты гоняют игру через внутренние глобалы в page.evaluate: state, save, biteTimer, reel, SPECIES, SPECIES_NIGHT, rod(), SHINY_CHANCE, forceBoss, forceBite, forceVariant, VARIANTS, VARIANT_CHAIN, rollVariant(), variantChance(), cardInfo, 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 и имена состояний MENU/IDLE/CHARGE/CAST/WAIT/APPROACH/BITE/REEL/SNAP/CARD. Переименование любого из них ломает тесты молча — имена держать, либо обновлять тест в том же коммите.
  • Ввод на Pointer Events (pointerdown/pointerup), а не touch/mouse — так тест управляет игрой мышью. Не заменять на тач-обработчики.
  • Сейв: ключ localStorage fishing-save-v1. При изменении формата — миграцию добавлять в loadSave() (старые сейвы обязаны оставаться читаемыми).

Где крутить

  • Баланс (рыба/удилища/приманки): константы SPECIES, RODS, LURES в начале скрипта. Апгрейды снастей (уровни 1..4): ROD_UPG_COST/LURE_UPG_COST/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, улучшение на карточке (кнопка #btnCardUp, цепочка VARIANT_CHAIN, cost = 0.6×Δ ценности, формула — в cardUpNext()).
  • Погода (M8): таблица WEATHER (rain — variantMult ×2,5; fog — biteMult ×1,5), таймеры в реальном времени — updateWeather() в frame() (gap WEATHER_GAP_MIN..MAX 120–240 с, длительность WEATHER_DURATION 120 с, под форсом не гоняются), 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 у поплавка); чистая косметика — без эффектов на геймплей.
  • Сложность вываживания: 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-miss 1.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.