Этап 48: размер рыбы в аквариуме — от абсолютного веса (общая шкала вываживания) + десктоп ×2. (1) aqFishSize(sp,w) → aqFishSize(w): вместо w/wMax+rarity читает общую с drawHookedFish кривую Этапа 41 — fishSizeFromW(w) (узловая таблица FISH_SIZE_KNOTS, log2 веса) — так «крупнее вес → крупнее рыба» работает в банке как в воде. Формула: clamp(fishSizeFromW(w)·AQ_FISH_SCALE·k, AQ_FISH_SIZE_MIN·k, min(W,H)·AQ_FISH_SIZE_REL·k); константы AQ_FISH_SCALE=0.7, AQ_FISH_SIZE_MIN=7, AQ_FISH_SIZE_REL=0.16. (2) Ревизия по просьбе владельца: мобильный вид одобрен как есть, поэтому десктопный множитель k=AQ_DESKTOP_SCALE=2 включается только при min(W,H)>=AQ_DESKTOP_MIN=600 (иначе k=1) — телефон 390×844 и mobile-landscape 800×450 остаются 1×, десктоп 1280×720 получает жильцов вдвое крупнее (щука 7.4 кг: 40→79 px DOM, кит 100 кг: 125→293 px). Оба клампа (MIN и REL) масштабируются на k вместе с телом — иначе на десктопе мелкая рыба застревала бы на мобильном минимуме, а верхний кламп резал бы увеличенных гигантов. Порог по min(W,H), а не IS_TOUCH: не ломает headless-скриншоты и работает при resize (initAquarium). (3) Обновлены 3 вызова — aqBuildFishEl/drawAquarium/aqTap (sp больше не нужен как аргумент; в drawAquarium sp остался для sp.glow); радиус тапа (size*1.2+10), рамка выбора и ореол свечения читают ту же size — согласованы автоматически. Таблицу/кривую НЕ дублируем (fishSizeFromW объявлен в «ОТРИСОВКА» раньше секции аквариума). Имя aqFishSize сохранено (глобал тестов/AGENTS). (4) Не тронуто: FISH_SIZE_KNOTS/fishSizeFromW/drawHookedFish (вода), wMin/wMax, формат/миграция save.aqFish, баланс/роллы/физика. (5) Тесты не правились — §AQ размеры не пинит (проверяются .aqfish/use href/варианты/transform; aqTap жмёт центр): 222/222 PASS, exit 0. check.mjs OK; balance.mjs — пины без изменений (Река 65.1/125.9 · Озёра 194.1/249.0 · Болото 240.3/328.9 · Море 297.3/476.7 · Океан 329.1/552.7). (6) Скриншоты: перегенерированы только кадры aquarium (6 PNG, оба набора — паттерн ревизии Этапа 47); остальные 84 кадра возвращены (набор не побитово-детерминирован из-за анимаций). (7) Доки: AGENTS.md (пункт «Аквариум» — формула + константы + десктоп-множитель), README.md (размер по весу, на десктопе ×2), DEVLOG (Этап 48 с таблицей мобильный/десктоп).
@@ -34,7 +34,7 @@
|
||||
- Погода: дождь (🌧️) — шанс особых рыб ×3; туман (🌫️) — клёв ×1,5; событие идёт 120 с, следующее — раз в 2–4 минуты — <img src="screenshots-wiki/mobp-weather-rain.png" width="200" alt="дождь">
|
||||
- Скины лодки и поплавка 🎨 — вкладка «Скины» в лавке: лодка «Красная лодка» 800 🪙 / «Лодка-домик» 2000 🪙, поплавок «Красный» 500 🪙 / «Неоновый» 1500 🪙 — чистая косметика (без эффектов на геймплей)
|
||||
- Локации: Озёра (65 536 🪙) — судак, хариус, гигантский рак; Море (131 072 🪙) — скумбрия, тунец, рыба-меч; Океан (196 608 🪙 = ×1.5 Моря) — глубоководные гиганты: синий кит, гигантский кальмар, скат-манта, гигантский осьминог; Волшебное болото (90 000 🪙) — светящиеся обитатели (светлячок, лунная рыбка, изумрудный светляк, фиалковый огонёк, дух болота), ночью рыбы и фоновые стайки светятся разными цветами, вода — тёмно-моховая, на поверхности кувшинки с лилиями и прыгающие лягушки; у каждой — свой состав днём/ночью, своя вода (цвет, волны, рыбы в толще) и свой берег (Река — лесистая полоса, Озёра — холмы, Море — парусник и чайки, Океан — открытый горизонт, Болото — мшистый берег с камышом), чем дальше вода — тем доходнее улов; покупка и переключение во вкладке 🗺️ лавки — <img src="screenshots-wiki/mobp-lake.png" width="200" alt="озёра"> <img src="screenshots-wiki/mobp-sea.png" width="200" alt="море"> <img src="screenshots-wiki/mobp-ocean.png" width="200" alt="океан"> <img src="screenshots-wiki/mobp-swamp.png" width="200" alt="болото днём"> <img src="screenshots-wiki/mobp-swamp-night.png" width="200" alt="болото ночью">
|
||||
- **Аквариум 🐠** — декоративная сцена коллекции: с карточки улова кнопка «🐠 В аквариум» кладёт бесплатную копию экземпляра (монеты за улов платятся как обычно, модель C — экономика не тронута); старт 6 мест, расширение до 12 за монеты (1 500 → 100 000 🪙); жильцы — те же SVG-портреты с вариантной перекраской, что на карточке улова; тап по рыбе — вес и редкость, «Выпустить» освобождает слот (без монет). Вход — из хаба «Меню»; чистая косметика — <img src="screenshots-wiki/mobp-aquarium.png" width="200" alt="аквариум">
|
||||
- **Аквариум 🐠** — декоративная сцена коллекции: с карточки улова кнопка «🐠 В аквариум» кладёт бесплатную копию экземпляра (монеты за улов платятся как обычно, модель C — экономика не тронута); старт 6 мест, расширение до 12 за монеты (1 500 → 100 000 🪙); жильцы — те же SVG-портреты с вариантной перекраской, что на карточке улова, и размером от абсолютного веса (та же шкала `fishSizeFromW`, что при вываживании; на десктопе жильцы вдвое крупнее); тап по рыбе — вес и редкость, «Выпустить» освобождает слот (без монет). Вход — из хаба «Меню»; чистая косметика — <img src="screenshots-wiki/mobp-aquarium.png" width="200" alt="аквариум">
|
||||
- Квесты дня (3 шт, генерация по дню), стрик входов со «щитом», 38 достижений с бонусами
|
||||
- Дневник рыбака — коллекция, максимальный улов по каждому виду, статистика
|
||||
- Магазин с вкладками: Лавка 🛒, Дневник 📔, Награды 🎯, Достижения 🏅, Скины 🎨, Локации 🗺️
|
||||
|
||||
@@ -904,3 +904,40 @@ git add -A && git commit -m "что изменилось" && git push
|
||||
**Скриншоты:** перегенерированы `node tests/gen-screenshots.js` — кадры дневника/карточки/аквариума и локаций содержат новые портреты.
|
||||
|
||||
**Доки / файлы:** 32 × `assets/fish/fish-*.svg`, `index.html` (только блок FISH-SVG), `tools/fish-preview.html`, этот DEVLOG.
|
||||
|
||||
## Этап 48. Размер рыбы в аквариуме — от абсолютного веса (ревизия Этапа 46)
|
||||
|
||||
**Задача:** владелец заметил, что размер жильцов банки считался от `w/wMax` + `rarity`, хотя в игре уже есть узловая таблица абсолютного веса `FISH_SIZE_KNOTS`/`fishSizeFromW()` (Этап 41, используется `drawHookedFish()`). Нужно переиспользовать её, чтобы «крупнее вес → крупнее рыба» работало в банке так же, как в воде.
|
||||
|
||||
**Решение — `aqFishSize(w) = clamp(fishSizeFromW(w)·0.7·k, 7·k, min(W,H)·0.16·k)`, где `k=2` только для десктопа:**
|
||||
- `aqFishSize` (`index.html`, секция АКВАРИУМ) больше не считает `rel=w/wMax` и не прибавляет `rarity` — только читает общую кривую воды. Формула: `s = fishSizeFromW(w)×AQ_FISH_SCALE×k`, кламп `[AQ_FISH_SIZE_MIN×k, min(W,H)×AQ_FISH_SIZE_REL×k]`.
|
||||
- Новые константы: `AQ_FISH_SCALE=0.7` (масштаб к шкале воды), `AQ_FISH_SIZE_MIN=7` (мелкая рыба читаема/тапается), `AQ_FISH_SIZE_REL=0.16` (верхний кламп = 16% меньшей стороны экрана).
|
||||
- **Ревизия (по просьбе владельца): десктоп ×2.** Мобильный вид одобрен владельцем как есть, поэтому множитель `k=AQ_DESKTOP_SCALE=2` включается только при `min(W,H)>=AQ_DESKTOP_MIN=600` (иначе `k=1`). Так десктоп 1280×720 и mobile-landscape 800×450 (min=450 < 600) остаются различными: банка на большом экране пустует сильнее, и жильцы там вдвое крупнее. Оба клампа (и `MIN`, и `REL`) масштабируются на `k` вместе с телом — иначе на десктопе `MIN`-рыба «отставала» бы от верхней границы, а `REL`-кламп резал бы только что увеличенных гигантов.
|
||||
- Сигнатура `aqFishSize(sp,w)` → `aqFishSize(w)`: `sp` больше не нужен; обновлены 3 вызова — `aqBuildFishEl` (`f.w`), `drawAquarium` (`f.w`, `sp` остался для `sp.glow`), `aqTap` (`f.w`). Имя `aqFishSize` сохранено (глобал в тестах/AGENTS).
|
||||
- Таблицу/кривую НЕ дублируем: `fishSizeFromW` объявлен в секции «ОТРИСОВКА» раньше секции аквариума — доступен как есть.
|
||||
|
||||
**Размеры (DOM-портрет = `2·size × 1.25·size`).** На телефоне 390×844 мобильный кламп (cap 125 px) режет только 100-кг кит; на десктопе 1280×720 cap = 460 px — в диапазоне 0.1–100 кг кламп не срабатывает вовсе:
|
||||
|
||||
| Вес | Моб. DOM | Десктоп DOM |
|
||||
|---|---|---|
|
||||
| 0.1–0.5 кг | 14×9 (MIN) | 28×18 (MIN×2) |
|
||||
| 1 кг | 16×10 | 32×20 |
|
||||
| 3 кг | 27×17 | 54×33 |
|
||||
| 4.8 кг | 33×20 | 66×41 |
|
||||
| 8 кг | 40×25 | 79×49 |
|
||||
| 16 кг | 49×30 | 97×61 |
|
||||
| 40 кг | 78×49 | 157×98 |
|
||||
| 72 кг | 119×75 | 239×149 |
|
||||
| 100 кг | **125×78 (clamp)** | 293×183 |
|
||||
|
||||
Мобильный/ландшафт-800 не тронуты ревизией. Ранее (w/wMax+rarity) 100 кг кит давал 73 px длины, теперь на десктопе 292 px, на мобильном 125 px; мелочь 0.1–0.7 кг на мобильном сглаживается `MIN=7` до 14 px, на десктопе `MIN×2` — до 28 px.
|
||||
|
||||
**Не тронуто:** `FISH_SIZE_KNOTS`/`fishSizeFromW`/`drawHookedFish` (вода), `wMin/wMax`, формат/миграция `save.aqFish`, баланс/роллы/физика, `drawFishShape` (в банке по-прежнему не используется).
|
||||
|
||||
**Тесты:** не правились — размеры в §AQ никто не пинит (проверяются `.aqfish`, `use href`, варианты, `transform`; `aqTap` жмёт точно центр). Итого **222/222 PASS, exit 0**; `node tools/check.mjs` OK; `tools/balance.mjs` — пины без изменений (Река 65.1/125.9 · Озёра 194.1/249.0 · Болото 240.3/328.9 · Море 297.3/476.7 · Океан 329.1/552.7).
|
||||
|
||||
**Скриншоты:** перегенерированы только кадры `aquarium` (оба набора, 6 PNG) — набор в целом не побитово-детерминирован (анимации/время), остальные кадры не тронуты (паттерн ревизии Этапа 47). На кадре `save.aqFish` = карась 0.6 кг, щука 7.4 кг, золотая 0.8 кг, светлячок 2.4 кг.
|
||||
|
||||
**Доки:** AGENTS.md (пункт «Аквариум» — формула `aqFishSize(w)` + `AQ_FISH_SCALE`/`AQ_FISH_SIZE_MIN`/`AQ_FISH_SIZE_REL`), README.md (фича «Аквариум» — размер по весу), этот DEVLOG.
|
||||
|
||||
**Файлы:** `index.html`, `AGENTS.md`, `README.md`, `docs/DEVLOG.md`, `screenshots-wiki*/{desk,mobl,mobp}-aquarium.png`.
|
||||
|
||||
@@ -4164,11 +4164,21 @@ function aqApplyVariant(el,variant){
|
||||
for(const id of AQ_VARIANT_CLASS) el.classList.toggle('var'+id,variant.id===id);
|
||||
}
|
||||
|
||||
/* Размер силуэта в банке: от веса к пределу вида (w/wMax) и редкости; кламп — чтобы влезал */
|
||||
function aqFishSize(sp,w){
|
||||
const rel=sp.wMax>0?Math.min(1,Math.max(0,w/sp.wMax)):0.4;
|
||||
const s=8+rel*22+sp.rarity*1.6;
|
||||
return Math.max(7,Math.min(Math.min(W,H)*0.11,s));
|
||||
/* Размер силуэта в банке — от АБСОЛЮТНОГО веса, как у рыбы при вываживании
|
||||
(drawHookedFish, Этап 41): fishSizeFromW(w) × AQ_FISH_SCALE, кламп
|
||||
[AQ_FISH_SIZE_MIN, min(W,H)×AQ_FISH_SIZE_REL], чтобы мелочь была читаема,
|
||||
а гигант не занял всю банку. Редкость/предел вида (wMax) больше не влияют.
|
||||
ДЕСКТОП: на большом экране (min(W,H) >= AQ_DESKTOP_MIN) всё ×2 — мобильный
|
||||
вид одобрен владельцем как есть, десктопный получает более крупных жильцов. */
|
||||
const AQ_FISH_SCALE=0.7; // масштаб к шкале воды (fishSizeFromW)
|
||||
const AQ_FISH_SIZE_MIN=7; // нижний кламп (мелкая рыба читаема/тапается)
|
||||
const AQ_FISH_SIZE_REL=0.16; // верхний кламп = 16% меньшей стороны экрана
|
||||
const AQ_DESKTOP_MIN=600; // min(W,H) >= 600 px — экран считаем десктопным
|
||||
const AQ_DESKTOP_SCALE=2; // десктоп: жильцы вдвое крупнее
|
||||
function aqFishSize(w){
|
||||
const k=Math.min(W,H)>=AQ_DESKTOP_MIN?AQ_DESKTOP_SCALE:1; // только десктоп ×2
|
||||
const s=fishSizeFromW(w)*AQ_FISH_SCALE*k;
|
||||
return Math.max(AQ_FISH_SIZE_MIN*k,Math.min(Math.min(W,H)*AQ_FISH_SIZE_REL*k,s));
|
||||
}
|
||||
/* Позиция жильца в DOM: та же система координат, что у canvas (W/H — CSS-px), элемент
|
||||
центрируется на (x,y) и зеркалится по направлению. Вызывается каждый кадр из updateAquarium. */
|
||||
@@ -4179,12 +4189,12 @@ function aqPlaceFish(el,rt){
|
||||
}
|
||||
/* DOM-элемент жильца: <div class=aqfish><svg viewBox=0 0 160 100><use href="#fish-<id>"/></svg></div>.
|
||||
Те же SVG-портреты и вариантное оформление, что на карточке улова (Э42 + ревизия). Размер —
|
||||
aqFishSize (прежняя шкала): длина 2·size, высота по соотношению 160:100. */
|
||||
aqFishSize(w) от абсолютного веса: длина 2·size, высота по соотношению 160:100. */
|
||||
function aqBuildFishEl(i){
|
||||
const f=save.aqFish[i];
|
||||
const sp=SPECIES.find(s=>s.id===f.id); if(!sp) return null;
|
||||
const variant=VARIANTS.find(v=>v.id===f.v)||VARIANTS[0];
|
||||
const size=aqFishSize(sp,f.w);
|
||||
const size=aqFishSize(f.w);
|
||||
const el=document.createElement('div');
|
||||
el.className='aqfish'; el.dataset.i=i;
|
||||
el.style.width=(size*2).toFixed(1)+'px';
|
||||
@@ -4328,7 +4338,7 @@ function drawAquarium(){
|
||||
const f=save.aqFish[i], rt=aqFishRt[i];
|
||||
if(!f||!rt) continue;
|
||||
const sp=SPECIES.find(s=>s.id===f.id); if(!sp) continue;
|
||||
const size=aqFishSize(sp,f.w);
|
||||
const size=aqFishSize(f.w);
|
||||
if(sp.glow) drawGlowAura(rt.x,rt.y,size*1.5,sp.glow,glowIntensity()); // болотные — свечение вида
|
||||
if(aqSel===i){ // рамка выбранной рыбы
|
||||
ctx.strokeStyle='#c8fff4'; ctx.lineWidth=2;
|
||||
@@ -4355,7 +4365,7 @@ function aqTap(x,y){
|
||||
const rt=aqFishRt[i], f=save.aqFish[i];
|
||||
if(!f) continue;
|
||||
const sp=SPECIES.find(s=>s.id===f.id); if(!sp) continue;
|
||||
const r=aqFishSize(sp,f.w)*1.2+10;
|
||||
const r=aqFishSize(f.w)*1.2+10;
|
||||
const d=(rt.x-x)*(rt.x-x)+(rt.y-y)*(rt.y-y);
|
||||
if(d<r*r&&d<bd){ bd=d; best=i; }
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 167 KiB |