Этап 41 ревизия 4 (финальная): размер рыбы — узловая таблица по степеням двойки, интерполяция по log2(веса). fishSizeFromW(w) + FISH_SIZE_KNOTS=[[0,16],[1,20],[2,25],[3,35],[4,48],[5,64],[6,80],[7,100],[8,120],[9,170],[log2(1000),256]] ([log2(w/0.1), длина px]) — явная таблица пользователя (веса степени двойки, «погрешность 10%»): степенная шкала ревизии 3 не покрыла таблицу в середине (на 25.6 кг было бы +24% к цифрам; локальный показатель кривой 0.26..0.61) — заменена узлами. Длина силуэта = 2.45·fs (тело 2·fs + хвост), интерполяция линейна по log2(w) — узлы бьются ТОЧНО (0% отклонения, проверено: 16/20/25/35/48/64/80/100/120/170/256 px), кривая монотонна (1000 точек); веса вне 0.1–100 кг клампятся в края (нижний кламп — только золотая рыбка 0.05–0.1 кг). Распределение: 0.1 кг → 16 px (0.25× лодки), 0.2 → 20, 0.4 → 25, 0.8 → 35, 1.6 → 48, 3.2 → 64 px (= лодка), 6.4 → 80, 12.8 → 100, 25.6 → 120, 51.2 → 170 px, 100 кг (кит max) → 256 px (4× лодки); 1 кг → 39.2 px, 10 кг → 92.9 px; босс 20–50 кг → 113–168 px (1.77–2.6×). Диапазон 16..256 px = ×16. Тесты: пинов на размер силуэта нет — 193/193 PASS, exit 0; check.mjs OK; скриншоты пересняты (кадр reel: 12.5 кг ≈ 99 px ≈ 1.55× лодки); узлы 100/3.2/0.1 кг проверены живыми кадрами headless-Chromium (численный контроль 16.0/64.0/256.0 px). Доки: DEVLOG (Этап 41 — таблица, интерполяция, пометки ревизий 3/4).

