| 2026-09-07 |
feat(game): солнечный день, золотой час и тюнинг под прорези
...
- движок lightSim: dayNightAmbient — трёхопорный микс суток день → закат →
ночь (DUSK_PEAK 0.45, DEFAULT_DUSK_COLOR 0xe8c090), экспорт из index.ts
- locations.ts: дневные ambient к почти-белому (meadows 0xf7f4ea, ponds
0xf2f0e8, zvenets 0xf5f2ea, rust_grove 0xe9e6da, old_siding 0xe4e2d8),
ночи темнее под прорези (0x3e4658…); duskColor в LightingDef
- HERO_LAMP компактнее и ярче (радиус 1.6, интенсивность 0.85); радиусы
колокольчиков 1.4→1.0, окон 1.8→1.2, интенсивности выше
- палитра: ключевые цвета травы/пепла/воды подняты тоном (G*, P4–P5H, W1–W2),
арт перегенерирован; art-style.md синхронизирован
- чеки lighting/time под новые значения и трёхопорный микс; тесты золотого
часа (lightSim, GameLighting); docs/engine/render.md
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
8 hours ago
|
feat(engine): свет «тьма с прорезями» — RT-карта силы тьмы вместо аддитивных пятен
...
- lightSim: stepAlphas/HOLE_PROFILE/TONE_PROFILE (ступенчатые профили прорези
и тона), quantizeHoleAlpha (шаг 1/8) — детерминизм bake
- Lighting: RenderTexture хранит силу тьмы (fill → erase-прорези), display-
спрайт multiply с tint=ambient — смена цвета дня/ночи без перерисовки RT;
bake по dirty-подписи {round(x),round(y),scale·1/8,alpha·1/8}; day fast-path
(белый ambient — слой выключен); fallback без renderer (тесты в node)
- источник = прорезь (makeHoleTexture, 7 ступеней) + приглушённый тон
(makeToneTexture, TONE_GAIN 0.3) — спрайты под светом остаются читаемыми
- тесты: fake-renderer (bake/dirty/fast-path/destroy), профиль ступеней;
docs/engine/render.md + practices.md
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
9 hours ago
|
feat(agent): мост scene:walk/approach вместо tapTile, чеки и смоуки на кнопках
...
- SceneAgentView: команды scene:walk (A* + followPath) и scene:approach
(findPathToNeighbor, возврат тайла прибытия)
- GameAgent: walkTo через одну команду + waitFor тайла (без !moving —
step-переход срабатывает в moving), новый approach; tapTile удалён
- lib.mjs: t.interactAt/t.talkTo/t.useInteractable — координаты из снапшота
по id; fightNearest — подход, доворот facing коротким шагом к врагу
(клавиши — экранный вектор) и конус, до 3 попыток
- Чеки и смоуки переписаны с кликов на кнопки: interact, interact-world,
quest-bells, transitions, final, resonator, zoom, lighting, collision,
perf; smoke-act1 и final-smoke green (бой починен)
- Доки: agent.md (контракт моста), practices.md (доворот facing, две
линейки), plan.md (задача 15)
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
9 hours ago
|
feat(engine): зум камеры (Camera.setZoom/zoomTo) + шаг cameraZoom
...
apply ставит scale контейнера мира рядом с позицией (единственная точка
мир->экран); toScreen/follow(deadZone)/clamp пересчитаны на z (тряска не
масштабируется, финал анимации — ровно целевой зум, промежуточно 0.25).
Катсценный шаг cameraZoom (seconds: 0 — мгновенно, параллелен cameraMove).
Дефолт z=1 — поведение без зума не меняется.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
12 hours ago
|
feat(engine): скролл MenuList (ListScroll), hover->курсор, value-слот Button
...
ListScroll — чистое окно видимых строк (курсор всегда в окне, пересборка
короче зажимает offset). MenuList: visibleRows, кнопки строятся все — окно
двигает visible/y; hover переставляет курсор. Button: value справа
(настройки — setValue без пересборки списка). TextInput — вне среза.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
12 hours ago
|
feat(engine): шаг moveView + skip() раннера катсцен
...
moveView двигает вьюху (Movable — структурно, без Pixi-типов) в локальных
координатах родителя со своим ease. skip() дожимает сцену до конца:
движения — в конечные точки, until форсируются (скип конечен даже на
шаге-ожидании); scene:skipCutscene переведён на него.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
12 hours ago
|
feat(engine): шаг until + tween-хелпер катсцен
...
Шаг until ждёт внешнее событие (predicate каждый тик; timeout —
принудительное завершение), уже выполненный предикат не блокирует.
Интерполяция cameraMove вынесена в чистый applyEase (cutscene/tween).
Типовая пара «диалог посреди сцены»: call(talkTo) + until(диалог закрыт).
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
12 hours ago
|
perf(render): фиксация WebGL-бэкенда (preference: 'webgl')
...
О-5. Renderer теперь всегда выбирает WebGL: рендер детерминирован на всех
машинах, WebGPU-ветка Pixi не выбирается. Canvas/WebGPU-рендереры Pixi и так
лежат отдельными async-чанками (~19/13 КБ gz) — с фиксированным preference
браузер их никогда не загружает, отдельная вырезка не нужна.
Финальные прогоны: check:fast зелёный, agent:check 16/16, smoke-act1 (акт 1
до кат-сцены), final-smoke (3 поездки луга<->Звенец, бой, guard аудио) —
чисто; прод-сборка собирается. Perf (swiftshader, без изменений к О-4):
bootMs ~2.8 с, newGame->location ~230 мс, route 2 мс, fps 6-7 покой /
7-8 бой, heap 30-33 МБ.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
13 hours ago
|

