Лестница веса по локациям (Этап 38): дороже вода = тяжелее рыба = сложнее бой — SPECIES.wMin/wMax 24 видов (Озёра ×8: судак 8–48, линь 2.4–32, сиг 4–28; Море ×6, рыба-меч ×4 по явному заданию: скумбрия 1.8–15, камбала 3–30, тунец 12–72, меч 20–80; Океан ×4, кит ×2 чтобы остался вываживаемым: мурена 6–24, горбуна 8–40, черепаха 20–56, осьминог 16–48, манта 24–60, акула 20–56, кальмар 16–48, кит 40–100). Река 0..7 не тронута (пины §9j/M4/M6); ролл-веса/цены/редкости — не тронуты; код боя не менялся (rollWeight/hook/updateReel читают wMin/wMax напрямую, st0 и силуэт инвариантны к w/wMax). Мотивация: лестница была сломана — E[вес] Река≈1.3≈Озёра≈1.3>Море≈1.1, макс Реки 40 (осётр) > макс Озёр 12 (судак). Теперь максимы монотонны: 40<48<80<100; E[вес] 1.3→10.7→6.5→21 кг. tools/balance.mjs: Озёра 970.0/1072.4 · Море 1028.9/1192.4 · Океан 1691.4/2053.9 (было 121.2/134.1 · 190.8/222.0 · 539.5/671.3), Река 65.1/125.9 без изменений; инвариант «новая вода доходнее в ОБЕ фазы» держится: Море/Озёра день ×1.06 (тонкий запас — зафиксировано в AGENTS/DEVLOG), ночь ×1.11, Океан/Море ×1.64/×1.72; здоровье роллов без изменений. Осознанные странности (не чинили): средний вес Моря (≈6.5) легче Озёр (≈10.7) — ролл Моря 82% сельдь/скумбрия; кит 40–100 кг на Титане (maxW 40) при w>40 рвёт леску в режиме «держи», вываживается контролем натяжения (паттерн кит-босса Этапа 37). Доходность выросла ×5–15 — Этап 39 (отдельный коммит) урезает цены к целям Озёра≈×3 Реки, Море≈×4, Океан≈×5. Тесты: пины lc1 (10f) переписаны (24 строки — только веса) + текст ассерта (кит 40–100 кг); lc2/lc4/lc4b/lc4c/§9j/M4/M6/§10 без изменений — 193/193 PASS, exit 0; check.mjs OK. Доки AGENTS.md (Локации/Баланс Озёр/Баланс-скрипт) и DEVLOG (Этап 38) синхронизированы.
This commit is contained in:
@@ -39,9 +39,9 @@
|
||||
- Варианты рыб (M6): таблица `VARIANTS` — 8 вариантов, порядок ролла = порядок массива: copper ×1.5 @0.12 → silver ×2 @0.08 → quartz ×2.5 @0.07 → gold ×3 @`SHINY_CHANCE` (0.06, живая) → emerald ×4 @0.01 → diamond ×4.5 @0.007 → rainbow ×5 @0.005 (топ), иначе normal; P(особой)≈31% без буста снасти, E[mult]≈1.36. `variantChance()` — единственная точка вероятностей (дождь M8 уже здесь — ×3, см. строку «Погода (M8)»; буст снасти M6+ тоже здесь: `variantBoost()` = `lure().rare×0.15 + (уровень наживки−1)×0.05`, множитель ×(1+B·r), r — номер в лестнице 1..7 — редким больше; простая наживка 1 ур. → B=0; блёстка 4 ур. → B=0.6, rainbow ×5.2), счётчики улова — `VARIANT_KEY` (cp/sv/qz/sn/em/dm/rb; sn = «shiny» = gold), миграция счётчиков в `loadSave()` (дефолт 0), тест-форс `forceVariant`, босс всегда normal. Рыбу нельзя улучшать: «Улучшить вариант» (M6) убрано в этапе 21 — карточка улова только бейдж/оформление по варианту. Пины шансов в тестах (§10c(1)/(5), M8(2)) — при нейтральной снасти (снапшот/рестор `save.lure`/`lureLv` в eval).
|
||||
- Погода (M8): таблица `WEATHER` (rain — `variantMult` ×3; 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`); погода не персистится в сейв. Тост погоды — из `T` (`t('wx_'+weather)`; `WEATHER.*.toast` — не используется). Заметка: туман может дать 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, этап 35, этап 36 — эксклюзивная ихтиофауна): таблица `LOCATIONS` (Река 0 — бесплатно / «Озёра» 65536 🪙 / «Море» 131072 🪙 = ×2 Озёр, этап 35 / «Океан» 196608 🪙 = ×1.5 Моря, этап 36; `water` — 6 hex [dayTop,dayMid,dayBot,nightTop,nightMid,nightBot] для градиента `drawWater()`, nightMix-логика та же), сейв `save.locations[]`/`save.location` + миграция в `loadSave()` (гарантия бесплатной Реки, граница `save.location>=LOCATIONS.length` — в этапе 36 ИСПРАВЛЕН баг этапа 5: жёсткое `>1` сбрасывало Море на reload, паттерн скинов), вкладка «Локации» 🗺️ (`#tabLocations`, строка `locationRow(loc,i,owned,selected)`, ветки buylocation/selectlocation в `shopClick()`: покупка — списание, push, АВТО-ПЕРЕХОД `save.location`, тост welcome_lakes/welcome_sea/welcome_ocean; выбор — только купленное). Таблицы рыб: у КАЖДОЙ воды свои `*_IDS`-списки, БЕЗ пересечений (этап 35): `RIVER_SPECIES(_NIGHT)` — ровно 8 базовых (`RIVER_IDS`; роллы реки не меняются: pin-тесты §9j(b)/M4/M6 завязаны на веса и порядок — новые виды в реку НЕ добавлять!), `LAKE_SPECIES(_NIGHT)` — только 10 озёрных (`LAKE_IDS`: zander/chuch/crayfish/bleak/roundel/eelpout/tench/navaga + rudd/cisco этап 35; веса `LAKE_DAY_WEIGHTS`/`LAKE_NIGHT_WEIGHTS`), `SEA_SPECIES(_NIGHT)` — только 5 морских (`SEA_IDS`: herring/mackerel/flounder/tuna/swordfish; веса `SEA_*_WEIGHTS`), `OCEAN_SPECIES(_NIGHT)` — только 9 океанских (этап 36, `OCEAN_IDS`: moray/grouper/turtle/angler/octopus/manta/shark/squid/whale; веса `OCEAN_*_WEIGHTS` — «с потолка», точный баланс отдельным этапом через tools/balance.mjs). `SPECIES`/`SPECIES_NIGHT` — полный словарь 32 видов; ночные веса реки — в `NIGHT_WEIGHTS`, ключ обязателен каждому id (иначе `weight=undefined`). **Новые виды добавлять только в КОНЕЦ `SPECIES`** — `SPECIES[0..7]` запинены тестами (§573/§1524/§2446 `SPECIES[0]`, §9j/M4/M6 по id) и квестами (`QUEST_TPLS` индексы 0..2); вставка в середину молча сломает их. `pickSpecies()` выбирает таблицу по `save.location` (0/1/2/3) и dayPhase (логика ролла та же); босс — вид `BOSS_BY_LOCATION[save.location]` текущей локации (Этап 37: стурдж/рак/рыба-меч/кит; runtime-флаг, новых видов нет — см. строку «Босс (M4)»); ачивка species_8 — «Поймай все виды» (все 32, check = `SPECIES.every`, id не менять — старые сейвы). Ачивки локаций (этап 22/36): `lake_first`/`sea_first`/`ocean_first` — locations-based (проверка `save.locations`), не caught-based — пин §10c(8) achBonus не трогает.
|
||||
- **Баланс Озёр (этап 34):** ожидаемая выручка за улов = Σ(w·V)/Σw, V — середина диапазона веса × цена. `pickSpecies()` нормирует веса, так что **умножение всех весов на константу доход НЕ меняет** — для «стало дороже» правят СДВИГ долей (меньше `carass`/`bleak` — самые дешёвые V≈9/6 🪙, больше `sturge`/`gold`/`catfish`/`tench`/`navaga`). Текущие ориентиры: Река день 65.1 / ночь 125.9 🪙 (не меняются с этапа 34); озёрные цифры этапа 34 (110.4/145.5) устарели после этапа 35 (эксклюзивная фауна — карася/окуня в Озёрах больше нет): актуальные пины — Озёра 121.2 / 134.1, Море 190.8 / 222.0, Океан 539.5 / 671.3 (этап 36, веса «с потолка» — точный баланс отдельным этапом; инвариант «новая вода доходнее предыдущей в ОБЕ фазы» — проверять при любой правке `*_WEIGHTS`; считать руками не нужно — см. строку «Баланс-скрипт»). **Минимальный вес вида ≥ ~2.2**: ниже — тест lc4 (3000 роллов, «каждый вид ≥1») начинает плавать.
|
||||
- **Баланс-скрипт (`tools/balance.mjs`):** `node tools/balance.mjs` — считает доходность рыб ЗА ВАС, ручные прикидки Σ(w·V)/Σw больше не нужны. Вывод: по каждой из 8 таблиц ролла (Река/Озёра/Море/Океан × день/вечер-ночь) — виды (вес/доля %/V/вклад) и итог E[🪙 за улов], сводка-матрица с отношениями соседних вод в обеих фазах (Озёра/Река, Море/Озёра, Океан/Море — инвариант «каждая новая вода доходнее предыдущей в ОБЕ фазы»), «здоровье» (⚠ если у самого редкого вида P(промаха за 3000 роллов) > 0.1% — ориентир lc4 «каждый вид ≥1», соответствует вес ≥ ~2.2 при Σw≈95) и проверки целостности (пересечения таблиц, нечисловые веса, недостижимые виды). Данные берёт ПРЯМО из index.html: вырезает секцию «ДАННЫЕ» по якорям `const SPECIES=[` … закрытие таблицы `VARIANTS` и исполняет в изоляции (переименование якоря → понятная ошибка скрипта, чинить в самом скрипте) — правки баланса видны сразу, ничего дублировать не нужно. Модель ролла та же, что `pickSpecies()`: по умолчанию нейтрально (lure rare=0, dist=1 — штраф `minDist` ×0.06 не действует, как в пинах и lc4); опции: `--dist 0..1` (штраф дальних видов), `--lure <rare>` (буст `1+rare·rarity` — снасть может менять сравнение локаций, не путать с пинами), `--set id:вес` (what-if, можно несколько раз: переопределяет вес вида во ВСЕХ его таблицах — день и ночь; НЕ сохраняется), `--help`. Варианты рыб — строкой в шапке: E[mult]≈1.36 нейтрально / ≈1.69 при дожде — одинаково для всех локаций, на СРАВНЕНИЕ не влияет (босс «Осётр» — вне ролла, в доходность не входит). **Пины при нейтральной снасти (сверять до и после правок): Река 65.1/125.9 · Озёра 121.2/134.1 · Море 190.8/222.0 · Океан 539.5/671.3 (день/ночь 🪙).**
|
||||
- Локации (этап 5, этап 35, этап 36 — эксклюзивная ихтиофауна): таблица `LOCATIONS` (Река 0 — бесплатно / «Озёра» 65536 🪙 / «Море» 131072 🪙 = ×2 Озёр, этап 35 / «Океан» 196608 🪙 = ×1.5 Моря, этап 36; `water` — 6 hex [dayTop,dayMid,dayBot,nightTop,nightMid,nightBot] для градиента `drawWater()`, nightMix-логика та же), сейв `save.locations[]`/`save.location` + миграция в `loadSave()` (гарантия бесплатной Реки, граница `save.location>=LOCATIONS.length` — в этапе 36 ИСПРАВЛЕН баг этапа 5: жёсткое `>1` сбрасывало Море на reload, паттерн скинов), вкладка «Локации» 🗺️ (`#tabLocations`, строка `locationRow(loc,i,owned,selected)`, ветки buylocation/selectlocation в `shopClick()`: покупка — списание, push, АВТО-ПЕРЕХОД `save.location`, тост welcome_lakes/welcome_sea/welcome_ocean; выбор — только купленное). Таблицы рыб: у КАЖДОЙ воды свои `*_IDS`-списки, БЕЗ пересечений (этап 35): `RIVER_SPECIES(_NIGHT)` — ровно 8 базовых (`RIVER_IDS`; роллы реки не меняются: pin-тесты §9j(b)/M4/M6 завязаны на веса и порядок — новые виды в реку НЕ добавлять!), `LAKE_SPECIES(_NIGHT)` — только 10 озёрных (`LAKE_IDS`: zander/chuch/crayfish/bleak/roundel/eelpout/tench/navaga + rudd/cisco этап 35; веса `LAKE_DAY_WEIGHTS`/`LAKE_NIGHT_WEIGHTS`), `SEA_SPECIES(_NIGHT)` — только 5 морских (`SEA_IDS`: herring/mackerel/flounder/tuna/swordfish; веса `SEA_*_WEIGHTS`), `OCEAN_SPECIES(_NIGHT)` — только 9 океанских (этап 36, `OCEAN_IDS`: moray/grouper/turtle/angler/octopus/manta/shark/squid/whale; веса `OCEAN_*_WEIGHTS` — «с потолка», точный баланс отдельным этапом через tools/balance.mjs). Этап 38: лестница веса `wMin/wMax` — Озёра ×8, Море ×6 (рыба-меч ×4), Океан ×4 (синий кит ×2 — чтобы остался вываживаемым), Река НЕ меняется: дороже вода = тяжелее рыба = сложнее бой (дольше `d0=45+7·w`, сильнее `ov→aggr`); ролл-веса не тронуты. `SPECIES`/`SPECIES_NIGHT` — полный словарь 32 видов; ночные веса реки — в `NIGHT_WEIGHTS`, ключ обязателен каждому id (иначе `weight=undefined`). **Новые виды добавлять только в КОНЕЦ `SPECIES`** — `SPECIES[0..7]` запинены тестами (§573/§1524/§2446 `SPECIES[0]`, §9j/M4/M6 по id) и квестами (`QUEST_TPLS` индексы 0..2); вставка в середину молча сломает их. `pickSpecies()` выбирает таблицу по `save.location` (0/1/2/3) и dayPhase (логика ролла та же); босс — вид `BOSS_BY_LOCATION[save.location]` текущей локации (Этап 37: стурдж/рак/рыба-меч/кит; runtime-флаг, новых видов нет — см. строку «Босс (M4)»); ачивка species_8 — «Поймай все виды» (все 32, check = `SPECIES.every`, id не менять — старые сейвы). Ачивки локаций (этап 22/36): `lake_first`/`sea_first`/`ocean_first` — locations-based (проверка `save.locations`), не caught-based — пин §10c(8) achBonus не трогает.
|
||||
- **Баланс Озёр (этап 34):** ожидаемая выручка за улов = Σ(w·V)/Σw, V — середина диапазона веса × цена. `pickSpecies()` нормирует веса, так что **умножение всех весов на константу доход НЕ меняет** — для «стало дороже» правят СДВИГ долей (меньше `carass`/`bleak` — самые дешёвые V≈9/6 🪙, больше `sturge`/`gold`/`catfish`/`tench`/`navaga`). Текущие ориентиры: Река день 65.1 / ночь 125.9 🪙 (не меняются с этапа 34); этап 38: лестница веса `wMin/wMax` (Озёра ×8, Море ×6 — меч ×4, Океан ×4 — кит ×2) при старых ценах: актуальные пины — Озёра 970.0 / 1072.4, Море 1028.9 / 1192.4, Океан 1691.4 / 2053.9 (Море/Озёра день ×1.06 — тонкий запас: при правках цен/весов следить, чтобы не ушло в <1; инвариант «новая вода доходнее предыдущей в ОБЕ фазы» — проверять при любой правке `*_WEIGHTS`/`price`/`wMin/wMax`; считать руками не нужно — см. строку «Баланс-скрипт»). **Минимальный вес вида ≥ ~2.2**: ниже — тест lc4 (3000 роллов, «каждый вид ≥1») начинает плавать.
|
||||
- **Баланс-скрипт (`tools/balance.mjs`):** `node tools/balance.mjs` — считает доходность рыб ЗА ВАС, ручные прикидки Σ(w·V)/Σw больше не нужны. Вывод: по каждой из 8 таблиц ролла (Река/Озёра/Море/Океан × день/вечер-ночь) — виды (вес/доля %/V/вклад) и итог E[🪙 за улов], сводка-матрица с отношениями соседних вод в обеих фазах (Озёра/Река, Море/Озёра, Океан/Море — инвариант «каждая новая вода доходнее предыдущей в ОБЕ фазы»), «здоровье» (⚠ если у самого редкого вида P(промаха за 3000 роллов) > 0.1% — ориентир lc4 «каждый вид ≥1», соответствует вес ≥ ~2.2 при Σw≈95) и проверки целостности (пересечения таблиц, нечисловые веса, недостижимые виды). Данные берёт ПРЯМО из index.html: вырезает секцию «ДАННЫЕ» по якорям `const SPECIES=[` … закрытие таблицы `VARIANTS` и исполняет в изоляции (переименование якоря → понятная ошибка скрипта, чинить в самом скрипте) — правки баланса видны сразу, ничего дублировать не нужно. Модель ролла та же, что `pickSpecies()`: по умолчанию нейтрально (lure rare=0, dist=1 — штраф `minDist` ×0.06 не действует, как в пинах и lc4); опции: `--dist 0..1` (штраф дальних видов), `--lure <rare>` (буст `1+rare·rarity` — снасть может менять сравнение локаций, не путать с пинами), `--set id:вес` (what-if, можно несколько раз: переопределяет вес вида во ВСЕХ его таблицах — день и ночь; НЕ сохраняется), `--help`. Варианты рыб — строкой в шапке: E[mult]≈1.36 нейтрально / ≈1.69 при дожде — одинаково для всех локаций, на СРАВНЕНИЕ не влияет (босс «Осётр» — вне ролла, в доходность не входит). **Пины при нейтральной снасти (сверять до и после правок): Река 65.1/125.9 · Озёра 970.0/1072.4 · Море 1028.9/1192.4 · Океан 1691.4/2053.9 (день/ночь 🪙, этап 38: лестница весов).**
|
||||
Цикл настройки баланса новой локации: (1) новые виды — только в КОНЕЦ `SPECIES` + ключ каждому id в `NIGHT_WEIGHTS`; (2) свои `*_IDS` + `*_DAY_WEIGHTS`/`*_NIGHT_WEIGHTS` без пересечений с другими водами (пин lc2 + проверка скрипта), развилка таблиц в `pickSpecies()`; (3) крутить доли прикидками без правки игры: `node tools/balance.mjs --set zander:30 --set bleak:5` (id — латиницей!) до нужных цифр — двигают СДВИГ долей, ×константа всех весов доход не меняет; (4) перенести финальные веса в index.html и перегнать скрипт БЕЗ `--set` — цифры обязаны совпасть с what-if; (5) цена/веса самих видов (`SPECIES`) — тоже видны скрипту сразу, но их правки могут задевать другие локации (общий словарь!) — пересчитать всё; (6) прогнать плейтест (lc2/lc4 и пины §9j/M4/M6 — река не должна меняться).
|
||||
- Сложность вываживания: `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) и править тесты в том же коммите.
|
||||
|
||||
@@ -700,3 +700,15 @@ git add -A && git commit -m "что изменилось" && git push
|
||||
- **`tools/balance.mjs` не тронут:** босс — runtime-флаг, в данных отсутствует, в доходность не входит (строка «босс „Осётр“ (Река) клюёт вне обычного ролла» — как была; теперь боссы и в других водах, но они тоже вне ролла).
|
||||
|
||||
**Тесты:** новый блок **§9k2 «Этап 37»** (рядом с §9k, паттерн 1:1): цикл по 3 новым локациям — `save.location=N` + monkey-patch `pickSpecies` + `forceBoss` → старт босса (stateBoss/bossPhase=1/reel.boss/spId, вес 20–50 по фактическому `reel.w`) → `bossPhase=3` + улов (паттерн M6(4)) → монеты = w×price×5 (±10%), `save.bosses[X]=true`, bossCount=1, ачивка выдана, карточка «<имя>-БОСС» + бейдж + boss-card; в конце — миграция (старый сейв без `save.bosses` → `{}`, паттерн lc5: `delete save.bosses` + localStorage + `loadSave()`) и полный сброс (forceBoss/pickSpecies/location/bosses/ach/caught/coins). **a22d**: total 31→34 + id `lake_boss`/`sea_boss`/`ocean_boss`. M4-пины (§9k/§9l(3)/M6(4)) **не поправлялись**: награда читает фактический `w` (`window.__m4W`/`__m6W`), фазы нормированы на `d0` (20–50 кг → d0 185–395, отношения те же) — все 3 блока прошли без изменений. Итого **193/193 PASS, exit 0** (было 183: +10 ok), два прогона; `check.mjs` OK; `emoji-font.py --check` OK (64 emoji); `tools/balance.mjs` — цифры без изменений (Река 65.1/125.9 · Озёра 121.2/134.1 · Море 190.8/222.0 · Океан 539.5/671.3). Доки (README/AGENTS/KODA) синхронизированы в том же коммите.
|
||||
|
||||
## Этап 38. Лестница веса по локациям (дороже вода = тяжелее рыба = сложнее бой)
|
||||
|
||||
**Задача:** «на более дорогих локациях было сложнее ловить рыбу (например, её вес сильно больше будет)». Вес — главный рычаг сложности: `d0=45+7·w` (дольше бой) и `ov=w/rod().maxW → aggr` (сильнее рывки/натяжение/escape). До этапа лестница была «сломана»: E[вес] Река ≈1.3 ≈ Озёра ≈1.3 > Море ≈1.1, а максимумы — Река 40 кг (осётр) > Озёра 12 кг (судак): на «бесплатной» реке рыба была тяжелее, чем в Озёрах. Множители подобраны по запросу пользователя: **Озёра ×8, Море ×6 (рыба-меч ×4 — 20–80 кг, явно задано), Океан ×4 (синий кит ×2 — 40–100 кг, чтобы остался вываживаемым)**; Река не меняется (пины §9j/M4/M6).
|
||||
|
||||
**Решения:**
|
||||
- **`SPECIES.wMin/wMax` — только 24 вида Озёр/Моря/Океана** (строки «Этап 5/33/35/36», река 0..7 не тронута). Ключевые: Озёра — судак 8–48, линь 2.4–32, сиг 4–28, навага 2.4–20; Море — скумбрия 1.8–15, камбала 3–30, тунец 12–72, рыба-меч 20–80; Океан — мурена 6–24, горбуна 8–40, черепаха 20–56, осьминог 16–48, манта 24–60, акула 20–56, кальмар 16–48, кит 40–100. Ролл-веса (`RIVER_*/LAKE_*/SEA_*/OCEAN_*`), цены, редкости, `minDist`/`strength` — не тронуты. Код боя не менялся: `rollWeight()`/`hook()`/`updateReel()` читают `wMin/wMax` напрямую; `st0` (0.65+0.35·min(1,w/wMax)) и силуэт (`reel.w/sp.wMax`) инвариантны к равномерному масштабированию диапазона.
|
||||
- **Новые цифры (`tools/balance.mjs`, нейтральная снасть):** Озёра 970.0/1072.4 (было 121.2/134.1), Море 1028.9/1192.4 (было 190.8/222.0), Океан 1691.4/2053.9 (было 539.5/671.3); Река 65.1/125.9 — без изменений. Инвариант «каждая новая вода доходнее предыдущей в ОБЕ фазы» держится: Море/Озёра день **×1.06** (тонкий запас — при правках цен/весов следить, чтобы не ушло в <1), ночь ×1.11; Океан/Море ×1.64/×1.72. E[вес] улова: Река ≈1.3 → Озёра ≈10.7 → Море ≈6.5 → Океан ≈21 кг; максимумы — 40 (осётр) < 48 (судак) < 80 (меч) < 100 (кит) — лестница максимумов теперь монотонна.
|
||||
- **Осознанные странности (не чинили, по решению пользователя):** (1) средний вес Моря (≈6.5 кг) легче среднего по Озёрам (≈10.7) — структура ролла Моря «82% сельдь/скумбрия + редкие гиганты», лестница дохода при этом нормальная; (2) кит 40–100 кг: на «Титане» (maxW 40) при w>40 в режиме «держи кнопку» — настоящий обрыв, вываживается контролем натяжения (тот же паттерн, что у кит-босса Этапа 37); (3) доходность выросла ×5–15 — **Этап 39** (отдельный коммит) урезает цены Озёр/Моря/Океана к целям Озёра ≈×3 Реки, Море ≈×4, Океан ≈×5.
|
||||
- **Боссы не затронуты:** вес фиксирован 20–50 кг в `hook()` (не читает `wMin/wMax`), `BOSS_BY_LOCATION` не меняется.
|
||||
|
||||
**Тесты:** пины **lc1** (10f) переписаны — 24 строки `name,rarity,price,wMin,wMax` (меняются только веса, цены не тронуты) + текст ассерта (кит «20–50 кг» → «40–100 кг», пометка про этап 38). lc2/lc4/lc4b/lc4c, §9j/M4/M6, §10 (физика) — **без изменений** (река и ролл-веса не тронуты). Итого **193/193 PASS, exit 0**; `check.mjs` OK; `tools/balance.mjs` — цифры выше, «здоровье» роллов без изменений (мин. 16.3 попаданий). Доки (AGENTS.md: строки «Локации», «Баланс Озёр», «Баланс-скрипт») синхронизированы в том же коммите.
|
||||
|
||||
+28
-24
@@ -360,39 +360,43 @@ const SPECIES=[
|
||||
{id:'catfish',name:'Сом', nameEn:'Catfish', emoji:'🐡', rarity:2, weight:5, minDist:0.65, strength:0.75, wMin:2.0, wMax:20.0, price:45, hue:260},
|
||||
{id:'sturge', name:'Осётр', nameEn:'Sturgeon', emoji:'🐋', rarity:3, weight:2.5, minDist:0.80, strength:0.85, wMin:5.0, wMax:40.0, price:65, hue:200},
|
||||
{id:'gold', name:'Золотая рыбка', nameEn:'Golden Fish', emoji:'🎏', rarity:4, weight:1.2, minDist:0.90, strength:0.90, wMin:0.05, wMax:1.0, price:1500, hue:45},
|
||||
/* Этап 38: лестница веса по локациям (дороже вода = тяжелее рыба = сложнее бой):
|
||||
Озёра ×8, Море ×6 (Рыба-меч ×4), Океан ×4 (Синий кит ×2 — чтобы остался вываживаемым);
|
||||
Река не меняется (пины §9j/M4/M6). Правятся только wMin/wMax — ролл-веса
|
||||
(RIVER_*, LAKE_*, SEA_*, OCEAN_*) не тронуты */
|
||||
/* Этап 5: жильцы Озёр (в реку не заходят — ролл реки по RIVER_*; порядок 0..7 не трогаем);
|
||||
weight у новых видов не используется для ролла: река — RIVER_*, Озёра — LAKE_DAY_WEIGHTS/LAKE_NIGHT_WEIGHTS */
|
||||
{id:'zander', name:'Судак', nameEn:'Zander', emoji:'🐟', rarity:2, weight:8, minDist:0.45, strength:0.60, wMin:1.0, wMax:6.0, price:40, hue:180},
|
||||
{id:'chuch', name:'Хариус', nameEn:'Grayling', emoji:'🐠', rarity:1, weight:18, minDist:0.35, strength:0.35, wMin:0.2, wMax:2.0, price:30, hue:25},
|
||||
{id:'crayfish',name:'Гигантский рак', nameEn:'Giant Crayfish', emoji:'🦞', rarity:3, weight:1.5, minDist:0.70, strength:0.50, wMin:0.2, wMax:1.0, price:200, hue:0},
|
||||
{id:'zander', name:'Судак', nameEn:'Zander', emoji:'🐟', rarity:2, weight:8, minDist:0.45, strength:0.60, wMin:8.0, wMax:48.0, price:40, hue:180},
|
||||
{id:'chuch', name:'Хариус', nameEn:'Grayling', emoji:'🐠', rarity:1, weight:18, minDist:0.35, strength:0.35, wMin:1.6, wMax:16.0, price:30, hue:25},
|
||||
{id:'crayfish',name:'Гигантский рак', nameEn:'Giant Crayfish', emoji:'🦞', rarity:3, weight:1.5, minDist:0.70, strength:0.50, wMin:1.6, wMax:8.0, price:200, hue:0},
|
||||
/* Этап 33: ещё пять жильцов Озёр (в реку не заходят — ролл реки по RIVER_*; порядок 0..7 и записи
|
||||
этапа 5 не тронуты); weight не используется для ролла Озёр — там LAKE_*_WEIGHTS */
|
||||
{id:'bleak', name:'Плотва', nameEn:'Roach', emoji:'🐟', rarity:0, weight:70, minDist:0.05, strength:0.28, wMin:0.05, wMax:0.6, price:18, hue:200},
|
||||
{id:'roundel',name:'Густера', nameEn:'White bream', emoji:'🐟', rarity:0, weight:34, minDist:0.15, strength:0.32, wMin:0.1, wMax:0.9, price:24, hue:215},
|
||||
{id:'eelpout',name:'Вьюн', nameEn:'Weather loach', emoji:'🐍', rarity:1, weight:12, minDist:0.40, strength:0.30, wMin:0.05, wMax:0.4, price:55, hue:35},
|
||||
{id:'tench', name:'Линь', nameEn:'Tench', emoji:'🐟', rarity:2, weight:10, minDist:0.55, strength:0.55, wMin:0.3, wMax:4.0, price:80, hue:95},
|
||||
{id:'navaga', name:'Навага', nameEn:'Navaga', emoji:'🐟', rarity:3, weight:2, minDist:0.75, strength:0.70, wMin:0.3, wMax:2.5, price:250, hue:195},
|
||||
{id:'bleak', name:'Плотва', nameEn:'Roach', emoji:'🐟', rarity:0, weight:70, minDist:0.05, strength:0.28, wMin:0.4, wMax:4.8, price:18, hue:200},
|
||||
{id:'roundel',name:'Густера', nameEn:'White bream', emoji:'🐟', rarity:0, weight:34, minDist:0.15, strength:0.32, wMin:0.8, wMax:7.2, price:24, hue:215},
|
||||
{id:'eelpout',name:'Вьюн', nameEn:'Weather loach', emoji:'🐍', rarity:1, weight:12, minDist:0.40, strength:0.30, wMin:0.4, wMax:3.2, price:55, hue:35},
|
||||
{id:'tench', name:'Линь', nameEn:'Tench', emoji:'🐟', rarity:2, weight:10, minDist:0.55, strength:0.55, wMin:2.4, wMax:32.0, price:80, hue:95},
|
||||
{id:'navaga', name:'Навага', nameEn:'Navaga', emoji:'🐟', rarity:3, weight:2, minDist:0.75, strength:0.70, wMin:2.4, wMax:20.0, price:250, hue:195},
|
||||
/* Этап 35: у каждой локации — своя ихтиофауна (без пересечения с Рекой).
|
||||
Две новые озёрные рыбы (в Озёра, в реку не заходят); weight не для ролла — там LAKE_*_WEIGHTS */
|
||||
{id:'rudd', name:'Краснопёрка', nameEn:'Rudd', emoji:'🐠', rarity:1, weight:20, minDist:0.25, strength:0.34, wMin:0.1, wMax:1.2, price:26, hue:25},
|
||||
{id:'cisco', name:'Сиг', nameEn:'Cisco', emoji:'🐟', rarity:2, weight:11, minDist:0.58, strength:0.58, wMin:0.5, wMax:3.5, price:90, hue:200},
|
||||
{id:'rudd', name:'Краснопёрка', nameEn:'Rudd', emoji:'🐠', rarity:1, weight:20, minDist:0.25, strength:0.34, wMin:0.8, wMax:9.6, price:26, hue:25},
|
||||
{id:'cisco', name:'Сиг', nameEn:'Cisco', emoji:'🐟', rarity:2, weight:11, minDist:0.58, strength:0.58, wMin:4.0, wMax:28.0, price:90, hue:200},
|
||||
/* Этап 35: Море — 5 морских видов (только в Море, id с конца; ролл — SEA_*_WEIGHTS) */
|
||||
{id:'herring', name:'Сельдь', nameEn:'Herring', emoji:'🐟', rarity:0, weight:60, minDist:0.05, strength:0.28, wMin:0.1, wMax:0.8, price:30, hue:205},
|
||||
{id:'mackerel',name:'Скумбрия', nameEn:'Mackerel', emoji:'🐟', rarity:1, weight:30, minDist:0.30, strength:0.42, wMin:0.3, wMax:2.5, price:70, hue:190},
|
||||
{id:'flounder',name:'Камбала', nameEn:'Flounder', emoji:'🐡', rarity:2, weight:12, minDist:0.50, strength:0.60, wMin:0.5, wMax:5.0, price:130, hue:40},
|
||||
{id:'tuna', name:'Тунец', nameEn:'Tuna', emoji:'🐋', rarity:3, weight:4, minDist:0.72, strength:0.80, wMin:2.0, wMax:12.0, price:180, hue:210},
|
||||
{id:'swordfish',name:'Рыба-меч', nameEn:'Swordfish', emoji:'🦈', rarity:4, weight:1.5, minDist:0.88, strength:0.92, wMin:5.0, wMax:20.0, price:320, hue:250},
|
||||
{id:'herring', name:'Сельдь', nameEn:'Herring', emoji:'🐟', rarity:0, weight:60, minDist:0.05, strength:0.28, wMin:0.6, wMax:4.8, price:30, hue:205},
|
||||
{id:'mackerel',name:'Скумбрия', nameEn:'Mackerel', emoji:'🐟', rarity:1, weight:30, minDist:0.30, strength:0.42, wMin:1.8, wMax:15.0, price:70, hue:190},
|
||||
{id:'flounder',name:'Камбала', nameEn:'Flounder', emoji:'🐡', rarity:2, weight:12, minDist:0.50, strength:0.60, wMin:3.0, wMax:30.0, price:130, hue:40},
|
||||
{id:'tuna', name:'Тунец', nameEn:'Tuna', emoji:'🐋', rarity:3, weight:4, minDist:0.72, strength:0.80, wMin:12.0, wMax:72.0, price:180, hue:210},
|
||||
{id:'swordfish',name:'Рыба-меч', nameEn:'Swordfish', emoji:'🦈', rarity:4, weight:1.5, minDist:0.88, strength:0.92, wMin:20.0, wMax:80.0, price:320, hue:250},
|
||||
/* Этап 36: Океан — 9 глубоководных видов (только Океан; ролл — OCEAN_*_WEIGHTS).
|
||||
Иконки: 🦈 дубль swordfish, 🐳 — у кита (китовой акулы нет); minDist/strength — кривые по редкости */
|
||||
{id:'moray', name:'Гигантская мурена', nameEn:'Giant Moray Eel', emoji:'🐍', rarity:0, weight:30, minDist:0.10, strength:0.35, wMin:1.5, wMax:6.0, price:25, hue:50},
|
||||
{id:'grouper', name:'Горбуна', nameEn:'Grouper', emoji:'🐟', rarity:0, weight:25, minDist:0.20, strength:0.35, wMin:2.0, wMax:10.0, price:25, hue:15},
|
||||
{id:'turtle', name:'Морская черепаха', nameEn:'Green Sea Turtle', emoji:'🐢', rarity:1, weight:10, minDist:0.35, strength:0.45, wMin:5.0, wMax:14.0, price:45, hue:140},
|
||||
{id:'angler', name:'Удильщик', nameEn:'Anglerfish', emoji:'🐡', rarity:2, weight:6, minDist:0.50, strength:0.55, wMin:1.0, wMax:4.0, price:110, hue:280},
|
||||
{id:'octopus', name:'Гигантский осьминог', nameEn:'Giant Octopus', emoji:'🐙', rarity:2, weight:4, minDist:0.55, strength:0.60, wMin:4.0, wMax:12.0, price:120, hue:350},
|
||||
{id:'manta', name:'Скат-манта', nameEn:'Manta Ray', emoji:'🐟', rarity:3, weight:3, minDist:0.75, strength:0.75, wMin:6.0, wMax:15.0, price:150, hue:220},
|
||||
{id:'shark', name:'Большая белая акула', nameEn:'Great White Shark',emoji:'🦈', rarity:3, weight:2.5, minDist:0.78, strength:0.78, wMin:5.0, wMax:14.0, price:160, hue:210},
|
||||
{id:'squid', name:'Гигантский кальмар', nameEn:'Giant Squid', emoji:'🦑', rarity:4, weight:2.5, minDist:0.85, strength:0.85, wMin:4.0, wMax:12.0, price:190, hue:300},
|
||||
{id:'whale', name:'Синий кит', nameEn:'Blue Whale', emoji:'🐳', rarity:4, weight:2, minDist:0.92, strength:0.95, wMin:20.0,wMax:50.0, price:350, hue:230},
|
||||
{id:'moray', name:'Гигантская мурена', nameEn:'Giant Moray Eel', emoji:'🐍', rarity:0, weight:30, minDist:0.10, strength:0.35, wMin:6.0, wMax:24.0, price:25, hue:50},
|
||||
{id:'grouper', name:'Горбуна', nameEn:'Grouper', emoji:'🐟', rarity:0, weight:25, minDist:0.20, strength:0.35, wMin:8.0, wMax:40.0, price:25, hue:15},
|
||||
{id:'turtle', name:'Морская черепаха', nameEn:'Green Sea Turtle', emoji:'🐢', rarity:1, weight:10, minDist:0.35, strength:0.45, wMin:20.0, wMax:56.0, price:45, hue:140},
|
||||
{id:'angler', name:'Удильщик', nameEn:'Anglerfish', emoji:'🐡', rarity:2, weight:6, minDist:0.50, strength:0.55, wMin:4.0, wMax:16.0, price:110, hue:280},
|
||||
{id:'octopus', name:'Гигантский осьминог', nameEn:'Giant Octopus', emoji:'🐙', rarity:2, weight:4, minDist:0.55, strength:0.60, wMin:16.0, wMax:48.0, price:120, hue:350},
|
||||
{id:'manta', name:'Скат-манта', nameEn:'Manta Ray', emoji:'🐟', rarity:3, weight:3, minDist:0.75, strength:0.75, wMin:24.0, wMax:60.0, price:150, hue:220},
|
||||
{id:'shark', name:'Большая белая акула', nameEn:'Great White Shark',emoji:'🦈', rarity:3, weight:2.5, minDist:0.78, strength:0.78, wMin:20.0, wMax:56.0, price:160, hue:210},
|
||||
{id:'squid', name:'Гигантский кальмар', nameEn:'Giant Squid', emoji:'🦑', rarity:4, weight:2.5, minDist:0.85, strength:0.85, wMin:16.0, wMax:48.0, price:190, hue:300},
|
||||
{id:'whale', name:'Синий кит', nameEn:'Blue Whale', emoji:'🐳', rarity:4, weight:2, minDist:0.92, strength:0.95, wMin:40.0, wMax:100.0, price:350, hue:230},
|
||||
];
|
||||
const RARITY_NAMES=['Обычная','Необычная','Редкая','Эпическая','Легендарная'];
|
||||
const RARITY_NAMES_EN=['Common','Uncommon','Rare','Epic','Legendary']; // Y5: i18n — EN-названия редкостей
|
||||
|
||||
+12
-12
@@ -1982,18 +1982,18 @@ async function toGameMenu(p) {
|
||||
l.water.every(h2 => typeof h2 === 'string' && /^#[0-9a-fA-F]{6}$/.test(h2)))
|
||||
};
|
||||
});
|
||||
ok('локации данные: SPECIES=32 (8 река + 10 озёра + 5 море + 9 океан, этапы 35/36) — zander «Судак» r2 40🪙, crayfish r3 200🪙, navaga r3 250🪙; озёрные новинки rudd «Краснопёрка» r1 26🪙, cisco «Сиг» r2 90🪙; морские herring «Сельдь» r0 30🪙, mackerel «Скумбрия» r1 70🪙, flounder «Камбала» r2 130🪙, tuna «Тунец» r3 180🪙, swordfish «Рыба-меч» r4 320🪙; океанские moray «Гигантская мурена» r0 25🪙, grouper «Горбуна» r0 25🪙, turtle «Морская черепаха» r1 45🪙, angler «Удильщик» r2 110🪙, octopus «Гигантский осьминог» r2 120🪙, manta «Скат-манта» r3 150🪙, shark «Большая белая акула» r3 160🪙, squid «Гигантский кальмар» r4 190🪙, whale «Синий кит» r4 350🪙 20–50 кг; первые 8 id не сдвинуты, новые — в конце; все поля заполнены; LOCATIONS 4 (Река 0 / Озёра 65536 / Море 131072 / Океан 196608), у каждой 6 hex в water',
|
||||
lc1.n === 32 && lc1.z === 'Судак,2,40,1,6' && lc1.c === 'Хариус,1,30,0.2,2' && lc1.k === 'Гигантский рак,3,200,0.2,1'
|
||||
&& lc1.b === 'Плотва,0,18,0.05,0.6' && lc1.r === 'Густера,0,24,0.1,0.9' && lc1.e === 'Вьюн,1,55,0.05,0.4'
|
||||
&& lc1.t === 'Линь,2,80,0.3,4' && lc1.n2 === 'Навага,3,250,0.3,2.5'
|
||||
&& lc1.rd === 'Краснопёрка,1,26,0.1,1.2' && lc1.ci === 'Сиг,2,90,0.5,3.5'
|
||||
&& lc1.h === 'Сельдь,0,30,0.1,0.8' && lc1.m === 'Скумбрия,1,70,0.3,2.5' && lc1.f === 'Камбала,2,130,0.5,5'
|
||||
&& lc1.tu === 'Тунец,3,180,2,12' && lc1.sw === 'Рыба-меч,4,320,5,20'
|
||||
&& lc1.mo === 'Гигантская мурена,0,25,1.5,6' && lc1.gr === 'Горбуна,0,25,2,10'
|
||||
&& lc1.turt === 'Морская черепаха,1,45,5,14' && lc1.an === 'Удильщик,2,110,1,4'
|
||||
&& lc1.oc === 'Гигантский осьминог,2,120,4,12' && lc1.ma === 'Скат-манта,3,150,6,15'
|
||||
&& lc1.sh === 'Большая белая акула,3,160,5,14' && lc1.sq === 'Гигантский кальмар,4,190,4,12'
|
||||
&& lc1.wh === 'Синий кит,4,350,20,50'
|
||||
ok('локации данные: SPECIES=32 (8 река + 10 озёра + 5 море + 9 океан, этапы 35/36) — zander «Судак» r2 40🪙, crayfish r3 200🪙, navaga r3 250🪙; озёрные новинки rudd «Краснопёрка» r1 26🪙, cisco «Сиг» r2 90🪙; морские herring «Сельдь» r0 30🪙, mackerel «Скумбрия» r1 70🪙, flounder «Камбала» r2 130🪙, tuna «Тунец» r3 180🪙, swordfish «Рыба-меч» r4 320🪙; океанские moray «Гигантская мурена» r0 25🪙, grouper «Горбуна» r0 25🪙, turtle «Морская черепаха» r1 45🪙, angler «Удильщик» r2 110🪙, octopus «Гигантский осьминог» r2 120🪙, manta «Скат-манта» r3 150🪙, shark «Большая белая акула» r3 160🪙, squid «Гигантский кальмар» r4 190🪙, whale «Синий кит» r4 350🪙 40–100 кг; этапы 38: лестница веса (Озёра ×8 / Море ×6, меч ×4 / Океан ×4, кит ×2; Река не меняется); первые 8 id не сдвинуты, новые — в конце; все поля заполнены; LOCATIONS 4 (Река 0 / Озёра 65536 / Море 131072 / Океан 196608), у каждой 6 hex в water',
|
||||
lc1.n === 32 && lc1.z === 'Судак,2,40,8,48' && lc1.c === 'Хариус,1,30,1.6,16' && lc1.k === 'Гигантский рак,3,200,1.6,8'
|
||||
&& lc1.b === 'Плотва,0,18,0.4,4.8' && lc1.r === 'Густера,0,24,0.8,7.2' && lc1.e === 'Вьюн,1,55,0.4,3.2'
|
||||
&& lc1.t === 'Линь,2,80,2.4,32' && lc1.n2 === 'Навага,3,250,2.4,20'
|
||||
&& lc1.rd === 'Краснопёрка,1,26,0.8,9.6' && lc1.ci === 'Сиг,2,90,4,28'
|
||||
&& lc1.h === 'Сельдь,0,30,0.6,4.8' && lc1.m === 'Скумбрия,1,70,1.8,15' && lc1.f === 'Камбала,2,130,3,30'
|
||||
&& lc1.tu === 'Тунец,3,180,12,72' && lc1.sw === 'Рыба-меч,4,320,20,80'
|
||||
&& lc1.mo === 'Гигантская мурена,0,25,6,24' && lc1.gr === 'Горбуна,0,25,8,40'
|
||||
&& lc1.turt === 'Морская черепаха,1,45,20,56' && lc1.an === 'Удильщик,2,110,4,16'
|
||||
&& lc1.oc === 'Гигантский осьминог,2,120,16,48' && lc1.ma === 'Скат-манта,3,150,24,60'
|
||||
&& lc1.sh === 'Большая белая акула,3,160,20,56' && lc1.sq === 'Гигантский кальмар,4,190,16,48'
|
||||
&& lc1.wh === 'Синий кит,4,350,40,100'
|
||||
&& lc1.first8 === 'carass,perch,bream,carp,pike,catfish,sturge,gold'
|
||||
&& lc1.lakeOrder === 'zander,chuch,crayfish,bleak,roundel,eelpout,tench,navaga,rudd,cisco'
|
||||
&& lc1.seaOrder === 'herring,mackerel,flounder,tuna,swordfish'
|
||||
|
||||
Reference in New Issue
Block a user