| 2026-09-07 |
feat(game): акт 2 — Клинт, размещения NPC по флагу
...
- механика NPC_PLACEMENTS/npcsForArea в npcs.ts: NPC появляется в области
при поднятом флаге (act2_hook от Милы), мёрж в LocationScene при загрузке;
- SceneObjects.addNpc: размещения попадают в клик-роутинг и кэш занятых
тайлов (иначе клик по Клинту резолвился как движение);
- диалоги clint_first/clint_repeat (крючок «пол-тона»), спрайт в атласе
npcs_sheet (6 кадров, clint_idle);
- validate: placements проверяются на bounds/walkable, def-ы — на ссылки
флагов и диалогов;
- interact.mjs: шаг «флаг -> Клинт в роще -> диалог -> met_clint».
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
15 hours ago
|
feat(game): акт 2 — карты Ржавой рощи, Старого разъезда и зала Машины (A1)
...
- Новые тайлы: WIRE (бухта кабеля, tall 0.6), RAIL (рельсы), STAIRS
(люк-ступени), MACHINE (печь-автоматон, tall 2.5) — текстуры в gen.mjs,
реестр sizes.mjs (wire), арт-библия дополнена.
- Карты: buildRustGroveMap (плотный мёртвый лес, тропа Звенец->разъезд,
поляна мастерской Клинта), buildOldSidingMap (рельсы, вагоны-времянки,
люк в подземелье), buildMachineHallMap (интерьер с Машиной в два тайла);
в Звенце расчистлен восточный выход.
- Области: rust_grove / old_siding / machine_hall (ambient темнее лугов,
гул Машины tower_hum у люка и в зале, накат у рельсов, враги с
патрулями) + переход Звенца на восток.
- Чек transitions: цепочка Звенец -> роща -> разъезд -> зал и обратно.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
16 hours ago
|
feat(game): зум в ClickRouting/cullMap/свет/GameAgent + чек zoom.mjs
...
Интеграция зума камеры в игру (Z3):
- ClickRouting: pointerToWorld/resolvePixelTile делят экранные px на zoom
(тела не трогаются — их getBounds уже экранные); deps.worldZoom.
- LocationScene: cullMap окно /z; debug-клавиша Z — уровни 1<->2;
walker'ы актёров тикаются всегда (исполнители until).
- Lighting/CombatFlow: радиусы источников и импульсов * zoomLevel
(lightRoot вне worldRoot, масштабируется вручную).
- GameAgent: tapTile переводит тайл в экранные px с учётом z;
публичный worldRootView() для чеков.
- Чек zoom.mjs: debugZoom -> scale=2, tapTile по NPC в Звенце открывает
диалог, walkTo доходит, возврат на 1.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
16 hours ago
|
feat(game): инвентарь со скроллом сумки, настройки без пересборки списка
...
Сумка — visibleRows: 6 (ломалось при 7+ предметах), журнал фиксированно
ниже сумки, подсказка/результат — статус-строка под заголовком. Настройки:
список собирается один раз, громкости обновляются setValue (value-бары
переехали в value-слот кнопки).
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
17 hours ago
|
feat(game): диалог и катсцена в одном тике + ходоки актёров
...
LocationScene.update больше не взаимоисключающие ветки: диалог тикается
и во время кат-сцены (паттерн «диалог посреди сцены» — call(talkTo) +
until(диалог закрыт)), геймплей на паузе пока активна хоть одна система.
CutsceneActors: ActorWalker ведёт вьюху NPC по A*-пути (тики сценой,
depth-слой по смене тайла), npcWalkSteps/npcMoveView — фабрики шагов.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
17 hours ago
|
feat(engine): шаг moveView + skip() раннера катсцен
...
moveView двигает вьюху (Movable — структурно, без Pixi-типов) в локальных
координатах родителя со своим ease. skip() дожимает сцену до конца:
движения — в конечные точки, until форсируются (скип конечен даже на
шаге-ожидании); scene:skipCutscene переведён на него.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
17 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
17 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
18 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
18 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
18 hours ago
|
refactor: типизация agent-слоя — снапшот без as unknown as
...
- Снапшот-типы (HeroSnapshot и пр.) — type alias'ы вместо interface: у alias'ов
есть неявная индексная подпись, поэтому они структурно совместимы с
JsonValue/SnapshotLayer без кастов.
- Фабрики слоёв (heroLayer/enemiesLayer/npcsLayer/dialogueLayer/lightingLayer/
collisionLayer) возвращают SnapshotLayer & { <ключ>: <тип> } — рассинхрон
слоя и типа теперь ошибка компиляции.
- SceneAgentView.agentSnapshot(): LocationSnapshot собирается из слоёв
напрямую и возвращается как есть (7 кастов убрано); GameAgent.waitFor
и invariants пользуются типизированным GameSnapshot (ещё 4).
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
19 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
19 hours ago
|