perf(assets): параллельная загрузка и ленивый амбиент
...
О-4. AssetLoader грузил ключи await-в-цикле (~45 последовательных запросов);
BootScene — 4 атласа и карты друг за другом; 2.3 МБ WAV амбиента всех областей
декодировались до меню, хотя играется в первую очередь луговой.
- AssetLoader.load: один Assets.load(массив URL, onProgress) — Pixi грузит
параллельно, onProgress честный по группе; пост-обработка nearest по всем
результатам. API не изменился.
- BootScene.loadAll: Promise.all по группам (текстуры / 4 атласа / карты
областей / шрифт+аудио) с общим взвешенным прогресс-баром (0.6/0.15/0.15/0.1).
- Ленивое амбиент-аудио: регистрируются все ключи (дёшево), декодируются
только лупы стартовой области (data/audio.ts areaAudioKeys); остальные —
фоновый game.preloadAudio при входе в область (LocationScene.enter).
playAmbience/playLoop и раньше умели декодировать на месте (BufferCache
дедуплицирует in-flight) — теперь до меню их никто не ждёт.
Perf (swiftshader): bootMs 2947-3191 -> 2781; newGame->location без изменений
(~200 мс, аудио и карты грузились в boot и раньше). agent:check 16/16,
включая аудио-чеки с ленивым декодом.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
13 hours ago
|

perf(map): A* на мин-куче, findPathToNeighbor — один многоцелевой прогон
...
О-3. Открытый список искался линейным сканом (O(n) на узел, O(n^2) на путь);
findPathToNeighbor запускал до 8 полных A* по очереди.
- OpenHeap: мин-куча по f, O(log n) на push/pop; tie-break по seq (порядок
вставки) — обход остался детерминированным, как с линейным сканом.
- Общий каркас astar(grid, start, dirs, isGoal, h): цель и эвристика —
параметры; findPath и findPathToNeighbor без дублирования цикла.
- findPathToNeighbor: все 8 соседей цели — цели одного прогона, эвристика —
минимум манхэттена по целям (админисибельна, первый popped-goal оптимален).
- Соседство DIRS4/DIRS8 — константы модуля (было: spread каждый узел).
Тесты pathfinding — без правок ожиданий (11 старых зелёные), +2: выбор
ближайшего из 8 соседей за один прогон, старт-сосед цели — пустой путь.
Perf (swiftshader): routeMs 1-4 мс — в пределах baseline (маршруты короткие,
выиграш растёт с длиной пути).
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
13 hours ago
|