This commit is contained in:
2026-09-20 20:41:14 +03:00
parent 31d60a6333
commit cfdb93bf7c
32 changed files with 19 additions and 12 deletions
+5 -5
View File
@@ -751,16 +751,16 @@ git add -A && git commit -m "что изменилось" && git push
## Этап 41. Размер рыбы при вываживании — от абсолютного веса (0.1 кг → минимум, 100 кг → 2× лодки)
**Задача:** «размер рыбы в воде (когда её вытягиваешь) должен соответствовать её весу в кг, а не минимальному/максимальному весу вида; диапазон больше: 0.1 кг — максимально маленькая как сейчас, 100 кг — в 2 раза больше лодки». Первая ревизия этапа была лог-шкалой (средние рыбы 1–10 кг подросли до 0.9–1.4× лодки — «все рыбы сильно большие»); по решению заменена на линейную. Вторая ревизия: минимум опустили ещё — 0.1 кг с длины 19.6 px до **8 px** («ещё меньше»). Третья ревизия: **100 кг → 256 px** (×2 к 128, т.е. 4× лодки) и **1 кг → 20 px**; предложенная квадратичная шкала 3 якоря не держит монотонно (парабола по w: пик ~420 px на 61 кг, затем схождение к 256 px на 100 кг — кит меньше «средней» рыбы; квадратичная по ln w: провал до ~4 px у 0.2 кг) — взята степенная `L = 3.95 + 16.05·w^0.598`, точная по всем трём якорям и монотонная.
**Задача:** «размер рыбы в воде (когда её вытягиваешь) должен соответствовать её весу в кг, а не минимальному/максимальному весу вида; диапазон больше: 0.1 кг — максимально маленькая как сейчас, 100 кг — в 2 раза больше лодки». Первая ревизия этапа была лог-шкалой (средние рыбы 1–10 кг подросли до 0.9–1.4× лодки — «все рыбы сильно большие»); по решению заменена на линейную. Вторая ревизия: минимум опустили ещё — 0.1 кг с длины 19.6 px до **8 px** («ещё меньше»). Третья ревизия: **100 кг → 256 px** (×2 к 128, т.е. 4× лодки) и **1 кг → 20 px**; предложенная квадратичная шкала 3 якоря не держит монотонно (парабола по w: пик ~420 px на 61 кг, затем схождение к 256 px на 100 кг — кит меньше «средней» рыбы; квадратичная по ln w: провал до ~4 px у 0.2 кг) — взята степенная `L = 3.95 + 16.05·w^0.598`, точная по всем трём якорям и монотонная. Четвёртая ревизия (финальная): **явная узловая таблица пользователя** (веса — степени двойки, длины 16..256 px, «погрешность 10%»): степенная формула не покрыла таблицу в середине (на 25.6 кг было бы +24% к цифрам таблицы; локальный показатель кривой гуляет 0.26..0.61) — заменена линейной интерполяцией по log₂(веса) между узлами: узлы бьются точно (0%), кривая монотонна, вне 0.1–100 кг — кламп в края таблицы.
**Решения:**
- **Новый хелпер `fishSizeFromW(w)`** (секция «ОТРИСОВКА», перед `drawHookedFish()`): **степенная по ДЛИНЕ силуэта** (длина = 2.45·fs: тело 2·fs + хвост): `fs = (3.95 + 16.05·(max(0.1,w))^0.5981)/2.45`. Якоря: **0.1 кг → 8 px** (мелочь-мелкая), **1 кг → 20 px**, **100 кг → 256 px = 4× лодки 64 px** из `drawBoat()` (`bx−32..bx+32`). Веса <0.1 кг клампятся в минимум (затрагивает только золотую рыбку 0.05–0.1 кг). Размер лодки (64 px) достигают ~9.1 кг; распределение: 0.1–1 кг 8..20 px (мелочь), 10 кг ≈ 1.05× лодки, 50 кг ≈ 2.7×, 100 кг = 4×.
- **`drawHookedFish()`:** `fs = fishSizeFromW(reel.w)` (было `8+(reel.w/sp.wMax)*10+sp.rarity`). Убраны **`+sp.rarity`** (редкая рыбка не обязана быть крупнее — размер = вес) и **босс-множитель ×1.5** (М4): босс весит 20–50 кг (1.57–2.67× лодки по степенной шкале), с ×1.5 босс-рак 30 кг выглядел бы крупнее кита 100 кг — противоречие «крупнее вес → крупнее рыба»; драматизм босса теперь несёт аура + бейдж + фазы. Золотая аура босса сохранена (читает новый `fs`).
- **Новый хелпер `fishSizeFromW(w)` + таблица `FISH_SIZE_KNOTS`** (секция «ОТРИСОВКА», перед `drawHookedFish()`): узлы `[log2(w/0.1), длина px] = [0,16],[1,20],[2,25],[3,35],[4,48],[5,64],[6,80],[7,100],[8,120],[9,170],[log2(1000),256]`, интерполяция линейна по log₂(веса) (узлы — степени двойки), длина силуэта = 2.45·fs (тело 2·fs + хвост). Узлы: **0.1 кг → 16 px** · 0.2 → 20 · 0.4 → 25 · 0.8 → 35 · 1.6 → 48 · 3.2 → 64 (= лодка) · 6.4 → 80 · 12.8 → 100 · 25.6 → 120 · 51.2 → 170 · **100 кг → 256 px = 4× лодки 64 px** из `drawBoat()` (`bx−32..bx+32`). Веса вне 0.1–100 кг клампятся в края таблицы (нижний кламп затрагивает только золотую рыбку 0.05–0.1 кг).
- **`drawHookedFish()`:** `fs = fishSizeFromW(reel.w)` (было `8+(reel.w/sp.wMax)*10+sp.rarity`). Убраны **`+sp.rarity`** (редкая рыбка не обязана быть крупнее — размер = вес) и **босс-множитель ×1.5** (М4): босс весит 20–50 кг (113–168 px = 1.77–2.6× лодки по узловой шкале), с ×1.5 босс-рак 30 кг выглядел бы крупнее кита 100 кг — противоречие «крупнее вес → крупнее рыба»; драматизм босса теперь несёт аура + бейдж + фазы. Золотая аура босса сохранена (читает новый `fs`).
- **Не тронуто:** подплывающая рыба до подсечки (APPROACH/BITE, `12+rarity*2.5` — вес к моменту подплывания ещё не зароллен, `hook()` роллит его при подсечке), фоновые рыбы `rand(8,22)`, 🐟-маркер на дистанционной полосе, emoji-карточка улова. Баланс/роллы не задеты: `wMin/wMax` остаются только в `rollWeight()`/`hook()`/`st0`.
**Размеры (полная длина силуэта; лодка = 64 px):** 0.1 кг → 8 px (0.13×) · 1 кг → 20 px (0.31×) · 5 кг → 46 px (0.72×) · 10 кг → 67.6 px (1.05×) · 20 кг (босс min) → 100 px (1.57×) · 50 кг (босс max) → 170.6 px (2.67×) · 100 кг (кит max) → 256 px (**4×**). Диапазон 8..256 px = ×32 (в исходной формуле ~20..54 px = ×2.7). Тяжёлая рыба в конце вываживания (позиция `boatX+24`, поверх лодки) заметно перекрывает лодку — осознанный эффект «вывезена рядом с лодкой»; на мобильном 360 px кит ≈ 71% ширины экрана.
**Размеры (полная длина силуэта; лодка = 64 px):** 0.1 кг → 16 px (0.25×) · 0.2 → 20 px (0.31×) · 0.4 → 25 px (0.39×) · 0.8 → 35 px (0.55×) · 1.6 → 48 px (0.75×) · 3.2 → 64 px (**1× = лодка**) · 6.4 → 80 px (1.25×) · 12.8 → 100 px (1.56×) · 25.6 → 120 px (1.88×) · 51.2 → 170 px (2.66×) · 100 кг (кит max) → 256 px (**4×**). Диапазон 16..256 px = ×16; между узлами — линейно по log₂(веса) (проверено: узлы 0% отклонения, монотонность 1000 точек). Тяжёлая рыба в конце вываживания (позиция `boatX+24`, поверх лодки) заметно перекрывает лодку — осознанный эффект «вывезена рядом с лодкой»; на мобильном 360 px кит ≈ 71% ширины экрана.
**Тесты:** пины на размер силуэта в `tests/test.js` отсутствуют (проверено: `drawFishShape`/`drawHookedFish`/`fishReelPos` не ассертятся; `reel.w` в тестах — для физики). Итого **193/193 PASS, exit 0**; `check.mjs` OK; `tools/balance.mjs` — пины без изменений (Река 65.1/125.9 · Озёра 194.1/249.0 · Море 297.3/476.7 · Океан 329.1/552.7). Скриншоты перегенерированы (`gen-screenshots.js`, 30 кадров; кадр `reel` форсит `reel.w=12.5` — рыба ~77 px ≈ 1.2× лодки).
**Тесты:** пины на размер силуэта в `tests/test.js` отсутствуют (проверено: `drawFishShape`/`drawHookedFish`/`fishReelPos` не ассертятся; `reel.w` в тестах — для физики). Итого **193/193 PASS, exit 0**; `check.mjs` OK; `tools/balance.mjs` — пины без изменений (Река 65.1/125.9 · Озёра 194.1/249.0 · Море 297.3/476.7 · Океан 329.1/552.7). Скриншоты перегенерированы (`gen-screenshots.js`, 30 кадров; кадр `reel` форсит `reel.w=12.5` — рыба ~99 px ≈ 1.55× лодки).
## Этап 42. SVG-портреты рыб в дневнике и карточке улова
+14 -7
View File
@@ -2579,13 +2579,20 @@ function fishReelPos(){
return {x:x,y:y};
}
/* Размер силуэта пойманной рыбы — от АБСОЛЮТНОГО веса (кг), не от wMin/wMax вида.
Степенная шкала по ДЛИНЕ силуэта (длина = 2.45·fs: тело 2·fs + хвост), якоря:
0.1 кг → 8 px (мелочь), 1 кг → 20 px, 100 кг → 256 px = 4× лодки 64 px.
Буквально квадратичная шкала 3 якоря не держит (парабола по w пиковала ~420 px на 61 кг
и сходилась к 100 кг; по ln w провисала до 4 px у 0.2 кг) — степенная A+B·w^0.598 точна и монотонна.
Веса <0.1 кг клампятся в минимум. Средние (длина): 5 кг → 46 px, 10 кг → 67.6 px (размер лодки ~9.1 кг),
20 кг → 100 px, 50 кг → 170.6 px. */
const fishSizeFromW=w=>(3.95+16.05*Math.pow(Math.max(0.1,w),0.5981))/2.45;
УЗЛОВАЯ ТАБЛИЦА (длина силуэта = 2.45·fs: тело 2·fs + хвост), узлы по степеням двойки:
0.1 кг → 16 px · 0.2 → 20 · 0.4 → 25 · 0.8 → 35 · 1.6 → 48 · 3.2 → 64 (= лодка) ·
6.4 → 80 · 12.8 → 100 · 25.6 → 120 · 51.2 → 170 · 100 → 256 px = 4× лодки 64 px.
Интерполяция линейна по log2(веса) — узлы степени двойки, кривая монотонна, узлы бьёт точно.
Веса <0.1 кг клампятся в 16 px, >100 кг — в 256 px. */
const FISH_SIZE_KNOTS=[[0,16],[1,20],[2,25],[3,35],[4,48],[5,64],[6,80],[7,100],[8,120],[9,170],[Math.log2(1000),256]]; // [log2(w/0.1), длина px]
function fishSizeFromW(w){
const t=Math.log2(Math.max(0.1,Math.min(100,w))/0.1);
for(let i=1;i<FISH_SIZE_KNOTS.length;i++){
const t0=FISH_SIZE_KNOTS[i-1][0],l0=FISH_SIZE_KNOTS[i-1][1],t1=FISH_SIZE_KNOTS[i][0],l1=FISH_SIZE_KNOTS[i][1];
if(t<=t1) return (l0+(l1-l0)*(t-t0)/(t1-t0))/2.45;
}
return 256/2.45;
}
function drawHookedFish(){
if(state!=='REEL'||!reel) return;
const sp=reel.sp;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 148 KiB