refactor(game): К3 — декомпозиция LocationScene, гигиена, валидация контента
...
Декомпозиция (1338 -> ~870 строк, сцена — оркестратор вьюх):
- systems/LocationAudio: слои/roomtone/шорохи/музыка угрозы (порядок тиков сохранён)
- systems/InteractableViews + data/assetSets: фабрики вьюх NPC/интерактивов,
enemyTextures из данных (префикс кадров из EnemyKindDef.frames)
- systems/Atmosphere: пепел/мотыли/дымка, накат, виньетка
- systems/quest/FlowerQuest: сбор/посадка/кат-сцена сдачи
- pixelTile -> чистый resolvePixelTile + pointerToWorld в ClickRouting (+ юнит-тесты)
Хардкоды сюжета -> data: QUEST_PLANT_TILES/QUEST_TOWER_TILE/QUEST_TOWER_GREEN,
harvestTile в AreaDef (правило «цветок только в ponds»)
Гигиена: HealthBar.setHp не перерисовывает при том же hp; eps-прибытие
вместо float-равенства (PlayerController, FaunaSystem) со снапом;
agentCommand — таблица Record вместо switch на 130 строк; удалён мёртвый код
(GameLighting.addLight/removeLight, Interactables.all, SceneObjects.byKind,
VolumeKey, questTaken/questDone/inventoryItems, реэкспорт MenuScene)
Валидация контента (тихие ошибки): патрули (границы+проходимость), хазарды,
источники света (дубли id, границы, пустые, whenFlag/notFlag), requiresItem
переходов/накатов, return-правило интерьеров, аудио-ключи (ambience/theme/
слои/sfx реакций и реплик/roomtone)
Проверено: check:fast (692 теста), agent:check 16/16, smoke-act1
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
20 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
20 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
21 hours ago
|
game: миграция эффектов на FxLayer-пресеты + починка нетикаемых мотылей
...
- LocationScene: два слоя (fx в мире, uiFx в uiRoot); тост — floatText
с readSeconds и clear() перед новым (раньше наслаивались); подбор
интерактива — fx.fade; флэш героя при уроне — fx.flash.
- CombatViews: кольцо резонанса, hitBurst/deathBurst и флэши врагов —
через fx.ring/fx.burst/fx.flash (ручной массив колец удалён);
трупы растворяются через FxFade (keep — вьюху чистит сцена).
- Баг: эмиттер мотылей над потревоженным пеплом добавлялся в мир без
engine.fx — не анимировался и не уничтожался; теперь fx.burst с
emberPreset, доигрывает и исчезает сам.
- docs/demo.md: строка матрицы про render/fx/.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
21 hours ago
|
feat(game): предмет «часы» и сумка с выбором
...
Ирвин выдаёт медные часы с квестом (giveItem в elder_first.json);
useItemLine — чистый хелпер юза́бельных предметов (часы → «Время: H:MM»,
остальные — null). InventoryScene — список с курсором MenuList и строкой
результата; время в сумке заморожено (локация не тикается). checks/time.mjs:
выдача часов и заморозка времени в сумке.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
21 hours ago
|
feat(game): непрерывное время суток вместо флага evening
...
GameClock (1 с = 1 игровая минута, старт 8:00) на Game; GameLighting
смешивает day/night ambient по кривой dayNightFactor через деп timeHours,
ночные источники — whenNight, лампа по фактору ≥ 0.5. Сейв v4 (clock),
миграция state v3 удаляет evening; мост: s.time, scene:setTime,
инвариант time-bounded, сценарий checks/time.mjs.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
22 hours ago
|
feat(game): ночь на улицах, свет колокольчиков, сценарий освещения
...
Контент: nightAmbient у лугов/прудов/Звенца, источник bells на тайлах
колокольчиков (whenFlag evening), окна домов горят только вечером.
Сценарий checks/lighting.mjs переписан под день/ночь (8 шагов: вечер
лугов, окна Звенца, виньетка через scene:damagePlayer). Доки: demo.md
(матрица), plan.md (10b), art-style.md (дизер-кольцо, ночной ambient,
B2 колокольчиков). agent:check 12/12, smoke-act1 зелёный.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
22 hours ago
|