perf(render): culling тайлов карты, пустые оверлеи света, setDepth без пересортировки
...
О-2 рендера. На лугах ~900+ спрайтов карты рисовались целиком при ~250
видимых; пустые fullscreen-оверлеи света (ambient/vignette/пульс) рисовались
каждый кадр даже днём; ходьба взводила sortDirty каждый тик.
- IsometricTileMap.cullToRect: тайловое окно из экранных углов (screenToIso),
margin ±1 по X и вверх на высоту самого высокого tall; пересчёт только при
сдвиге окна; isTileVisible — для тестов/отладки; вызов из сцены
(updateCameraFollow). null — вернуть всё.
- Lighting: ambient.visible=false при белом тинте, vignette/pulseAmb — при
нулевой альфе (днём минус 2-3 fullscreen-квада).
- IsoDepthLayer.setDepth/setDepthRect: ранний выход при неизменном zIndex
(пиксельная сортировка детей не перезапускается каждый тик ходьбы).
- agent.mjs perf: возврат 0 (объект отчёта ломал process.exit).
Perf (swiftshader headless, до → после): fps бой 3-6 → 7-8, покой 5-6 → 5-7
(rasterizer swiftshader глушит выигрыш — он раскроется на целевом iGPU);
heap в пределах шума (28-31 МБ). Скриншот смоука не изменился, тайлы у краёв
камеры на месте.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
13 hours ago
|

perf: горячий путь 60 Гц без аллокаций (О-1)
...
Baseline после: fps покой 6 / бой 6 (было 5-6/3-6), heap 29 МБ — не хуже.
- Частицы: stepParticle — out-параметр визуала; Particles — компакт swap-pop
в один проход вместо двух filter за тик (~3400 объектов/сек раньше).
- Свет движка: upsertLight/upsertDarkSpot — апсейт def по полям вместо
{...def}; пульсы — итерация Map без спреда; lightFrame — out-кадр
(applyFrame reuse).
- Свет игры: дифф-наборы источников/пятна — два буфера со свопом (clear не
задевает активный), defsBuf/posBuf/lampDef — переиспользуемые.
- Тики: Tween/Updatables/shake — компакт на месте вместо filter каждый тик
(в Tween пограничный случай dt-в-delay сохранён — твин остаётся).
- Сцена: debug.setLines только при видимой панели; Atmosphere.updateHazard —
поиск зоны только при смене тайла героя.
- Бой: CombatFlow.updateRing — трогает Graphics только на переходах;
noiseFor — общий view вместо копии origin; EnemyBrain — переиспользуемые
INTENT_* константы + dirInto/moveIntentFrom (dirToward больше не аллоцирует).
- SceneRegistry.near — опциональный out-массив + reuse Set дедупликации.
- PlayerController: имя клипа ходьбы — кэш по facing.
- docs/engine/render.md: out-параметр stepParticle.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
13 hours ago
|
perf: реальный fps в агентном мосту + чек perf (baseline О-0)
...
- Engine.get fps() через FpsMeter (было 1/fixedStep = 60 всегда); AgentHost.fps.
- checks/perf.mjs: старт (страница+ассеты, newGame→location), A* (scene:route),
walkTo через луга, fps/heap в покое и в бою; agent.mjs perf (не в check —
бенчмарк, не гейт); lib.mjs open() с сырыми опциями openGame.
- eslint: performance: readonly для node-тулз.
Baseline (swiftshader headless, 960x540):
- старт: ассеты→меню ~3.0 с (raw 4.5 с с фиксированными 1.5 с сна моста),
newGame→location ~150-175 мс;
- A* путь через луга: 2-4 мс (10 узлов), walkTo: ~0.5 с;
- fps: 5-6 в покое, 3-6 в бою; heap: 28-31 МБ.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
13 hours ago
|

