Лестница веса по локациям (Этап 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) и править тесты в том же коммите.
|
||||
|
||||
Reference in New Issue
Block a user