feat(game): вечер после сдачи квеста, виньетка, световые импульсы в бою
...
- ids.ts: флаг FLAGS.evening; граф elder_hand_in.json ставит его узлом accept
(«вернусь до темноты» — контент-driven, валидатор держит флаг упомянутым)
- LightingDef.nightAmbient: GameLighting.update ведёт ночную цель ambient
(guard по последней цели, фейд 6 c); лампа героя — в тёмной области или вечером
- тёмные ауры зон наката: пятно на каждый тайл HazardDef (multiply под светом)
- виньетка: низкий hp (0.3) / накат без полотна (0.35), фейд 0.5 c
- импульсы: combat:hurt → тёплая вспышка в точке врага, резонанс → вспышка
у героя + подъём ambient, урон герою → красная вспышка на весь экран
- LocationScene: maxLights: MAX_LIGHTS (движковый пул больше не теряет
источники), setTileTracked — индекс layerTilePos живой при смене тайлов
- мост: scene:damagePlayer (полный боевой путь), снапшот lighting.vignette,
инвариант lighting-bounded расширен виньеткой 0..1
- доки: agent.md (vignette/scene:damagePlayer), practices.md (ночь = флаг,
импульсы = тот же пул)
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
23 hours ago
|
feat(game): контент освещения + сценарий проверки lighting
...
- locations.ts: ambient областей (луга/пруды/Звенец/интерьеры), окна трёх
домов Звенца, очаг дома Ирвина (hearth_low -> hearth_high по флагу квеста),
лампа прилавка лавки, холодный отсвет башни после звонка
- LocationScene: вызов lightView.update(dt) — время/лерп ambient/кадры
к спрайтам (сценарий поймал замороженное мерцание)
- tools/checks/lighting.mjs: 6 шагов (ambient областей, окна, тёмный интерьер,
мерцание с допуском, лампа следует за героем, очаг ярче после сдачи),
регистрация в agent.mjs
- доки: demo.md (матрица), plan.md (задача 10 + точки расширения day/night),
art-style.md (свет — оверлей, цвет из гаммы жизни, интенсивность ≤ 1.1)
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
23 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
23 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>
|
feat(game): диалоги в JSON + dialogueRules (чистый анализ графов) + CLI dry-run
...
- графы диалогов — 6 JSON-файлов в data/dialogues/ (4 пробела + \n),
реестр dialogues.ts собирается JSON-импортами
- новый data/dialogueRules.ts: чистые checkGraph/analyzeGraph — реестры
параметрами, одна истина для валидатора, CLI и будущего редактора
- validate.ts делегирует графы в checkGraph; новые инварианты:
textless-cycle (error), silent-end (warn), string-unknown, item-unknown,
do-item/do-custom-unknown, quest-stage-unreachable (error)
- CLI npm run dialogues:dry [<id>] — прогон графов через DialogueRunner
на пресетах состояния (fresh / quest-taken+flowers / quest-done)
- доки: ui-and-dialogue.md (раздел «Графы в JSON и dry-run»), practices.md,
CLAUDE.md
Co-Authored-By: Claude Code <noreply@anthropic.com>
|

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>
|
feat(game): do[]-эффекты в контенте через EffectSink; QuestEffect-свитч удалён
...
- data/effects.ts: реестр DIALOGUE_CUSTOM (plant_flowers) — имена сюжетных
эффектов, как FLAGS/VARS.
- systems/DialogueEffects.ts: DialogueEffectSink + applyDialogueEffect
(give/take в сумку, sound, toast, custom; unknown kind — тихий игнор,
валидатор = error).
- DialogueSystem: registerSink + runner.onEffect → sink.
- Контент на do[]: elder_hand_in.accept → custom plant_flowers;
trader_first.give_cloth → giveItem cloth. Удалены QuestEffect,
EXTRA_EFFECTS, questEffectFor, поле QuestStage.effect и ручной switch
в onDialogueFinished (custom откладывается до конца диалога — кат-сцена
не рвёт реплику).
- Валидатор: do[].id give/take ∈ ITEMS, custom ∈ DIALOGUE_CUSTOM (error).
- Сценарий interact-world: первый advance теперь догоняет печать —
двойное нажатие.
- Тесты: give/take в Inventory, custom до sink, unknown игнор.
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
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>
|
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>
|
game: формат сейва — в saveData.ts, слоты конфигом, дефолт хазарда в контент
...
- scenes/saveData.ts: SAVE_VERSION/ReturnToData/SaveData/normalizeSave вынесены
из MenuScene (формат сейва больше не живёт в файле меню); MenuScene
реэкспортирует для совместимости.
- data/saves.ts: SAVE_SLOTS/slotTitle/canDelete вместо хардкода
['autosave','slot1'...] в SaveSlotsScene (дважды).
- HazardDef.requiresItem стал обязательным — контент уже задаёт 'cloth',
мёртвый фолбэк ?? 'cloth' в LocationScene снят (дефолт — в контенте).
- Тест: слоты/подписи/canDelete (510 всего).
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
UX-пачка: подсказки, клик-зоны, моты, коллизия домов, сундук, тихая вода
...
- Тосты держатся по длине текста (до 6 с) + подсказка сбора колокольчиков
при первом входе в пруды (флаг hint_bells).
- Клик по «телу» объекта: RouterDeps.pixelTile как фолбэк на голом клике —
bbox тел NPC/интерактивов (без подписей) + ремап на высокий тайл (дома).
- Моты: заметная насыпь с пульсирующей искрой, HUD-счётчик, вьюха
растворяется с искрами при поднятии (InteractSink.onConsumed).
- Движок: TallSpec.shadow — тайл (tx-1,ty-1) за домом блокируется
(shadowTilesOf в collision.ts + isWalkable карты, тесты).
- Интерьеры: бронзовый контур проёма + подпись «выход».
- Спрайт сундука (tiles/chest.png) вместо ящика-плейсхолдера.
- Вода прудов: radius 2.5, volume 0.22 — только вплотную и тихо.
Co-Authored-By: Claude Code <noreply@anthropic.com>
|