refactor: DialogueRunner -> модель + рантайм; LocationScene 890 -> 737
...
Движок:
- dialogue/graph.ts (новый): типы графа, VarCondition/DialogueEffects/... и
чистые предикаты hasConditions/evalConditions — их берут валидатор, dry-run
и квест-стадии; DialogueRunner.ts (459 -> 298) — только рантайм обхода.
Публичный API @rpg/engine не изменился; ui-and-dialogue.md дополнен.
Игра (сцена — оркестратор, механики — в systems/):
- systems/combat/CombatFlow.ts (новый): боевая обвязка героя — ввод атаки/
резонанс, реакции на урон (вспышка/шейк/отброс/смерть-респаун), hp-бар,
кольцо заряда, уровень угрозы для музыки, подписка combat:hurt.
- systems/LocationHud.ts (новый): название локации, подсказка, счётчик мотов
(одна вьюха в uiRoot вместо трёх полей сцены).
- makeExitGlow — маркер выхода интерьера уехал в InteractableViews.
Проверки: check:fast, agent:check 16/16, smoke-act1 (квест+кат-сцена),
final-smoke (3 поездки + реальный бой) — зелёные.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
14 hours ago
|
chore: ESLint (typescript-eslint, flat config) + npm run lint в check:fast
...
- eslint.config.js: recommended TS для src обоих пакетов + тулз; node/браузерные
глобали для .mjs-сценариев и редактора диалогов; no-undef выключен в TS
(глобали DOM/Vite проверяет tsc).
- Попутные находки (8): no-useless-assignment в waitFor-агентах (catch опирается
на init false), тернарник-выражение в StateMachine, причина ошибки в
AssetLoader ({ cause }), неиспользуемые consoleLogs/JSON_SPACE, let->const,
as const в тесте квестов.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
14 hours ago
|

