diff --git a/docs/plan.md b/docs/plan.md index 278d22d..5aad660 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -743,3 +743,21 @@ - **Трек 21 — сборка**: сейв полного состояния, меню (подтверждено фидбеком: пользователь заметил, что меню нет — игра стартует сразу в мир), полный прогон, сравнительные скриншоты с v1 как референсом. + **Меню и сейв закрыты раньше срока** — раундом полировки (ниже); из + трека 21 осталось: полный прогон и сравнительные скриншоты с v1. +- **Раунд полировки** (2026-09-10, «улучшим игру технически и визуально»): + недоделки визуального слоя трека 18 + техполировка. Меню (Esc) + + автосейв/«Продолжить» (движковый SaveManager, localStorage `rpg2:`; + `GameSave` — полный снапшот сюжета: область/тайл, флаги+вары, сумка, + часы, правки цветков — применяются до `setArea`, мир строится цветущим); + виньетка опасности + пелена наката (`screenFx.ts`, DOM); частицы v1 + Atmosphere (`ambientParticles.ts`: мотыльки лугов, дымка прудов — клочья + якорятся на клетки воды); шиммер воды прудов (`waterShimmer.ts`: редкие + блики-вспышки по фазам sin). Мост: `ambientInfo`, `menuInfo`, + `startGame(load)`; страница стартует в меню — всем гейтам и `shot.mjs` + сначала `startGame(false)`. Грабли: **`addVoxelCloud` после пересоздания + облака возвращает castShadow=true** — гасить после каждого update; + **z-fighting копланарных instanced-кубов с world-мешем** (блик на воде + мерцает на кромках пруда от порядка отрисовки, кадр-равенства падают) — + сдвиг EPS 0.03 из плоскости; **зомби-vite переживает process.exit + тулзы** — kill по PID из `ss -tlnp`. diff --git a/v2/CLAUDE.md b/v2/CLAUDE.md index 633b6d5..763856d 100644 --- a/v2/CLAUDE.md +++ b/v2/CLAUDE.md @@ -606,6 +606,25 @@ центрируется на центр тайла. - **solidAt для blocked-гейта — колонка НАД землёй (y≥2)**: земля y0-1 сплошная всюду; проверка с y=0 даёт «всё заблокировано». +- **Voxel-облако после пересоздания снова бросает тень**: `addVoxelCloud` + ставит `castShadow=true`, а при росте списка ОБЛАКО пересоздаётся + (update подменяет mesh) — частицам/бликам `castShadow=false` ставить + после КАЖДОГО `update`, не только в конструкторе (иначе чёрные квадраты + теней на воде). +- **Z-fighting копланарных instanced-кубов с world-мешем**: куб облака, + объёмно перекрывающийся с кубом мира (блик/клочок на воде), имеет + копланарные вертикальные грани; у culled-меша воды боковые грани + выживают только на кромках пруда — там пиксели «мерцают» от порядка + отрисовки, который меняется при пересоздании облака (кадр-равенства + гейтов падают на 19 px). Лечится сдвигом позы на EPS 0.03 из плоскости + куба воды. +- **Зомби-vite держит порт после `process.exit()` тулзы** (дочерний vite + переживает выход): `startDevServer` падает «vite упал (code 1)» — + убивать по PID из `ss -tlnp` (`pkill -f` убивает и свою сессию). +- **`encodePng` принимает Buffer** (`rgba.copy is not a function` с + Uint8Array) — `Buffer.from(rgba)` перед записью. +- **Node-скрипты вне v2 не резолвят `@rpg/engine`** (TS-исходники): + одноразовые пробы класть в `apps/*/tools/` и запускать с cwd внутри v2. ## Ревью-раунд (2026-09-10): багфиксы движка и игры @@ -646,6 +665,39 @@ камера входа в область на float-цели без округления (микро-скачок в начале фейда-ин). +## Раунд полировки (2026-09-10): меню/сейвы, живые слои воды и воздуха + +Игра `v2/apps/game`, недоделки визуального слоя трека 18 + техполировка: + +- **Меню (Esc) + автосейв**: страница стартует в меню («Продолжить» disabled + без сейва / «Войти в мир»), Esc — пауза с автосейвом в слот `auto` + (движковый `SaveManager`, localStorage с префиксом `rpg2:`), `#menu` + поверх канваса (CSS `#menu[hidden]{display:none}` — грабля flex+hidden). + Пейлоад — полный снапшот сюжета `src/save.ts` (`GameSave`): область/тайл + героя, флаги+вары, сумка, часы суток, правки цветков по областям; + правки цветков применяются ДО `setArea` (мир строится уже цветущим). + Мост: `menuInfo` {open, hasAuto}, `startGame(load)`, `gotoMenu()` + (сбрасывает открытый диалог). +- **Виньетка опасности + пелена наката** `src/screenFx.ts` (DOM-слои + `#vignette`/`#haze`, alpha из состояний наката — полотно без полотна). +- **Атмосферные частицы** `src/ambientParticles.ts` (v1 Atmosphere): + мотыльки лугов (тёплые, орбитальный дрейф вокруг камеры) и дымка прудов + (серые клочья 2×2, якорятся на клетки поверхности воды, блуждают на + месте — раньше дрейфовал по окну камеры и вылезал на берег). +- **Шиммер воды прудов** `src/waterShimmer.ts`: редкие светлые блики на + поверхности воды (кубы-«вспышки» по фазам sin от времени цикла, + детерминировано; STEP/GLOW подобраны визуально: ~30 бликов на пруд). +- Мост: `ambientInfo` {ash, ambient: {kind, count} | null, + shimmer: {cells, visible} | null}. Гейты кадр-равенств гасят живые слои + как раньше (`setAsh(false)` гасит и ambient, и shimmer). +- **Все гейты игры и `tools/shot.mjs` обязаны начинаться с + `startGame(false)`** — страница стартует в меню, тики/скриншоты до входа + снимают панель меню. + +Грабли раунда (добавлены в «Известные грабли»): тени voxel-облаков после +пересоздания, z-fighting копланарных кубов над world-мешем, зомби-vite, +`encodePng` Buffer, резолв `@rpg/engine` вне v2. + ## Рабочие привычки - Для задач из 3+ шагов веди список задач и иди автономно. diff --git a/v2/apps/game/index.html b/v2/apps/game/index.html index da76914..cf94b79 100644 --- a/v2/apps/game/index.html +++ b/v2/apps/game/index.html @@ -27,6 +27,22 @@ border-radius: 4px; white-space: nowrap; } + /* Пелена наката: полупрозрачная пелена низин (alpha из screenFx) */ + #haze { + position: fixed; + inset: 0; + background: #5a6a78; + opacity: 0; + pointer-events: none; + } + /* Виньетка опасности: края экрана темнеют (накат без полотна) */ + #vignette { + position: fixed; + inset: 0; + background: radial-gradient(ellipse at center, transparent 55%, rgba(10, 10, 14, 0.9) 100%); + opacity: 0; + pointer-events: none; + } /* Фейд перехода областей: чёрный слой поверх всего */ #fade { position: fixed; @@ -113,16 +129,56 @@ } #dialog .choices button:hover { border-color: #d99a32; color: #fff; } #dialog .continue { margin-top: 8px; color: #626c4c; font-size: 13px; } + /* Меню: Esc — пауза с автосейвом; старт страницы — выбор входа */ + #menu { + position: fixed; + inset: 0; + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + justify-content: center; + background: rgba(10, 10, 14, 0.72); + z-index: 10; + } + #menu[hidden] { display: none; } /* display:flex перебивает атрибут hidden */ + #menu h1 { + margin: 0 0 8px; + color: #d99a32; + font: bold 26px monospace; + letter-spacing: 2px; + } + #menu button { + width: 260px; + padding: 8px 0; + background: #23232c; + color: #c9c9d2; + border: 1px solid #44454e; + border-radius: 4px; + font: 16px monospace; + cursor: pointer; + } + #menu button:hover:not(:disabled) { border-color: #d99a32; color: #fff; } + #menu button:disabled { opacity: 0.4; cursor: default; } + #menu p { margin-top: 10px; color: #626c4c; font: 13px monospace; }
+ + +