К4: каркас withChecks для чек-сценариев, конфиги, доки
...
- apps/game/tools/lib.mjs: withChecks(name, fn) — сервер/браузер и помощники
(boot/goToArea/sleepEnemies/readAudioLog/waitAudioLog/expectInvariantsClean)
в одном месте; все 12 сценариев checks/*.mjs переписаны на шаги.
- Контекст сценария — только t.ctx (геттер): деструктуризация до boot давала
null — задокументировано в agent.md и practices.md.
- vitest.config.ts: явный include (src-тесты + тулзы с чистой математикой);
GameLoop.manual.test.ts -> GameLoop.timing.test.ts.
- Редактор диалогов: порт 5299 (5199 остаётся за dev-сервером смоуков) + фикс
разбора --port (indexOf(-1) -> NaN, npm run dialogues падал без аргумента).
- tsconfig: apps/game включает только src (движок типчекается своим проектом);
vite/client убран из движка (import.meta.env там нет).
- Докси: getting-started — regex-алиас вместо строкового; llms.txt + anim.md;
CLAUDE.md — полный список док; wav.mjs writeWav удалён (0 вызовов).
Проверено: agent:check 16/16, check:fast (692 теста), smoke-act1, редактор на 5299.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
15 hours ago
|

refactor(engine): глубокий рефакторинг К2 — расшивка аудио, FadeState, контракт fx, Engine.destroy
...
Расшивка переростков:
- AudioManager (479→279) → AudioManager (фасад) + Voices (полифония sfx,
pan в полёте в шине голоса) + Loops (слоты музыки/амбиента, кроссфейд)
+ decode.ts (BufferCache с in-flight дедупом) + types.ts;
load() → register()
- IsometricTileMap: проходимость делегирует новому StaticGrid
(map/collision.ts) — единый источник blocked/пропы/тени; headless-потребители
берут через gridOf(data), setTile виден сразу
- core/fade.ts (FadeState): вместо ручных fadeLeft/fadeTotal в Lighting
(ambient+виньетка) и SceneManager; повторный to() в середине фейда
стартует от текущего значения — без прыжка
Контракт fx-эффектов: cancel() = остановить + вернуть базовое + погасить
свою вьюху (FxFade без keep теперь гасит, FxTrail получил cancel,
SpriteFlash.cancel через TintFlash.finish); конец жизни — destroyed
везде. FxLayer: один реестр тикающихся (items), flashes — только индекс
перезапуска.
Прочее:
- Engine.destroy() — полная зачистка (exit сцены, fx/tweens/events,
ввод, FPS-метр, app.destroy) для HMR; stop() — только цикл
- FpsMeter считает всегда, отдаёт геттером fps; DebugOverlay принимает
FpsMeter и перерисовывает Text не чаще 0,5 с
- Lighting: кэши текстур на инстансе (модульный переживал destroy
рендерера), чинен двойной destroy darkFree/darkLayer
- InputManager: DOM-обработчики и инъекции идут через общие примитивы
press/release (клавиши, указатель)
- SpriteAnimator: xorshift → math/rng.createRng (единый ГПСЧ)
- Мёртвый код: FrameAnimation (файл+экспорт+тест), vec2(), pxToUnits,
screenToIsoExact, pushOutOfWalls
Тесты: +fade, +SceneManager (стек/фазы/глотание), +EventBus, +Engine
(stepTicks, мок pixi.js), +SpriteAnimator, +computeScale. Доки движка
обновлены в том же коммите (core.md: FadeState/Engine.destroy;
render.md: контракт fx, кэш Lighting; anim.md/maps.md/README.md — без
FrameAnimation; practices.md: тест ядра без WebGL, поле-метод).
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
16 hours ago
|

fix: утечки жизненного цикла (сцена, ввод, карта) + read-only тест карт
...
Игра:
- LocationScene: подписки EventBus снимаются в exit() (off из on);
DialogueSystem.destroy(); worldAudio.setLayers([]) в exit (лупы
амбиент-слоёв раньше звучали поверх следующей сцены); guard гонки
roomtone (сцена вышла до резолва промиса — луп гасится); в exit
добавлены moteLabel и chargeRing (в worldRoot — не вычищался).
- SaveSlotsScene: имена областей из areaOf() (интерьеры показывались
как «луга»).
Движок:
- InputManager: contextmenu-слушатель снимается в dispose().
- IsometricTileMap: плейсхолдер-графика ячейки хранится в CellSprites
и уничтожается при перерисовке (раньше накапливалась на setTile).
Тесты карт: fresh/round-trip read-only в npm test; запись файлов —
только при RUN_MAPS_GEN=1 (npm run maps) — обычный прогон больше не
мутирует рабочее дерево. agent:check: дублирующая проба maps удалена,
invariants — через сценарий checks/agent-invariants.mjs (был инлайн-
дубль, разошедшийся с ним).
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
16 hours ago
|
engine: FxLayer.destroy гасит все активные эффекты
...
Exit сцены — одна строка destroy({children:true}) вместо ручного clear.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
17 hours ago
|
engine: FX-слой one-shot эффектов + пресеты (render/fx/)
...
Инфраструктура визуальных эффектов: FxLayer (Container + Updatable, тикается
один раз в engine.fx, сам вычищает закончившиеся) и пресеты — кольцо/волна/
телеграф зоны (FxRing, repeat/shapes/fill), всплывающий текст (FxFloatText),
растворение/проявление с защитой от двойного destroy (FxFade), pop-появление
с овершутом (FxPop), шлейф (FxTrail), фабрики частиц spark/smoke/ember/dust.
Чистая математика сэмплов — fxSim.ts (headless-тесты); backOut в easing;
SpriteFlash + destroyed (самозачистка из реестра); Updatables.forEach.
Доки: render.md (раздел FX), core.md (engine.fx), recipes.md, practices.md.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
17 hours ago
|
feat(engine): dayNightFactor — чистая кривая суток для day/night
...
dayNightFactor(tHours, spec) — фактор ночи 0..1 (mod 24, линейный
рассвет/закат, непрерывен на стыке 24/0); DayNightSpec + дефолт
(рассвет 06–08, закат 18–20). Доки render.md дополнены.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
17 hours ago
|
feat(engine): импульсы света, виньетка и тёмные пятна в Lighting
...
- lightSim: PulseSpec {attack, hold?, decay} + pulseEnvelope/pulseTotal
(чистая трапеция-огибающая, тестируется в node)
- Lighting.pulseLight: вспышка в точке, занимает пул источников,
авто-снятие по концу огибающей; в frames() видна как pulse#N
- Lighting.pulseAmbient: аддитивная вспышка на весь экран, один слот
- Lighting.setVignette: затемнение краёв (makeVignetteTexture, ступенчатые
кольца) с лерпом по образцу ambient
- Lighting.upsertDarkSpot/removeDarkSpot: локальные тени (multiply),
отдельный пул maxDarkSpots, рисуются под источниками
- makeGlowTexture: 4 ступени + дизер-кольцо по внешнему радиусу
- доки: render.md (раздел «Импульсы, виньетка, тёмные пятна»),
recipes.md (вспышки / виньетка / тёмная аура)
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
18 hours ago
|
feat(game): интеграция освещения — статика областей, лампа героя, слой снапшота
...
- data/locations.ts: LightSourceDef/LightingDef (ambient + источники at/tiles,
радиус в юнитах, цвет/интенсивность/мерцание, whenFlag/notFlag), поле lighting в AreaDef
- systems/Lighting.ts: GameLighting — сборка активных источников (статика +
runtime add/remove + лампа героя в тёмных областях), проекция camera.toScreen
каждый тик, детерминированный seed мерцания по id, снапшот для моста
- LocationScene: lightView в renderer.lightRoot, тик света до ранних выходов
(свет живёт в диалогах/катсценах), зачистка в exit()
- агентный мост: слой lighting {ambient, sources}, инвариант lighting-bounded
- движок: Lighting.destroy принимает опции (делегирует в Container)
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
19 hours ago
|

feat(engine): слой освещения — ambient multiply + аддитивные источники
...
- render/lightSim.ts — чистая математика (образец particleSim): LightDef/
LightFrame, детерминированное мерцание flickerFactor (сумма синусов,
диапазон [1-amount, 1]), lightFrame, lerpAmbient, dimColor
- render/Lighting.ts — Pixi-адаптер: fullscreen ambient (Texture.WHITE,
blendMode multiply — цвет и есть яркость, фундамент day/night) + пул
аддитивных спрайтов света со ступенчатой пиксельной текстурой свечения
(makeGlowTexture, generateTexture resolution 1 + nearest)
- runtime API: setAmbient(color, fadeSec) с плавным лерпом, upsertLight/
removeLight/hasLight/setLightPos/setLightColor/setLightEnabled, frames()
для снапшота агента
- Renderer: третий корень lightRoot между worldRoot и uiRoot — экранное
пространство: ambient затемняет сцену, источники рисуются поверх, HUD/
fog/fade остаются читаемыми сверху
- Camera.toScreen(wx, wy) — проекция мировой точки с учётом камеры и
тряски для слоёв вне worldRoot (без лага на кадр); apply делегирует
математику в общие хелперы
- тесты: lightSim (мерцание/кадры/лерп/dim), Lighting (ambient fade,
пул, upsert/remove, мерцание, destroy), camera toScreen согласована
с apply в т.ч. при тряске
- доки: render.md (lightRoot, toScreen, раздел «Освещение», порядок
отрисовки), recipes.md (источник света)
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
19 hours ago
|
feat(game): агентный мост — pickChoiceByText, path/mood/tags в снапшоте, сценарий quest-bells
...
- DialogueSystem.pickChoiceByText(text) — выбор варианта по тексту реплики
(реплики героя в графе — выборы по библии мира; elder_first теперь ветвится)
- DialogueSnapshot += mood/tags/path — снапшот показывает настроение и путь
прохождения графа
- мост: команда сцены scene:pickChoiceByText, GameAgent.pickChoiceByText
(ждёт waitingForChoice, берёт по тексту), прокси AgentClient (+timeout
у runDialogue)
- новый checks/quest-bells.mjs — полный цикл квеста «Три цветка»: взятие
у Ирвина выбором по тексту -> сдача (кат-сцена посадки, цветы списаны)
-> Мила (полотно из do[] в сумке) -> эпилог trader_after; входит в agent:check
- доки: agent.md (снапшот диалога, команды, API), practices.md (typewriter
и выбор в сценариях; метод моста = 3 места), в том же коммите
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
20 hours ago
|

feat(game): квест-стадии на DialogueConditions; чистка флагов знакомств + миграция v2
...
- QuestStage.ready — данные (DialogueConditions) вместо замыкания; стадия
сдачи — whenVars flowers >= 3. questDialogueFor(state, npc, world?)
фильтрует через движковый evalConditions.
- Двойной учёт знакомств устранён: npcs.flagKey → FLAGS.met_elder/met_mila,
графы уже ставят их сами — talkTo перестал ставить флаг вручную;
camelCase-ключи metElder/metTrader удалены из ids.ts.
- data/migrations.ts: registerStateMigrations + migrateV1toV2
(metElder→met_elder, metTrader→met_mila); Game регистрирует при старте.
- Движок: фикс цикла миграций в GameState.load — выход при достижении
dataVersion (миграция, забывшая повысить version, не портит данные);
контракт задокументирован в core.md, оси версий — в assets-audio-save.md.
- Тесты: две миграции по порядку, незабампленная миграция не применяется
дважды, старый сейв v1 читается, готовность стадии через whenVars.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
20 hours ago
|
feat(engine): презентация диалогов — mood/tags/textKey, typewriter, клавиатурный выбор
...
- DialogueNode/Choice: mood, tags, textKey (+speakerKey у узла); view
получает mood/tags, текст резолвится hooks.resolve (fallback — ключ,
inline-текст первичен). Узел с одним textKey — реплика.
- ui/reveal.ts: чистый revealText(full, elapsedMs, cps) — тесты в node.
- DialogueBox v2: typewriter (update/skipReveal/revealing), moodColors,
клавиатурный курсор выбора (ListCursor: moveCursor/activate/cursorIndex),
высота панели растёт от контента.
- Игра: DialogueSystem.advance — сначала догнать печать, при выборе
активировать курсор; up/down листают варианты; typewriter 40 cps +
moodColors в LocationScene.
- Тесты: revealText, mood/tags в view, textKey/speakerKey; доки
ui-and-dialogue.md.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
20 hours ago
|
feat(engine): диалоговые эффекты do[] + onEffect и результат onFinish
...
- DialogueEffectOp (giveItem/takeItem/sound/toast/custom) — эффекты как
данные в графе; движок только эмитит через onEffect(op, at), исполнение
— EffectSink игры. Никаких Inventory в движке.
- DialogueHooks { world, resolve } третьим аргументом конструктора +
setHooks (заменяет параметр world из D1).
- DialogueResult { lastNodeId, path, picks }: путь показанных узлов и
выборы игрока; onFinish(graph, result), result читается и после конца.
- Порядок эффектов: setFlags → clearFlags → setVars → do[].
- Тесты: порядок onEffect у узла/выбора, unknown kind не падает,
path/result/перезапуск; доки ui-and-dialogue.md + practices.md.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
20 hours ago
|
feat(engine): условия диалогов v2 — whenVars/hasItem + чистый evalConditions
...
- DialogueWorld { hasItem } — предикаты мира (сумка) инъекцией; без world
hasItem-условия ложны.
- DialogueConditions: whenVars (несколько переменных, AND) и hasItem;
легаси-whenVar не тронут.
- evalConditions/hasConditions — чистые функции для квест-стадий, dry-run
и валидатора; рантайм делегирует проверку им.
- Тесты: AND по переменным, hasItem с/без world, evalConditions на чистом
GameState; доки ui-and-dialogue.md + practices.md.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
20 hours ago
|
game(scenes): 4 сцены меню на движковом каркасе MenuSceneBase
...
- MenuScene/SettingsScene/SaveSlotsScene/InventoryScene — наследники
MenuSceneBase: скопированная навигация (up/down/advance/menu + гард
перехода) уехала в движок; у каждой сцены только build() и хуки.
- SaveSlotsScene: слоты из data/saves.ts, подписи из slotMeta с фолбэком
на полный load (старые сейвы), пустые слоты disabled (курсор их
пропускает), autosave защищён canDelete.
- Меню сейвов пишет мету (title/savedAt/version/extras.area).
- MenuList.setItems сохраняет фокус при пересборке (громкость больше не
сбрасывает курсор на первую строку).
- Агентный мост не тронут: снапшот из inventory.all, команды те же.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
20 hours ago
|