diff --git a/docs/plan.md b/docs/plan.md
index 560c3e2..577cb00 100644
--- a/docs/plan.md
+++ b/docs/plan.md
@@ -602,3 +602,53 @@
спек-синтез без WAV, позиционный звук и шаги в демке, DEV-шпион).
Все 14 треков переноса подсистем v1 на v2 закрыты — дальше: воспроизведение
демки v1 («Пепельные луга») на v2 по мере движения.
+
+#### Воспроизведение демки v1 на v2 — дорожная карта (2026-09-09)
+
+Цель: на движке v2 воспроизвести демо-срез v1 («Пепельные луга») — 8
+областей (5 уличных + 3 интерьера), 3 NPC, 2 квеста, переходы, пикапы,
+накат-зоны, фауна, день/ночь, темы областей. Референс — v1; точность
+визуала не критична (воксель ≠ спрайт), но раскладка тайлов v1
+(позиции NPC/переходов/квестов) переносится данными — сходится сюжет.
+Состав референса (разведка 2026-09-09): `v1/apps/game/src/data/` —
+locations.ts (AREAS: meadows/ponds/zvenets/rust_grove/old_siding/
+machine_hall/house_elder/shop, переходы step/click с requiresFlag/
+requiresItem/lockedText, hazard-зоны наката, свет/атмосфера/fauna),
+map.ts (TILES ~35 id, BLOCKED, tall-объекты), карты assets/maps/*.map
+(RLE, parseMap), npcs.ts (Ирвин, Мила; Клинт по флагу act2_hook),
+dialogues/ (9 графов: elder × 3, trader × 3, clint × 3), ids.ts
+(18 флагов + vars), items.ts (7 предметов), interactables.ts,
+quests.ts + quest/FlowerQuest.ts, ResonatorQuest.ts, music.ts (THEMES —
+спек-музыка, рендер уже портирован в v2), fauna, пепел/моты.
+
+Треки (нумерация продолжается с 15):
+
+- **Трек 15 — карты и области** ✅ (2026-09-10, коммит): конвертация карт v1
+ (`apps/game/tools/convertMaps.mjs` → `src/data/maps.json`, RLE как есть),
+ воксельный генератор областей (`src/areas.ts`: 1 тайл = 3×3 вокселя,
+ земля y0-1, вода слоем y=2 = blocked, штампы — компактные генераторы
+ в тайл 3×3 mapTree/mapBush/mapBoulder + дома/колодец/башня/машина/
+ рельсы/стены/декор, всё детерминировано); новое приложение `apps/game`
+ (демка не тронута — её 11 гейтов завязаны на сценку); переходы step/click
+ с фейдом out→swap→in и стеком возврата 'return' (`src/main.ts`); мост
+ areaInfo/gotoArea/setManual/stepTicks/heroInfo/heroTeleport/setInput/
+ setFollow/setSun/solidAt/interact; гейт `tools/checks/areas.mjs` —
+ 16 проверок (детерминизм пересборки, blocked, зажим у воды, step-переходы
+ луга⇄Звенец/луга⇄пруды, люк→зал→return по стеку, click «E», интерьер).
+ Уроки: экранная ось (1,0) — диагональ мира (триггер срезается углом —
+ чистые оси комбинированным стиком); штампы шире тайла запирают проходы —
+ контент карты строго в тайл 3×3; solidAt — колонка над землёй (y≥2).
+- **Трек 16 — NPC и квестовый каркас**: NPC-дефы (появление по флагам),
+ перенос 9 диалоговых графов v1, флаги/вары, тосты; гейт.
+- **Трек 17 — интерактив, пикапы, квест «Три цветка»**: INTERACTABLES
+ (сундуки, записка, колодцы, прилавок, bell_rope), сбор цветов/мотов,
+ FlowerQuest (взять → собрать в прудах → посадить → сдать с кат-сценой),
+ предметы (cloth от Милы), накат-зоны (без cloth — голос); гейт.
+- **Трек 18 — время суток и атмосфера**: GameClock (день/вечер/ночь),
+ источники света (колокольчики ночью), частицы пепла/тумана, темы
+ областей (renderMusic) + локальные амбиент-слои (спек-лупы вместо WAV);
+ гейт.
+- **Трек 19 — акт 2**: Клинт, ResonatorQuest, тон-деревья, Машина
+ (machine_hall), фауна (олени); гейт.
+- **Трек 20 — сборка**: сейв полного состояния, меню, полный прогон,
+ сравнительные скриншоты с v1 как референсом.
diff --git a/v2/CLAUDE.md b/v2/CLAUDE.md
index 8515f43..d896e53 100644
--- a/v2/CLAUDE.md
+++ b/v2/CLAUDE.md
@@ -49,7 +49,9 @@
node apps/demo/tools/checks/scenes.mjs # скриншот-гейт сцен/меню (/tmp/v2_scenes_*.png)
node apps/demo/tools/checks/combat.mjs # скриншот-гейт боя (/tmp/v2_combat_*.png)
node apps/demo/tools/checks/audio.mjs # гейт аудио (DEV-шпион, /tmp/v2_audio_*.png)
+node apps/game/tools/checks/areas.mjs # гейт областей игры (/tmp/v2_areas_*.png)
npx vite --port 5299 # демка (в apps/demo), http://localhost:5299
+npx vite --port 5315 # игра «Пепельные луга» (в apps/game)
```
## Структура
@@ -302,6 +304,32 @@
резонанс → bell_low, лог ограничен 24 записями. Мост: `audioUnlock()`
(разблокировка контекста гейтом без жестов), `audioInfo()` (кольцевой
буфер 24 записей {key, volume, pan, rate} фактических запусков звука).
+- `apps/game` — игра «Пепельные луга» на v2: воспроизведение демки v1
+ (референс — `v1/apps/game`, раскладка карт переносится данными).
+ `tools/convertMaps.mjs` — конвертация карт v1 (assets/maps/*.map, RLE)
+ → `src/data/maps.json` (прогон при правке референса: `node tools/convertMaps.mjs`);
+ `src/areas.ts` — генератор мира области из раскладки: 1 тайл v1 = 3×3
+ вокселя (UNIT=3, ноги на y=2), земля y0-1, вода слоем y=2 (непроходима,
+ как v1 blocked), штампы объектов — компактные генераторы в тайл 3×3
+ (mapTree/mapBush/mapBoulder — крона не вылезает на соседей) + дома/колодец/
+ башня/машина/рельсы/декор (светлый пиксель на земле, y=1); детерминировано
+ (пятна — хэш координат, сиды штампов — от тайла). `src/data/areas.ts` —
+ AREA_DEFS: 8 областей с переходами v1 1:1 (step — вход на тайл-триггер,
+ click — «E» рядом с дверью/колодцем, 'return' — стек возврата). `src/main.ts`:
+ мир области пересоздаётся на переходе (меши сбрасываются), герой-манекен
+ (Character, скорость 4), фейд out→swap→in через DOM `#fade` (полный переход
+ 2×duration), подсказка «E — …» в `#hint`, камера follow с шагом пиксельной
+ сетки. Мост: `setManual`/`stepTicks`/`heroInfo` (area/tile/pos/camTarget)/
+ `heroTeleport`/`setInput`/`setFollow`/`setSun`/`areaInfo` (id/переходы/fade/
+ returns/chunks/meshes)/`gotoArea(id, entry?)` (прямой прыжок, стек чистится)/
+ `solidAt(x,z)` (сплошная ли колонка НАД землёй — y≥2)/`interact`/`interactInfo`.
+ Гейт `tools/checks/areas.mjs` (16 проверок): старт на спавне лугов,
+ пересбор области — тот же кадр (детерминизм), стик (1,1) = чистый +x мира,
+ blocked (дерево/вода сплошные — по 3×3 под-колонкам тайла, тропа нет),
+ тело зажимается гранью воды, скриншоты 7 областей различаются, step-переходы
+ (луга→Звенец, луга⇄пруды) с фейдом и запоминанием возврата, люк→зал→return
+ по стеку, click-переход «E» (подсказка у двери, вдали тишина), интерьер
+ рисуется.
## Известные грабли (уже собранные)
@@ -408,6 +436,23 @@
- **`AgentClient.call` вариадический**: `call('combatSpawn', 'crawler', 15, 20)`,
а не `call('combatSpawn', ['crawler', 15, 20])` — массив уходит ОДНИМ
аргументом (ENEMY_KINDS[Array] → undefined).
+- **Экранная ось (1,0) — диагональ мира** (при yaw π/4 это (+x,−z)/√2):
+ герой, идущий «экраном», срезает угол тайла и может ПРОПУСТИТЬ
+ тайл-триггер перехода (x и z пересекают границы в один тик). Чистая
+ мировая ось — комбинированный стик: (1,1) = +x, (−1,−1) = −x,
+ (1,−1) = −z, (−1,1) = +z (проверено гейтом: дрейфа по z нет).
+- **Гейт-подход к триггеру — телепортом на соседний тайл + чистая ось**:
+ телепорт точно на триггер не срабатывает (prevTile = триггер, «входа» нет);
+ после телепорта prevTile = тайл прибытия, триггер fires только при входе
+ СНАРУЖИ.
+- **Штампы шире тайла запирают проходы**: модель 7 вокселей, отштампованная
+ от угла тайла 3×3, вылезает на соседей на 2 вокселя — крона (тело героя
+ 3×10×3, крона на y≥4) делает непроходимыми коридоры, которые в v1
+ свободны (blocked только свой тайл). Контентные объекты карты —
+ генераторы строго в тайл 3×3 (mapTree/mapBush/mapBoulder), штамп
+ центрируется на центр тайла.
+- **solidAt для blocked-гейта — колонка НАД землёй (y≥2)**: земля y0-1
+ сплошная всюду; проверка с y=0 даёт «всё заблокировано».
## Рабочие привычки
diff --git a/v2/apps/game/index.html b/v2/apps/game/index.html
new file mode 100644
index 0000000..d7a12eb
--- /dev/null
+++ b/v2/apps/game/index.html
@@ -0,0 +1,46 @@
+
+
+
+
+ Пепельные луга — v2
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/v2/apps/game/package.json b/v2/apps/game/package.json
new file mode 100644
index 0000000..59167e8
--- /dev/null
+++ b/v2/apps/game/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@rpg/game",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "description": "Пепельные луга на v2: воспроизведение демки v1 на воксельном движке.",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build"
+ },
+ "dependencies": {
+ "@rpg/engine": "*",
+ "three": "^0.186.0"
+ },
+ "devDependencies": {
+ "vite": "^6.0.0"
+ }
+}
\ No newline at end of file
diff --git a/v2/apps/game/src/areas.ts b/v2/apps/game/src/areas.ts
new file mode 100644
index 0000000..fba4d94
--- /dev/null
+++ b/v2/apps/game/src/areas.ts
@@ -0,0 +1,329 @@
+/**
+ * Области «Пепельных луга» на v2: раскладка тайлов v1 (maps.json, референс)
+ * → воксельный мир. 1 тайл v1 = 3×3 вокселя (1 юнит = 3 вокселя, как в бою
+ * демки). Рельеф — плоский (земля y0-1), объекты — штампы генераторов
+ * движка (деревья/валуны) и локальных генераторов контента (кусты, дома,
+ * колодец, башня). Позиции v1 (NPC/переходы/квесты, в тайлах) конвертируются
+ * на границе: центр тайла = (tile*UNIT + 1.5) в воксельных координатах.
+ */
+import {
+ VoxelWorld, VoxelGrid, stampModel,
+ encodeModel, Rng, type Variant,
+} from '@rpg/engine';
+import type { VoxelModel } from '@rpg/engine';
+import mapsData from './data/maps.json';
+
+/** Раскладка области (конвертация v1): тайлы RLE, blocked/tall/decor/props v1. */
+export interface AreaLayout {
+ width: number;
+ height: number;
+ /** RLE-пары [id, длина] построчно (idx = y*width + x). */
+ tiles: [number, number][];
+ blocked: number[];
+ tall: Record;
+ decor: { id: number; x: number; y: number }[];
+ props: { id: number; x: number; y: number; w?: number; h?: number }[];
+}
+
+export const AREA_LAYOUTS = mapsData as unknown as Record;
+
+/** Вокселей на тайл v1 (1 юнит = 3 вокселя — общий масштаб демки/боя). */
+export const UNIT = 3;
+/** Точка ног героя в вокселях (верх земли). */
+export const GROUND_Y = 2;
+
+// Слоты палитры сцены (арт-библия v1; 11+ — тайлы областей)
+const ASH = 11; // P2 — пепел (тёмный)
+const ASH_LIGHT = 12; // P3 — пепел, пятна
+const WATER = 13; // W1 — вода
+const WATER_LIGHT = 14; // W2 — вода, блики
+const FLOOR = 15; // T0 — пол интерьеров
+const PATH = 16; // T1 — тропа
+const RAIL = 17; // M2 — рельсы (металл)
+const FLOWER = 9; // B1 — колокольчик (жизнь!)
+
+// Тайлы раскладки v1 (TILES из data/map.ts v1)
+const T = {
+ GRASS: 0, PATH: 1, WATER: 2, TREE: 3, ASH: 4, BELLFLOWER: 5, TOWER: 6,
+ HOUSE: 7, FLOOR: 8, WALL: 9, WELL: 10, WIRE: 18, RAIL: 19, STAIRS: 20,
+ MACHINE: 21, TREE_V1: 22, TREE_V2: 23, HOUSE_V1: 24, HOUSE_V2: 25,
+ HOUSE_V3: 26, HOUSE_V4: 27, BOULDER: 28, BUSH: 29, BOULDER_2: 30,
+ BOULDER_3: 31, BUSH_2: 32, BUSH_3: 33,
+} as const;
+
+const TREES: Record = { [T.TREE]: 0, [T.TREE_V1]: 1, [T.TREE_V2]: 2 };
+const HOUSE_KINDS: readonly number[] = [T.HOUSE, T.HOUSE_V1, T.HOUSE_V2, T.HOUSE_V3, T.HOUSE_V4];
+const BOULDER_KINDS: readonly number[] = [T.BOULDER, T.BOULDER_2, T.BOULDER_3];
+const BUSH_KINDS: readonly number[] = [T.BUSH, T.BUSH_2, T.BUSH_3];
+
+/** Детерминированное пятно по координате тайла (чистый хэш, без ГПСЧ). */
+const mottle = (tx: number, ty: number, salt = 0) => (tx * 7 + ty * 13 + salt * 31) % 11 < 3;
+
+// ---- Генераторы контента игры (деревья/кусты/валуны в тайл 3×3, дома...) ----
+
+/** Дерево карты: компактное, ровно в тайл 3×3 (крона не вылезает на соседей —
+ * blocked-семантика v1). Варианты: 0 круглая крона, 1 узкая колонна, 2 ель. */
+export function mapTreeModel(variant: Variant, seed: number): VoxelModel {
+ const rng = new Rng(seed ^ 0x7ee1);
+ const trunkH = variant === 1 ? rng.range(3, 5) : rng.range(2, 3);
+ const crownH = variant === 2 ? rng.range(3, 4) : rng.range(2, 3);
+ const g = new VoxelGrid(3, trunkH + crownH + 1, 3);
+ for (let y = 0; y < trunkH; y++) g.set(1, y, 1, 4); // ствол
+ const r = 1 + rng.next() * 0.35; // джиттер радиуса фиксируем заранее
+ for (let dy = 0; dy < crownH; dy++) {
+ const y = trunkH - 1 + dy;
+ for (let dz = -1; dz <= 1; dz++)
+ for (let dx = -1; dx <= 1; dx++) {
+ const d2 = dx * dx + dz * dz;
+ let ok: boolean;
+ if (variant === 0) ok = d2 + dy * dy * 0.6 <= r * r + 1;
+ else if (variant === 1) ok = d2 <= 1;
+ else ok = d2 <= (dy < crownH / 2 ? 2 : 1); // ель: ярусы сужаются
+ if (!ok) continue;
+ if (dy > 0 && d2 > 1 && !rng.chance(0.7)) continue; // рваный край
+ const light = dy === crownH - 1 || rng.chance(0.25);
+ g.set(1 + dx, y, 1 + dz, light ? 5 : 3);
+ }
+ }
+ return encodeModel(g, { 3: '#626c4c', 5: '#7d8662', 4: '#5c3b2e' });
+}
+
+/** Куст карты: низкая крона без ствола, ровно в тайл. */
+export function mapBushModel(variant: Variant, seed: number): VoxelModel {
+ const rng = new Rng(seed ^ 0x5a51);
+ const H = variant === 1 ? 3 : 2;
+ const g = new VoxelGrid(3, H, 3);
+ for (let y = 0; y < H; y++)
+ for (let dz = -1; dz <= 1; dz++)
+ for (let dx = -1; dx <= 1; dx++) {
+ const d2 = dx * dx + dz * dz;
+ if (y === 0 ? d2 > 2 : d2 > 1) continue; // низ шире, верх уже
+ if (d2 > 0 && !rng.chance(0.8)) continue;
+ g.set(1 + dx, y, 1 + dz, y === H - 1 || rng.chance(0.25) ? 5 : 3);
+ }
+ return encodeModel(g, { 3: '#626c4c', 5: '#7d8662' });
+}
+
+/** Валун карты: глыба-купол в тайл (не ниже 2 — не плитка), свет на верхушке. */
+export function mapBoulderModel(variant: Variant, seed: number): VoxelModel {
+ const rng = new Rng(seed ^ 0x4b0d);
+ const H = 2 + Math.min(variant, 1) + (rng.next() < 0.4 ? 1 : 0); // 2..4
+ const g = new VoxelGrid(3, H, 3);
+ for (let y = 0; y < H; y++) {
+ const w = y === 0 ? 3 : y === H - 1 ? 1 : 2; // купол: шире к низу
+ const o = (3 - w) >> 1;
+ for (let dz = 0; dz < w; dz++)
+ for (let dx = 0; dx < w; dx++) {
+ if (w === 2 && dx === 1 && dz === 1 && rng.chance(0.5)) continue;
+ g.set(o + dx, y, o + dz, y === H - 1 ? 7 : 6);
+ }
+ }
+ return encodeModel(g, { 6: '#5b5b66', 7: '#94949e' });
+}
+
+/** Дом: каменные стены с окнами + двускатная ржавая крыша (tall 1.75 юнита). */
+export function houseModel(kind: number, _seed: number): VoxelModel {
+ const tall = kind === T.HOUSE_V3 || kind === T.HOUSE_V4;
+ const W = 8, H = tall ? 10 : 7, D = 6;
+ const g = new VoxelGrid(W, H, D);
+ for (let y = 0; y < H - 2; y++)
+ for (let z = 0; z < D; z++)
+ for (let x = 0; x < W; x++) {
+ const edge = x === 0 || z === 0 || x === W - 1 || z === D - 1;
+ if (!edge) continue;
+ const win = y > 1 && y < H - 3 && (x + z) % 3 === 0;
+ g.set(x, y, z, win ? 10 : 6);
+ }
+ for (let dy = 0; dy < 3; dy++) {
+ const y = H - 3 + dy;
+ for (let z = 0; z < D; z++)
+ for (let x = dy; x < W - dy; x++) g.set(x, y, z, 8);
+ }
+ return encodeModel(g, { 6: '#5b5b66', 8: '#82503a', 10: '#44454e' });
+}
+
+/** Колодец: каменное кольцо + ворот с брусом (tall 1 юнит). */
+export function wellModel(_seed: number): VoxelModel {
+ const g = new VoxelGrid(3, 4, 3);
+ for (let z = 0; z < 3; z++)
+ for (let x = 0; x < 3; x++) {
+ const edge = x === 0 || z === 0 || x === 2 || z === 2;
+ if (edge) g.set(x, 0, z, 6);
+ }
+ g.set(0, 1, 0, 4);
+ g.set(2, 1, 0, 4);
+ g.set(1, 2, 0, 10); // ворот (металл)
+ g.set(1, 3, 0, 4);
+ return encodeModel(g, { 6: '#5b5b66', 4: '#5c3b2e', 10: '#44454e' });
+}
+
+/** Башня звона: колонна с площадкой и колоколом (v1 tall 2.5 юнита). */
+export function towerModel(_seed: number): VoxelModel {
+ const g = new VoxelGrid(3, 9, 3);
+ for (let y = 0; y < 6; y++)
+ for (let z = 0; z < 3; z++)
+ for (let x = 0; x < 3; x++) g.set(x, y, z, 6);
+ for (let x = 0; x < 3; x++)
+ for (let z = 0; z < 3; z++) g.set(x, 6, z, 7); // площадка, свет камня
+ g.set(1, 7, 1, 10); // колокол (металл)
+ g.set(1, 8, 1, 4);
+ return encodeModel(g, { 6: '#5b5b66', 7: '#94949e', 10: '#44454e', 4: '#5c3b2e' });
+}
+
+/** Старая Машина: тяжёлый блок механизма (prop 2×2 тайла, tall 2.5 юнита). */
+export function machineModel(_seed: number): VoxelModel {
+ const g = new VoxelGrid(8, 9, 7);
+ for (let y = 0; y < 5; y++)
+ for (let z = 0; z < 7; z++)
+ for (let x = 0; x < 8; x++)
+ g.set(x, y, z, (x + z + y) % 4 === 0 ? 10 : 6);
+ for (let z = 2; z < 5; z++)
+ for (let x = 1; x < 7; x++) g.set(x, 5, z, 8);
+ for (let z = 2; z < 5; z++) g.set(4, 6, z, 10);
+ g.set(4, 7, 3, 9); // тёплая искра в жерле (жизнь!)
+ return encodeModel(g, { 6: '#5b5b66', 8: '#82503a', 10: '#44454e', 9: '#d99a32' });
+}
+
+/** Бухта кабеля: низкая ржавая спираль (v1 tall 0.6 юнита). */
+export function wireModel(_seed: number): VoxelModel {
+ const g = new VoxelGrid(4, 3, 3);
+ for (let z = 0; z < 3; z++)
+ for (let x = 0; x < 4; x++) {
+ const rim = x === 0 || x === 3 || z === 1;
+ if (rim) g.set(x, 0, z, 8);
+ if (rim && (x + z) % 2 === 0) g.set(x, 1, z, 10);
+ }
+ return encodeModel(g, { 8: '#82503a', 10: '#44454e' });
+}
+
+/** Кэш моделей: генерация 1 раз на (тип, тайл) — роща из 1005 деревьев дёшева. */
+const cache = new Map();
+function cached(key: string, make: () => VoxelModel): VoxelModel {
+ const hit = cache.get(key);
+ if (hit) return hit;
+ const m = make();
+ cache.set(key, m);
+ return m;
+}
+
+/** Поставить штамп на тайл: модель центрируется на центре тайла
+ * (ствол/фундамент по центру, крона/крыша свисают за край — как в v1). */
+function stampAt(world: VoxelWorld, m: VoxelModel, tx: number, ty: number): void {
+ const [sx, , sz] = m.size;
+ stampModel(
+ world, m,
+ tx * UNIT + 1 - ((sx - 1) >> 1), GROUND_Y, ty * UNIT + 1 - ((sz - 1) >> 1),
+ );
+}
+
+/**
+ * Сборка воксельного мира области из раскладки v1. Детерминировано:
+ * пятна — хэш координат, штампы — генераторы с сидом от координат.
+ */
+export function buildAreaWorld(l: AreaLayout): VoxelWorld {
+ const world = new VoxelWorld(16); // высота чанка 16 (башня 9 + запас)
+ const tiles = rleDecode(l.tiles, l.width * l.height);
+ // 1) земля (все тайлы) — потом декор, потом штампы (объекты поверх)
+ for (let ty = 0; ty < l.height; ty++)
+ for (let tx = 0; tx < l.width; tx++) {
+ const t = tiles[ty * l.width + tx] ?? T.ASH;
+ for (let dz = 0; dz < UNIT; dz++)
+ for (let dx = 0; dx < UNIT; dx++) {
+ const x = tx * UNIT + dx, z = ty * UNIT + dz;
+ world.set(x, 0, z, 2); // низ земли — тень травы
+ world.set(x, 1, z, groundFor(t, tx, ty, dx, dz));
+ if (t === T.WATER) {
+ // вода — на воксель выше земли (v1: непроходима);
+ // тело героя стоит на y=2 и упирается в этот слой
+ world.set(x, 1, z, WATER);
+ world.set(x, 2, z, groundFor(t, tx, ty, dx, dz));
+ }
+ }
+ }
+ // 1б) декор v1 (мелкие объекты на земле, уровень y=1 — ходьбе не мешает)
+ for (const d of l.decor) stampDecor(world, d);
+ // 2) штампы объектов
+ for (let ty = 0; ty < l.height; ty++)
+ for (let tx = 0; tx < l.width; tx++) {
+ stampObject(world, tiles[ty * l.width + tx] ?? T.ASH, tx, ty);
+ }
+ return world;
+}
+
+// Декор v1: микро-объекты на земле (не тайлы — в раскладке отдельно)
+const DECOR = { TUFT: 15, STONE: 16, BALD: 17 } as const;
+
+/** Декор: светлый пиксель на земле (кустик травы / камешек / проплешина). */
+function stampDecor(
+ world: VoxelWorld,
+ d: { id: number; x: number; y: number; dx?: number; dy?: number },
+): void {
+ const ox = Math.max(-1, Math.min(1, Math.round((d.dx ?? 0) / 3)));
+ const oz = Math.max(-1, Math.min(1, Math.round((d.dy ?? 0) / 3)));
+ const slot = d.id === DECOR.TUFT ? 5 : d.id === DECOR.STONE ? 7 : 2;
+ world.set(d.x * UNIT + 1 + ox, 1, d.y * UNIT + 1 + oz, slot);
+}
+
+/** RLE-пары → плоский массив (недостающие — пепел). */
+function rleDecode(pairs: [number, number][], total: number): number[] {
+ const out: number[] = [];
+ for (const [id, run] of pairs) for (let k = 0; k < run; k++) out.push(id);
+ while (out.length < total) out.push(T.ASH);
+ return out;
+}
+
+/** Слот поверхности тайла (день: трава пятнистая, пепел, тропа, вода, пол). */
+function groundFor(t: number, tx: number, ty: number, dx: number, dz: number): number {
+ switch (t) {
+ case T.GRASS: return mottle(tx, ty, dx + dz) ? 3 : 1; // G1 + пятна G2
+ case T.ASH: return mottle(tx, ty, dx + dz) ? ASH_LIGHT : ASH;
+ case T.PATH: return PATH;
+ case T.WATER: return mottle(tx * 2 + dx, ty * 2 + dz, dz) ? WATER_LIGHT : WATER;
+ case T.FLOOR: return FLOOR;
+ case T.RAIL: return mottle(tx, ty, dx) ? PATH : FLOOR; // шпалы
+ case T.STAIRS: return ASH; // люк: тёмный провал
+ case T.WALL: return FLOOR; // стены интерьеров стоят на полу
+ default: return mottle(tx, ty, dx + dz) ? ASH_LIGHT : ASH; // под объектами
+ }
+}
+
+/** Штамп объекта тайла (дерево/дом/валун/куст/башня/колодец/рельсы...). */
+function stampObject(world: VoxelWorld, t: number, tx: number, ty: number): void {
+ const seed = tx * 131 + ty * 17;
+ if (t in TREES) stampAt(world, cached(`tree${t}`, () => mapTreeModel(TREES[t]!, seed)), tx, ty);
+ else if (HOUSE_KINDS.includes(t)) stampAt(world, cached(`house${t}`, () => houseModel(t, seed)), tx, ty);
+ else if (BOULDER_KINDS.includes(t))
+ stampAt(world, cached(`boul${t}`, () => mapBoulderModel((t - T.BOULDER) as Variant, seed)), tx, ty);
+ else if (BUSH_KINDS.includes(t))
+ stampAt(world, cached(`bush${t}`, () => mapBushModel((t - T.BUSH) as Variant, seed)), tx, ty);
+ else if (t === T.TOWER) stampAt(world, cached('tower', () => towerModel(seed)), tx, ty);
+ else if (t === T.WELL) stampAt(world, cached('well', () => wellModel(seed)), tx, ty);
+ else if (t === T.WIRE) stampAt(world, cached('wire', () => wireModel(seed)), tx, ty);
+ else if (t === T.MACHINE) stampAt(world, cached('machine', () => machineModel(seed)), tx, ty);
+ else if (t === T.RAIL) stampRail(world, tx, ty);
+ else if (t === T.BELLFLOWER) stampBellflower(world, tx, ty);
+ else if (t === T.WALL) stampWall(world, tx, ty);
+}
+
+/** Рельсы: металлическая нить по земле (полоса тайлов даёт линию). */
+function stampRail(world: VoxelWorld, tx: number, ty: number): void {
+ for (let d = 1; d < UNIT; d++) world.set(tx * UNIT + d, GROUND_Y, ty * UNIT + 1, RAIL);
+}
+
+/** Колокольчик: цветок на пепле (v1 harvest-тайл). */
+function stampBellflower(world: VoxelWorld, tx: number, ty: number): void {
+ world.set(tx * UNIT + 1, GROUND_Y, ty * UNIT + 1, FLOWER);
+}
+
+/** Стена интерьера: колонка камня высотой 4 над полом (v1 tall 1 юнит). */
+function stampWall(world: VoxelWorld, tx: number, ty: number): void {
+ for (let dz = 0; dz < UNIT; dz++)
+ for (let dx = 0; dx < UNIT; dx++)
+ for (let y = GROUND_Y; y < GROUND_Y + 4; y++)
+ world.set(tx * UNIT + dx, y, ty * UNIT + dz, 6);
+}
+
+/** Центр тайла в вокселях (позиции v1 → мир v2, на границе конвертации). */
+export function tileCenter(tx: number, ty: number): { x: number; z: number } {
+ return { x: tx * UNIT + UNIT / 2, z: ty * UNIT + UNIT / 2 };
+}
\ No newline at end of file
diff --git a/v2/apps/game/src/data/areas.ts b/v2/apps/game/src/data/areas.ts
new file mode 100644
index 0000000..fc630d6
--- /dev/null
+++ b/v2/apps/game/src/data/areas.ts
@@ -0,0 +1,102 @@
+/**
+ * Области демки (референс v1 locations.ts → данные v2): спавны и переходы
+ * в тайлах v1 (конвертация в воксели — на границе, tileCenter/UNIT).
+ * Переходы: step — наступил на тайл, click — «E» рядом (двери, колодцы);
+ * 'return' — назад, откуда вошли (стек переходов).
+ */
+
+/** Тайл v1 (юниты v1). */
+export interface Tile { x: number; y: number }
+
+export interface TransitionDef {
+ /** Тайл-триггер. */
+ tile: Tile;
+ /** Подпись подсказки (для click-переходов). */
+ label?: string;
+ /** Куда: [область, тайл входа] или 'return' — назад, откуда вошли. */
+ target: { area: string; entry: Tile } | 'return';
+ /** step (по умолчанию) или click (двери/колодцы/порталы). */
+ trigger?: 'step' | 'click';
+ /** Длительность фейда (сек), по умолчанию 0.4. */
+ duration?: number;
+}
+
+export interface AreaDef {
+ id: string;
+ name: string;
+ /** Стартовый тайл (вход в область по умолчанию). */
+ spawn: Tile;
+ transitions: TransitionDef[];
+}
+
+export const AREA_DEFS: AreaDef[] = [
+ {
+ id: 'meadows',
+ name: 'Выжженные луга',
+ spawn: { x: 16, y: 28 },
+ transitions: [
+ { tile: { x: 2, y: 2 }, target: { area: 'ponds', entry: { x: 4, y: 4 } } },
+ { tile: { x: 54, y: 28 }, target: { area: 'zvenets', entry: { x: 3, y: 20 } } },
+ { tile: { x: 44, y: 40 }, label: 'Старый колодец', target: { area: 'zvenets', entry: { x: 16, y: 14 } }, trigger: 'click', duration: 0.2 },
+ ],
+ },
+ {
+ id: 'ponds',
+ name: 'Серые пруды',
+ spawn: { x: 4, y: 4 },
+ transitions: [{ tile: { x: 2, y: 2 }, target: { area: 'meadows', entry: { x: 3, y: 3 } } }],
+ },
+ {
+ id: 'zvenets',
+ name: 'Звенец',
+ spawn: { x: 28, y: 20 },
+ transitions: [
+ { tile: { x: 2, y: 20 }, target: { area: 'meadows', entry: { x: 53, y: 28 } } },
+ { tile: { x: 16, y: 13 }, label: 'Старый колодец', target: { area: 'meadows', entry: { x: 44, y: 41 } }, trigger: 'click', duration: 0.2 },
+ { tile: { x: 13, y: 12 }, label: 'Дом Ирвина', target: { area: 'house_elder', entry: { x: 5, y: 7 } }, trigger: 'click', duration: 0.2 },
+ { tile: { x: 43, y: 19 }, label: 'Лавка Милы', target: { area: 'shop', entry: { x: 5, y: 7 } }, trigger: 'click', duration: 0.2 },
+ { tile: { x: 54, y: 20 }, target: { area: 'rust_grove', entry: { x: 2, y: 20 } } },
+ ],
+ },
+ {
+ id: 'rust_grove',
+ name: 'Ржавая роща',
+ spawn: { x: 2, y: 20 },
+ transitions: [
+ { tile: { x: 1, y: 20 }, target: { area: 'zvenets', entry: { x: 53, y: 20 } } },
+ { tile: { x: 46, y: 20 }, target: { area: 'old_siding', entry: { x: 2, y: 20 } } },
+ ],
+ },
+ {
+ id: 'old_siding',
+ name: 'Старый разъезд',
+ spawn: { x: 2, y: 20 },
+ transitions: [
+ { tile: { x: 1, y: 20 }, target: { area: 'rust_grove', entry: { x: 45, y: 20 } } },
+ { tile: { x: 28, y: 31 }, label: 'Люк в подземелье', target: { area: 'machine_hall', entry: { x: 13, y: 19 } }, duration: 0.6 },
+ ],
+ },
+ {
+ id: 'machine_hall',
+ name: 'Зал Старой Машины',
+ spawn: { x: 13, y: 19 },
+ transitions: [{ tile: { x: 13, y: 21 }, target: 'return', duration: 0.2 }],
+ },
+ {
+ id: 'house_elder',
+ name: 'Дом Ирвина',
+ spawn: { x: 5, y: 7 },
+ transitions: [{ tile: { x: 5, y: 8 }, target: 'return', duration: 0.2 }],
+ },
+ {
+ id: 'shop',
+ name: 'Лавка Милы',
+ spawn: { x: 5, y: 7 },
+ transitions: [{ tile: { x: 5, y: 8 }, target: 'return', duration: 0.2 }],
+ },
+];
+
+/** Область по id (фолбэк — луга, как в v1 areaOf). */
+export function areaOf(id: string): AreaDef {
+ return AREA_DEFS.find((a) => a.id === id) ?? AREA_DEFS[0]!;
+}
\ No newline at end of file
diff --git a/v2/apps/game/src/data/maps.json b/v2/apps/game/src/data/maps.json
new file mode 100644
index 0000000..aac8fd9
--- /dev/null
+++ b/v2/apps/game/src/data/maps.json
@@ -0,0 +1 @@
+{"meadows":{"width":56,"height":56,"tiles":[[0,3],[3,1],[0,1],[4,1],[3,1],[4,1],[0,4],[4,8],[0,18],[4,4],[0,7],[3,2],[0,2],[3,1],[0,3],[1,1],[0,3],[4,2],[3,1],[4,3],[0,1],[4,1],[0,1],[4,7],[0,4],[4,1],[0,1],[4,1],[0,9],[4,5],[0,7],[4,2],[3,1],[0,3],[3,2],[1,2],[0,2],[4,1],[3,1],[4,1],[0,1],[4,1],[0,2],[4,10],[0,3],[4,1],[0,1],[4,5],[0,5],[4,4],[0,3],[4,8],[0,3],[3,1],[0,2],[1,2],[0,1],[1,1],[0,2],[4,3],[0,1],[4,12],[0,1],[4,4],[0,1],[4,2],[0,5],[4,3],[0,4],[4,2],[0,1],[4,1],[0,2],[3,1],[0,1],[4,1],[0,6],[1,5],[0,4],[4,1],[0,1],[4,8],[0,1],[4,1],[0,3],[4,4],[0,6],[4,3],[0,3],[4,10],[0,3],[3,1],[0,3],[3,1],[4,1],[0,1],[1,1],[0,4],[4,5],[0,1],[4,11],[0,2],[4,4],[0,2],[4,3],[0,4],[4,5],[0,1],[4,1],[0,1],[4,1],[0,2],[3,1],[0,2],[3,2],[4,1],[3,1],[4,1],[1,1],[0,4],[4,5],[0,1],[4,17],[0,9],[4,1],[0,1],[4,3],[0,9],[3,1],[4,1],[0,1],[4,1],[3,1],[1,1],[0,5],[4,1],[0,1],[4,1],[0,2],[4,6],[0,1],[4,6],[0,1],[4,3],[0,2],[4,1],[0,14],[3,1],[0,5],[3,3],[4,2],[1,1],[0,5],[4,2],[0,1],[4,1],[0,3],[4,1],[0,2],[4,1],[0,1],[4,1],[0,1],[4,6],[0,1],[28,1],[0,2],[4,2],[0,9],[3,1],[0,12],[3,1],[0,1],[1,1],[0,4],[4,5],[0,3],[4,3],[0,1],[4,9],[0,5],[4,2],[0,10],[4,5],[0,4],[4,1],[3,3],[1,1],[0,13],[4,2],[0,1],[29,1],[0,1],[4,7],[0,15],[3,1],[0,2],[4,3],[0,5],[3,1],[0,3],[1,1],[0,18],[4,1],[29,1],[4,4],[0,3],[5,1],[0,14],[4,3],[0,1],[4,1],[0,1],[3,1],[0,1],[4,3],[3,1],[0,1],[1,2],[0,14],[4,5],[0,2],[4,1],[0,2],[4,4],[0,1],[4,1],[0,10],[3,1],[0,1],[4,1],[0,2],[4,1],[0,1],[3,1],[0,2],[3,1],[0,3],[3,1],[1,1],[0,9],[28,1],[0,4],[4,2],[0,4],[4,2],[0,2],[4,2],[0,1],[4,1],[29,1],[4,1],[0,2],[4,3],[0,2],[4,1],[0,3],[4,1],[0,1],[4,3],[0,6],[3,1],[0,2],[1,1],[0,14],[4,1],[0,1],[4,1],[0,5],[4,2],[0,1],[4,4],[2,2],[0,1],[2,4],[0,2],[4,7],[0,2],[4,2],[3,2],[0,2],[3,1],[0,1],[1,1],[0,22],[4,6],[0,2],[2,6],[0,1],[4,8],[0,10],[1,2],[0,19],[4,8],[0,1],[2,4],[0,1],[2,2],[0,1],[5,1],[4,7],[0,2],[3,1],[4,2],[3,1],[4,1],[3,3],[0,1],[1,4],[0,16],[4,1],[0,1],[4,5],[0,2],[2,1],[0,1],[2,5],[0,1],[4,1],[0,1],[4,3],[0,2],[4,1],[0,3],[3,4],[0,1],[3,1],[4,3],[0,1],[4,1],[1,4],[0,15],[4,3],[0,1],[4,1],[0,2],[2,4],[0,1],[2,1],[0,5],[3,1],[28,1],[0,5],[3,1],[0,1],[3,6],[0,1],[4,1],[0,2],[4,2],[0,1],[1,1],[0,10],[4,4],[0,1],[4,2],[0,27],[3,1],[0,3],[4,3],[0,4],[1,1],[0,5],[4,1],[0,1],[4,1],[0,3],[4,2],[0,1],[4,1],[0,17],[3,1],[0,9],[3,1],[0,1],[3,1],[0,1],[3,2],[28,1],[0,1],[4,1],[0,1],[3,1],[4,2],[1,1],[0,5],[4,4],[0,2],[29,1],[4,3],[0,8],[4,3],[0,1],[4,1],[0,3],[4,4],[0,9],[3,1],[0,1],[3,1],[4,4],[0,1],[4,1],[3,1],[4,1],[1,1],[0,5],[4,3],[0,3],[4,1],[0,1],[4,2],[0,3],[4,2],[0,1],[4,2],[0,1],[4,4],[0,3],[4,4],[3,2],[0,3],[3,1],[0,3],[3,1],[0,1],[3,1],[0,6],[4,2],[1,2],[4,2],[0,1],[4,2],[0,6],[4,1],[0,5],[4,1],[0,1],[4,3],[28,1],[4,1],[0,2],[4,1],[0,3],[4,1],[3,1],[0,1],[4,2],[0,8],[3,1],[0,1],[3,1],[0,9],[1,1],[0,1],[4,1],[29,1],[4,4],[0,10],[4,8],[0,5],[4,1],[0,1],[4,3],[0,7],[3,1],[0,9],[28,1],[0,2],[1,1],[0,1],[4,1],[0,1],[4,2],[0,11],[4,5],[29,1],[4,1],[0,1],[4,3],[0,3],[4,5],[0,1],[3,1],[0,8],[3,1],[0,12],[4,2],[3,1],[0,2],[28,1],[0,9],[4,5],[0,2],[4,1],[0,1],[4,5],[0,5],[4,3],[0,4],[3,1],[0,2],[3,1],[0,3],[4,1],[0,1],[4,1],[0,5],[29,1],[0,1],[4,1],[0,2],[4,3],[0,1],[4,1],[0,4],[4,1],[0,2],[4,5],[0,2],[4,6],[0,6],[4,1],[0,1],[3,1],[0,2],[3,3],[0,1],[29,1],[0,2],[4,3],[0,5],[1,33],[0,1],[1,2],[0,1],[3,1],[4,4],[0,1],[4,1],[0,4],[4,1],[0,1],[4,1],[0,8],[4,7],[0,3],[29,1],[4,3],[0,1],[4,4],[0,2],[4,5],[0,3],[4,1],[0,3],[3,2],[4,4],[3,1],[4,1],[0,4],[29,1],[0,7],[4,5],[0,1],[4,1],[0,2],[4,1],[0,4],[4,3],[0,4],[29,1],[0,2],[4,1],[0,1],[4,3],[0,2],[4,1],[0,1],[4,3],[0,1],[4,3],[0,1],[3,1],[4,1],[3,1],[0,8],[4,4],[0,1],[4,7],[0,6],[4,3],[0,1],[4,2],[0,5],[4,3],[0,1],[4,1],[0,2],[3,1],[4,3],[0,1],[3,2],[4,2],[3,1],[4,1],[3,2],[0,4],[2,4],[4,1],[2,1],[0,1],[4,1],[28,1],[4,4],[0,9],[4,2],[0,1],[4,3],[0,7],[4,1],[0,1],[4,4],[0,1],[4,1],[0,1],[3,1],[4,4],[0,1],[4,1],[0,5],[2,3],[0,1],[2,3],[4,1],[0,1],[4,4],[0,5],[29,1],[0,6],[4,1],[0,1],[4,1],[0,7],[4,2],[0,6],[3,2],[0,3],[3,1],[0,1],[3,2],[29,1],[0,3],[2,4],[0,1],[2,2],[4,1],[5,1],[4,1],[0,1],[4,2],[0,19],[4,1],[0,2],[4,3],[0,1],[3,1],[0,1],[4,1],[3,1],[0,2],[3,1],[0,2],[3,4],[0,1],[29,1],[4,2],[0,1],[2,1],[0,1],[2,4],[0,2],[4,2],[0,1],[4,5],[0,13],[4,1],[0,1],[4,1],[0,5],[3,1],[4,1],[0,1],[4,1],[0,2],[3,2],[0,5],[3,1],[0,1],[4,2],[5,1],[2,6],[0,6],[4,5],[0,15],[4,1],[0,6],[4,1],[0,1],[4,1],[3,1],[0,2],[4,1],[3,2],[4,1],[3,1],[0,4],[4,2],[0,4],[29,1],[0,9],[4,3],[0,28],[4,1],[3,1],[4,1],[3,1],[0,20],[4,4],[0,9],[29,1],[0,1],[4,1],[0,9],[4,4],[0,2],[3,1],[4,1],[3,1],[4,1],[0,1],[4,1],[0,8],[4,5],[0,5],[4,1],[0,1],[4,3],[0,10],[4,3],[0,6],[3,1],[0,1],[4,4],[0,3],[3,1],[4,4],[3,2],[0,7],[4,2],[0,1],[4,1],[0,20],[4,2],[0,3],[10,1],[0,6],[4,3],[0,1],[3,2],[4,3],[0,1],[4,1],[3,1],[0,7],[4,3],[0,1],[4,1],[0,3],[4,3],[0,1],[4,1],[0,22],[4,2],[0,1],[4,4],[0,1],[3,1],[0,3],[3,1],[0,8],[4,2],[0,1],[4,2],[0,2],[4,6],[0,8],[4,2],[0,12],[3,1],[4,5],[3,5],[0,1],[3,1],[0,6],[28,1],[0,1],[4,2],[0,1],[4,2],[0,2],[4,6],[0,7],[4,3],[0,12],[4,5],[0,1],[4,1],[0,1],[3,1],[0,1],[3,2],[0,1],[3,2],[0,3],[4,1],[0,1],[4,1],[0,7],[4,6],[0,7],[4,2],[0,13],[4,1],[0,1],[4,1],[0,1],[4,2],[0,2],[3,1],[0,2],[3,3],[0,3],[4,5],[0,5],[4,6],[0,23],[4,1],[0,1],[4,1],[3,1],[4,1],[3,1],[4,1],[0,3],[3,1],[0,6],[4,2],[0,1],[4,1],[0,1],[4,1],[0,4],[4,6],[0,24],[3,1],[28,1],[0,2],[4,1],[0,1],[3,1],[0,4],[3,1],[0,4],[4,6],[0,4],[4,8],[0,5],[4,3],[0,15],[3,1],[0,1],[4,2],[0,3],[3,1],[0,2],[3,3],[4,2],[0,1],[4,1],[0,1],[4,1],[0,1],[4,1],[0,4],[4,6],[0,1],[4,1],[0,3],[4,2],[0,2],[4,1],[0,17],[4,2],[0,1],[3,1],[0,1],[3,1],[0,2],[3,1],[0,1],[4,7],[0,1],[4,1],[0,6],[4,2],[0,2],[4,2],[0,3],[4,6],[0,17],[3,1],[4,1],[0,3],[3,1],[0,1],[3,2],[4,6],[0,2],[4,1],[0,1],[4,1],[0,4],[4,2],[0,7],[4,5],[0,14],[3,1],[4,4],[0,2],[3,4],[4,1],[0,1],[4,3],[0,1],[4,2],[0,3],[4,2],[0,13],[4,5],[0,13],[4,3],[3,1],[4,2],[0,3],[3,4],[4,5],[0,1],[4,2],[0,1],[4,3],[0,14],[4,1],[0,2],[4,2],[0,11],[3,1],[4,6],[0,1],[3,2],[0,3],[3,3],[4,10],[0,15],[4,3],[0,6],[4,1],[0,1],[4,3],[0,1],[3,1],[0,3],[4,1],[0,1],[4,1],[0,3],[3,2],[4,2],[0,1],[3,1],[0,1],[4,1],[0,1],[4,2],[0,1],[4,4],[0,6],[28,1],[0,7],[28,1],[0,9],[4,5],[0,5],[4,1],[3,1],[4,1],[0,2],[3,1],[0,1],[4,1],[0,3],[3,1],[0,1],[4,2],[0,1],[4,5],[0,25],[4,4],[0,10]],"blocked":[2,3,6,7,24,25,26,27,9,10,18,21,28,29,30,31,32,33],"tall":{"3":{"height":1.5,"ground":0},"6":{"height":2.5,"ground":1},"7":{"height":1.75,"ground":4,"shadow":true},"9":{"height":1,"ground":8},"18":{"height":0.6,"ground":4},"21":{"height":2.5,"ground":8},"22":{"height":1.5,"ground":0},"23":{"height":1.5,"ground":0},"24":{"height":1.75,"ground":4,"shadow":true},"25":{"height":1.75,"ground":4,"shadow":true},"26":{"height":2.5,"ground":4,"shadow":true},"27":{"height":2.5,"ground":4,"shadow":true},"28":{"height":1.05,"ground":4},"29":{"height":1.25,"ground":4},"30":{"height":1.25,"ground":4},"31":{"height":0.75,"ground":4},"32":{"height":1,"ground":4},"33":{"height":1.45,"ground":4}},"decor":[{"id":16,"x":7,"y":0,"dx":-3,"dy":-2},{"id":16,"x":17,"y":0,"dx":-4,"dy":2},{"id":15,"x":30,"y":0,"dx":1,"dy":1},{"id":16,"x":38,"y":0,"dx":-3,"dy":0},{"id":16,"x":39,"y":0,"dx":-3,"dy":1},{"id":15,"x":2,"y":1,"dx":-2,"dy":-2},{"id":16,"x":8,"y":1,"dx":-2,"dy":1},{"id":15,"x":22,"y":1,"dx":3,"dy":1},{"id":15,"x":24,"y":1,"dx":-3,"dy":-1},{"id":17,"x":43,"y":1,"dx":1,"dy":1},{"id":17,"x":44,"y":1,"dx":1,"dy":1},{"id":17,"x":3,"y":2,"dx":-3,"dy":2},{"id":17,"x":8,"y":2,"dx":-2,"dy":0},{"id":15,"x":10,"y":2,"dx":-3,"dy":1},{"id":16,"x":20,"y":2,"dx":-2,"dy":2},{"id":16,"x":28,"y":2,"dx":-4,"dy":0},{"id":16,"x":31,"y":2,"dx":3,"dy":1},{"id":15,"x":42,"y":2,"dx":4,"dy":0},{"id":17,"x":7,"y":3,"dx":1,"dy":0},{"id":16,"x":52,"y":3,"dx":-1,"dy":-2},{"id":16,"x":15,"y":4,"dx":-2,"dy":1},{"id":16,"x":17,"y":4,"dx":3,"dy":1},{"id":17,"x":26,"y":4,"dx":-1,"dy":0},{"id":17,"x":33,"y":4,"dx":2,"dy":-1},{"id":17,"x":35,"y":4,"dx":-1,"dy":-2},{"id":15,"x":42,"y":4,"dx":-3,"dy":0},{"id":16,"x":51,"y":4,"dx":-3,"dy":1},{"id":16,"x":14,"y":5,"dx":0,"dy":0},{"id":16,"x":28,"y":5,"dx":-4,"dy":0},{"id":16,"x":34,"y":5,"dx":-3,"dy":0},{"id":15,"x":35,"y":5,"dx":2,"dy":1},{"id":16,"x":38,"y":5,"dx":-2,"dy":2},{"id":15,"x":43,"y":5,"dx":3,"dy":-2},{"id":15,"x":51,"y":5,"dx":-2,"dy":1},{"id":16,"x":52,"y":5,"dx":-1,"dy":-1},{"id":15,"x":0,"y":6,"dx":1,"dy":1},{"id":17,"x":1,"y":6,"dx":1,"dy":1},{"id":16,"x":29,"y":6,"dx":4,"dy":0},{"id":17,"x":39,"y":6,"dx":4,"dy":0},{"id":16,"x":46,"y":6,"dx":-2,"dy":-2},{"id":17,"x":51,"y":6,"dx":2,"dy":-1},{"id":15,"x":1,"y":7,"dx":0,"dy":1},{"id":16,"x":3,"y":7,"dx":-1,"dy":0},{"id":16,"x":15,"y":7,"dx":-1,"dy":0},{"id":15,"x":17,"y":7,"dx":3,"dy":-1},{"id":16,"x":18,"y":7,"dx":0,"dy":1},{"id":15,"x":24,"y":7,"dx":4,"dy":-1},{"id":16,"x":37,"y":7,"dx":1,"dy":0},{"id":15,"x":38,"y":7,"dx":-3,"dy":-1},{"id":15,"x":39,"y":7,"dx":-3,"dy":-2},{"id":17,"x":47,"y":7,"dx":0,"dy":-1},{"id":15,"x":51,"y":7,"dx":3,"dy":-1},{"id":17,"x":26,"y":8,"dx":4,"dy":2},{"id":16,"x":30,"y":8,"dx":1,"dy":-2},{"id":16,"x":32,"y":8,"dx":-4,"dy":-2},{"id":15,"x":35,"y":8,"dx":-1,"dy":0},{"id":16,"x":38,"y":8,"dx":-2,"dy":-2},{"id":15,"x":45,"y":8,"dx":-2,"dy":0},{"id":15,"x":47,"y":8,"dx":4,"dy":0},{"id":17,"x":2,"y":9,"dx":3,"dy":0},{"id":16,"x":20,"y":9,"dx":-4,"dy":-2},{"id":16,"x":24,"y":9,"dx":-2,"dy":1},{"id":17,"x":33,"y":9,"dx":-3,"dy":0},{"id":16,"x":50,"y":9,"dx":2,"dy":1},{"id":15,"x":15,"y":10,"dx":-3,"dy":0},{"id":15,"x":18,"y":10,"dx":0,"dy":-1},{"id":15,"x":20,"y":10,"dx":0,"dy":2},{"id":16,"x":22,"y":10,"dx":-3,"dy":1},{"id":15,"x":25,"y":10,"dx":2,"dy":-1},{"id":15,"x":49,"y":10,"dx":-3,"dy":2},{"id":16,"x":51,"y":10,"dx":1,"dy":2},{"id":17,"x":17,"y":11,"dx":3,"dy":2},{"id":15,"x":18,"y":11,"dx":3,"dy":-2},{"id":17,"x":21,"y":11,"dx":1,"dy":1},{"id":16,"x":29,"y":11,"dx":-1,"dy":0},{"id":16,"x":31,"y":11,"dx":-3,"dy":0},{"id":15,"x":37,"y":11,"dx":1,"dy":0},{"id":17,"x":1,"y":12,"dx":2,"dy":-1},{"id":15,"x":9,"y":12,"dx":-3,"dy":1},{"id":17,"x":10,"y":12,"dx":0,"dy":0},{"id":17,"x":22,"y":12,"dx":4,"dy":-2},{"id":16,"x":26,"y":12,"dx":-4,"dy":2},{"id":16,"x":36,"y":12,"dx":0,"dy":1},{"id":15,"x":40,"y":12,"dx":-2,"dy":-1},{"id":17,"x":46,"y":12,"dx":-3,"dy":0},{"id":15,"x":16,"y":13,"dx":1,"dy":1},{"id":17,"x":28,"y":13,"dx":2,"dy":-1},{"id":16,"x":46,"y":13,"dx":-1,"dy":2},{"id":17,"x":1,"y":14,"dx":0,"dy":-1},{"id":16,"x":31,"y":14,"dx":1,"dy":-1},{"id":16,"x":36,"y":14,"dx":-2,"dy":-2},{"id":16,"x":0,"y":15,"dx":-3,"dy":1},{"id":15,"x":13,"y":15,"dx":1,"dy":2},{"id":17,"x":21,"y":15,"dx":-3,"dy":-2},{"id":16,"x":47,"y":15,"dx":0,"dy":-1},{"id":16,"x":35,"y":16,"dx":1,"dy":-1},{"id":16,"x":1,"y":17,"dx":-3,"dy":-1},{"id":17,"x":27,"y":17,"dx":2,"dy":1},{"id":16,"x":31,"y":17,"dx":4,"dy":1},{"id":16,"x":34,"y":17,"dx":2,"dy":2},{"id":16,"x":46,"y":17,"dx":2,"dy":1},{"id":15,"x":16,"y":18,"dx":4,"dy":2},{"id":15,"x":34,"y":18,"dx":-3,"dy":0},{"id":17,"x":48,"y":18,"dx":2,"dy":1},{"id":17,"x":54,"y":18,"dx":2,"dy":2},{"id":17,"x":1,"y":19,"dx":-3,"dy":0},{"id":16,"x":12,"y":19,"dx":1,"dy":0},{"id":16,"x":13,"y":19,"dx":-1,"dy":0},{"id":15,"x":21,"y":19,"dx":0,"dy":0},{"id":16,"x":29,"y":19,"dx":2,"dy":0},{"id":15,"x":44,"y":19,"dx":4,"dy":1},{"id":17,"x":55,"y":19,"dx":0,"dy":-1},{"id":16,"x":10,"y":20,"dx":-3,"dy":1},{"id":15,"x":24,"y":20,"dx":-2,"dy":1},{"id":17,"x":36,"y":20,"dx":-2,"dy":-1},{"id":17,"x":25,"y":21,"dx":-1,"dy":0},{"id":15,"x":37,"y":21,"dx":-4,"dy":2},{"id":16,"x":47,"y":21,"dx":-1,"dy":2},{"id":15,"x":3,"y":22,"dx":-4,"dy":1},{"id":15,"x":11,"y":22,"dx":0,"dy":-1},{"id":16,"x":21,"y":22,"dx":2,"dy":0},{"id":16,"x":22,"y":22,"dx":2,"dy":-1},{"id":15,"x":55,"y":22,"dx":-4,"dy":0},{"id":17,"x":3,"y":23,"dx":3,"dy":0},{"id":16,"x":34,"y":23,"dx":2,"dy":-1},{"id":15,"x":41,"y":23,"dx":-2,"dy":-2},{"id":15,"x":46,"y":23,"dx":2,"dy":1},{"id":16,"x":50,"y":23,"dx":2,"dy":0},{"id":15,"x":9,"y":24,"dx":1,"dy":-1},{"id":15,"x":28,"y":24,"dx":3,"dy":-2},{"id":15,"x":32,"y":24,"dx":4,"dy":-1},{"id":16,"x":39,"y":24,"dx":2,"dy":0},{"id":15,"x":1,"y":25,"dx":-3,"dy":1},{"id":15,"x":11,"y":25,"dx":-1,"dy":1},{"id":15,"x":23,"y":25,"dx":3,"dy":-1},{"id":17,"x":25,"y":25,"dx":2,"dy":-1},{"id":17,"x":28,"y":25,"dx":1,"dy":1},{"id":17,"x":32,"y":25,"dx":-3,"dy":1},{"id":15,"x":44,"y":25,"dx":-1,"dy":-1},{"id":15,"x":0,"y":26,"dx":-1,"dy":-2},{"id":17,"x":16,"y":26,"dx":2,"dy":-1},{"id":17,"x":17,"y":26,"dx":-4,"dy":2},{"id":15,"x":26,"y":26,"dx":-3,"dy":-1},{"id":17,"x":31,"y":26,"dx":-3,"dy":-2},{"id":15,"x":51,"y":26,"dx":0,"dy":0},{"id":15,"x":6,"y":27,"dx":-1,"dy":-1},{"id":16,"x":11,"y":27,"dx":3,"dy":0},{"id":17,"x":16,"y":27,"dx":3,"dy":1},{"id":15,"x":29,"y":27,"dx":2,"dy":0},{"id":16,"x":36,"y":27,"dx":4,"dy":-1},{"id":16,"x":38,"y":27,"dx":1,"dy":0},{"id":17,"x":50,"y":27,"dx":-3,"dy":-2},{"id":17,"x":53,"y":27,"dx":1,"dy":-1},{"id":15,"x":14,"y":28,"dx":-4,"dy":1},{"id":17,"x":16,"y":29,"dx":-3,"dy":-1},{"id":17,"x":20,"y":29,"dx":-1,"dy":-1},{"id":15,"x":29,"y":29,"dx":0,"dy":1},{"id":16,"x":33,"y":29,"dx":-2,"dy":2},{"id":16,"x":37,"y":29,"dx":0,"dy":2},{"id":15,"x":7,"y":30,"dx":2,"dy":-1},{"id":15,"x":9,"y":30,"dx":0,"dy":2},{"id":16,"x":21,"y":30,"dx":3,"dy":1},{"id":17,"x":41,"y":30,"dx":-4,"dy":0},{"id":17,"x":28,"y":32,"dx":1,"dy":1},{"id":15,"x":32,"y":32,"dx":-1,"dy":-1},{"id":17,"x":33,"y":32,"dx":0,"dy":-2},{"id":17,"x":41,"y":32,"dx":-3,"dy":0},{"id":16,"x":50,"y":32,"dx":0,"dy":-2},{"id":15,"x":11,"y":33,"dx":-3,"dy":2},{"id":17,"x":54,"y":33,"dx":-3,"dy":1},{"id":15,"x":2,"y":34,"dx":1,"dy":-2},{"id":16,"x":24,"y":34,"dx":3,"dy":0},{"id":15,"x":38,"y":34,"dx":3,"dy":0},{"id":15,"x":14,"y":35,"dx":1,"dy":0},{"id":16,"x":53,"y":35,"dx":2,"dy":-1},{"id":15,"x":4,"y":36,"dx":-2,"dy":1},{"id":17,"x":6,"y":36,"dx":1,"dy":-1},{"id":16,"x":10,"y":36,"dx":0,"dy":0},{"id":17,"x":18,"y":36,"dx":-1,"dy":-1},{"id":16,"x":24,"y":36,"dx":0,"dy":0},{"id":16,"x":26,"y":36,"dx":0,"dy":-2},{"id":17,"x":32,"y":36,"dx":-1,"dy":-2},{"id":15,"x":35,"y":36,"dx":-1,"dy":-1},{"id":17,"x":36,"y":36,"dx":-1,"dy":-1},{"id":15,"x":40,"y":36,"dx":0,"dy":0},{"id":15,"x":43,"y":36,"dx":1,"dy":1},{"id":17,"x":12,"y":37,"dx":-2,"dy":-1},{"id":16,"x":27,"y":37,"dx":0,"dy":0},{"id":15,"x":48,"y":37,"dx":-1,"dy":2},{"id":15,"x":50,"y":37,"dx":4,"dy":2},{"id":15,"x":55,"y":37,"dx":2,"dy":1},{"id":16,"x":3,"y":38,"dx":-3,"dy":2},{"id":17,"x":8,"y":38,"dx":2,"dy":0},{"id":16,"x":28,"y":38,"dx":-1,"dy":-1},{"id":15,"x":34,"y":38,"dx":0,"dy":0},{"id":17,"x":25,"y":39,"dx":3,"dy":2},{"id":17,"x":29,"y":39,"dx":2,"dy":0},{"id":15,"x":33,"y":39,"dx":3,"dy":-2},{"id":16,"x":51,"y":39,"dx":-1,"dy":-1},{"id":16,"x":52,"y":39,"dx":-1,"dy":0},{"id":16,"x":53,"y":39,"dx":-2,"dy":0},{"id":15,"x":54,"y":39,"dx":-4,"dy":-2},{"id":16,"x":4,"y":40,"dx":-2,"dy":1},{"id":17,"x":41,"y":40,"dx":-2,"dy":-2},{"id":16,"x":52,"y":40,"dx":1,"dy":2},{"id":17,"x":10,"y":41,"dx":-3,"dy":2},{"id":16,"x":22,"y":41,"dx":0,"dy":0},{"id":16,"x":23,"y":41,"dx":-3,"dy":-1},{"id":15,"x":31,"y":41,"dx":1,"dy":1},{"id":15,"x":43,"y":41,"dx":2,"dy":-1},{"id":15,"x":47,"y":41,"dx":2,"dy":0},{"id":16,"x":53,"y":41,"dx":3,"dy":1},{"id":17,"x":8,"y":42,"dx":1,"dy":-1},{"id":16,"x":14,"y":42,"dx":0,"dy":-1},{"id":17,"x":19,"y":42,"dx":1,"dy":1},{"id":16,"x":23,"y":42,"dx":-2,"dy":1},{"id":15,"x":28,"y":42,"dx":3,"dy":-1},{"id":16,"x":35,"y":42,"dx":2,"dy":0},{"id":17,"x":41,"y":42,"dx":2,"dy":-2},{"id":15,"x":48,"y":42,"dx":0,"dy":1},{"id":16,"x":52,"y":42,"dx":-1,"dy":1},{"id":17,"x":8,"y":43,"dx":-2,"dy":0},{"id":15,"x":16,"y":43,"dx":-1,"dy":1},{"id":15,"x":0,"y":44,"dx":2,"dy":2},{"id":15,"x":30,"y":44,"dx":1,"dy":0},{"id":17,"x":43,"y":44,"dx":-1,"dy":0},{"id":16,"x":14,"y":45,"dx":0,"dy":1},{"id":15,"x":31,"y":45,"dx":3,"dy":2},{"id":15,"x":37,"y":45,"dx":-2,"dy":2},{"id":15,"x":38,"y":45,"dx":1,"dy":1},{"id":15,"x":42,"y":45,"dx":-2,"dy":1},{"id":15,"x":46,"y":45,"dx":-2,"dy":-2},{"id":15,"x":1,"y":46,"dx":-1,"dy":2},{"id":16,"x":15,"y":46,"dx":-2,"dy":-1},{"id":17,"x":29,"y":46,"dx":3,"dy":1},{"id":15,"x":41,"y":46,"dx":-1,"dy":0},{"id":17,"x":42,"y":46,"dx":4,"dy":-1},{"id":15,"x":44,"y":46,"dx":1,"dy":1},{"id":15,"x":52,"y":46,"dx":-3,"dy":-2},{"id":16,"x":54,"y":46,"dx":0,"dy":0},{"id":15,"x":4,"y":47,"dx":3,"dy":2},{"id":16,"x":15,"y":47,"dx":1,"dy":2},{"id":16,"x":22,"y":47,"dx":-3,"dy":-1},{"id":16,"x":24,"y":47,"dx":1,"dy":2},{"id":16,"x":27,"y":47,"dx":-4,"dy":1},{"id":16,"x":33,"y":47,"dx":-1,"dy":2},{"id":15,"x":38,"y":47,"dx":-2,"dy":-1},{"id":17,"x":37,"y":48,"dx":3,"dy":0},{"id":15,"x":41,"y":48,"dx":0,"dy":0},{"id":15,"x":45,"y":48,"dx":0,"dy":-2},{"id":17,"x":46,"y":48,"dx":-4,"dy":1},{"id":16,"x":34,"y":49,"dx":1,"dy":-1},{"id":17,"x":46,"y":49,"dx":-4,"dy":2},{"id":15,"x":49,"y":49,"dx":-3,"dy":-1},{"id":17,"x":1,"y":50,"dx":-1,"dy":1},{"id":16,"x":8,"y":50,"dx":4,"dy":1},{"id":16,"x":22,"y":50,"dx":3,"dy":-1},{"id":16,"x":31,"y":50,"dx":1,"dy":-2},{"id":16,"x":32,"y":50,"dx":-4,"dy":-1},{"id":15,"x":37,"y":50,"dx":-2,"dy":0},{"id":16,"x":54,"y":50,"dx":3,"dy":-1},{"id":16,"x":17,"y":51,"dx":-3,"dy":-1},{"id":15,"x":18,"y":51,"dx":-2,"dy":0},{"id":16,"x":9,"y":52,"dx":-1,"dy":0},{"id":16,"x":10,"y":52,"dx":-1,"dy":0},{"id":16,"x":13,"y":52,"dx":2,"dy":2},{"id":15,"x":19,"y":52,"dx":0,"dy":1},{"id":15,"x":25,"y":52,"dx":3,"dy":-1},{"id":15,"x":26,"y":52,"dx":0,"dy":0},{"id":15,"x":29,"y":52,"dx":-4,"dy":0},{"id":17,"x":40,"y":52,"dx":3,"dy":-1},{"id":16,"x":52,"y":52,"dx":3,"dy":0},{"id":16,"x":11,"y":53,"dx":1,"dy":1},{"id":16,"x":13,"y":53,"dx":-2,"dy":-1},{"id":16,"x":34,"y":53,"dx":-2,"dy":1},{"id":16,"x":52,"y":53,"dx":0,"dy":-1},{"id":16,"x":16,"y":54,"dx":-1,"dy":1},{"id":16,"x":17,"y":54,"dx":0,"dy":1},{"id":15,"x":19,"y":54,"dx":3,"dy":-1},{"id":15,"x":41,"y":54,"dx":-2,"dy":-1},{"id":16,"x":44,"y":54,"dx":1,"dy":-2},{"id":17,"x":6,"y":55,"dx":2,"dy":-2},{"id":15,"x":25,"y":55,"dx":-3,"dy":-1},{"id":15,"x":34,"y":55,"dx":1,"dy":-1}],"props":[]},"ponds":{"width":48,"height":48,"tiles":[[4,1],[0,5],[4,26],[0,1],[4,1],[0,2],[4,8],[3,1],[4,4],[3,2],[0,3],[4,23],[0,1],[4,1],[0,4],[4,10],[3,1],[4,3],[0,1],[1,1],[0,3],[4,3],[0,2],[4,18],[0,2],[4,1],[0,4],[4,13],[3,2],[0,3],[4,3],[0,1],[4,1],[0,2],[4,16],[0,3],[4,1],[0,1],[4,1],[0,1],[4,8],[3,2],[4,6],[1,1],[0,1],[4,2],[28,1],[0,2],[4,5],[0,1],[4,2],[0,1],[4,4],[28,1],[4,4],[0,2],[4,2],[0,1],[4,1],[0,2],[4,15],[0,2],[1,3],[0,1],[4,6],[0,1],[4,2],[0,1],[4,5],[29,1],[4,3],[0,3],[4,4],[0,2],[4,8],[3,1],[4,6],[0,3],[1,3],[4,6],[0,1],[4,1],[0,2],[4,23],[3,1],[4,6],[3,1],[4,1],[3,1],[4,1],[0,2],[4,1],[1,1],[4,4],[0,3],[4,1],[0,2],[4,10],[29,1],[4,2],[0,1],[4,6],[0,1],[4,1],[0,3],[3,1],[4,4],[3,3],[4,1],[0,2],[4,1],[1,1],[4,1],[0,2],[4,1],[0,2],[4,1],[0,3],[4,12],[0,1],[4,2],[0,1],[4,4],[0,4],[4,3],[3,1],[4,4],[0,1],[4,2],[0,2],[1,2],[4,2],[0,2],[4,2],[29,1],[4,7],[0,1],[4,2],[0,2],[4,1],[0,1],[4,1],[0,2],[4,7],[0,5],[3,1],[4,2],[3,1],[4,7],[1,1],[0,1],[4,1],[0,3],[4,9],[0,2],[4,1],[0,1],[4,3],[0,4],[4,2],[0,1],[4,1],[0,5],[4,2],[3,1],[4,2],[3,1],[4,7],[1,1],[0,2],[4,1],[28,1],[4,10],[0,2],[4,1],[0,2],[4,2],[0,2],[4,4],[0,3],[3,1],[0,1],[4,1],[3,1],[4,11],[0,1],[4,1],[1,1],[0,1],[4,1],[0,2],[4,11],[0,2],[4,1],[0,1],[4,2],[0,1],[4,1],[0,1],[4,2],[5,1],[0,3],[4,1],[0,2],[3,2],[4,1],[3,1],[4,7],[0,1],[4,2],[1,2],[4,8],[2,2],[4,1],[2,1],[4,1],[0,1],[4,1],[0,1],[4,1],[2,1],[4,8],[0,1],[28,1],[0,3],[4,1],[0,1],[4,6],[3,1],[4,3],[0,2],[4,2],[1,1],[4,4],[2,1],[0,1],[2,2],[4,1],[2,3],[4,2],[0,1],[2,2],[4,6],[0,1],[4,1],[0,2],[4,1],[0,1],[4,7],[3,1],[4,3],[3,1],[4,3],[0,1],[4,2],[1,1],[4,4],[0,1],[2,13],[0,1],[2,1],[4,1],[2,1],[4,2],[0,1],[4,1],[0,2],[4,14],[0,1],[5,1],[4,1],[0,1],[4,1],[1,2],[4,2],[2,19],[4,2],[0,1],[4,1],[0,1],[4,3],[3,1],[4,4],[3,2],[4,5],[0,3],[2,3],[1,1],[2,21],[4,1],[2,2],[0,1],[4,1],[0,1],[4,3],[3,1],[4,9],[29,1],[4,1],[0,1],[4,1],[2,1],[4,1],[2,1],[1,3],[2,18],[4,2],[2,1],[4,1],[0,1],[2,1],[4,3],[3,1],[4,9],[0,4],[2,1],[0,1],[4,1],[2,3],[1,4],[2,17],[4,2],[2,1],[0,1],[4,5],[3,1],[4,4],[3,1],[4,3],[0,2],[4,1],[0,2],[2,7],[1,1],[2,21],[4,4],[3,3],[4,3],[3,1],[4,4],[0,2],[2,1],[4,1],[2,7],[1,1],[2,20],[4,1],[2,1],[4,2],[3,1],[4,2],[28,1],[4,5],[3,1],[4,1],[0,2],[4,1],[0,1],[2,8],[1,1],[2,19],[4,1],[2,1],[4,9],[3,1],[4,4],[0,2],[2,2],[4,1],[2,7],[1,1],[2,19],[4,2],[2,1],[4,2],[3,1],[4,11],[2,1],[4,1],[2,9],[1,3],[2,18],[4,18],[2,1],[4,1],[2,9],[1,3],[2,17],[4,3],[0,2],[4,3],[0,2],[3,1],[4,1],[3,1],[4,5],[2,12],[1,2],[2,14],[4,2],[28,1],[4,1],[0,1],[4,1],[3,1],[4,3],[3,1],[4,1],[0,1],[4,5],[2,1],[4,1],[2,13],[1,1],[2,14],[4,5],[3,1],[4,1],[3,2],[4,1],[0,2],[4,2],[3,1],[4,5],[2,1],[4,1],[2,11],[1,1],[2,15],[4,5],[3,1],[4,3],[0,4],[4,5],[2,1],[4,1],[2,12],[1,2],[2,12],[4,11],[0,1],[4,1],[0,2],[4,7],[2,2],[29,1],[2,10],[1,6],[2,7],[4,9],[3,1],[4,3],[3,1],[4,2],[3,1],[4,5],[2,2],[4,1],[2,23],[4,6],[3,1],[4,3],[3,1],[4,1],[0,2],[4,8],[2,1],[4,1],[2,2],[0,1],[2,1],[0,1],[2,13],[4,1],[2,3],[4,8],[3,1],[4,2],[3,1],[4,2],[0,1],[3,2],[4,8],[2,1],[4,2],[0,1],[2,1],[4,1],[2,13],[4,2],[2,1],[4,1],[2,1],[0,1],[4,1],[0,1],[4,4],[3,2],[4,4],[0,2],[3,1],[4,8],[5,1],[4,2],[0,1],[4,1],[0,2],[2,8],[0,1],[2,1],[4,1],[2,3],[4,1],[2,1],[4,1],[5,1],[0,2],[4,11],[3,1],[0,1],[4,11],[0,1],[4,1],[0,5],[2,1],[0,2],[2,1],[0,2],[4,1],[2,1],[4,3],[29,1],[4,2],[0,1],[4,1],[0,1],[4,10],[3,1],[4,2],[3,1],[4,11],[0,5],[4,1],[0,2],[4,2],[0,2],[4,24],[3,1],[4,1],[0,1],[4,9],[0,2],[4,1],[0,5],[4,1],[0,2],[4,22],[3,2],[4,15],[0,5],[4,13],[0,1],[4,15],[0,1],[4,1],[0,1],[4,13],[0,2],[4,1],[0,1],[4,11],[0,2],[4,6],[3,1],[4,5],[3,1],[4,7],[28,1],[4,9],[0,1],[29,1],[4,1],[0,1],[4,9],[0,1],[4,1],[0,3],[4,13],[3,1],[4,3],[0,2],[4,11],[0,1],[29,1],[0,5],[4,6],[0,2],[4,1],[0,2],[4,17],[0,3],[4,11],[0,1],[4,1],[0,2],[5,1],[4,7],[29,1],[4,1],[0,3],[4,9],[3,1],[4,2],[3,3],[4,2],[0,2],[4,15],[0,3],[4,1],[0,2],[4,1],[0,1],[4,1],[0,3],[4,1],[0,1],[4,12],[3,1],[4,1],[3,1],[4,15],[0,2],[4,6],[0,1],[4,2],[0,4],[4,2],[0,1],[4,9],[3,1],[4,21],[0,1],[4,6],[0,1],[4,1],[0,1],[4,1],[0,1],[4,17],[3,1],[4,1],[3,1],[4,3],[28,1],[4,2],[0,1],[4,7],[0,2],[4,8],[0,1],[4,1],[0,2],[4,6],[29,1],[4,6],[3,1],[4,10],[0,2],[4,17],[0,1],[4,1],[0,2],[4,12],[3,1],[4,4]],"blocked":[2,3,6,7,24,25,26,27,9,10,18,21,28,29,30,31,32,33],"tall":{"3":{"height":1.5,"ground":0},"6":{"height":2.5,"ground":1},"7":{"height":1.75,"ground":4,"shadow":true},"9":{"height":1,"ground":8},"18":{"height":0.6,"ground":4},"21":{"height":2.5,"ground":8},"22":{"height":1.5,"ground":0},"23":{"height":1.5,"ground":0},"24":{"height":1.75,"ground":4,"shadow":true},"25":{"height":1.75,"ground":4,"shadow":true},"26":{"height":2.5,"ground":4,"shadow":true},"27":{"height":2.5,"ground":4,"shadow":true},"28":{"height":1.05,"ground":4},"29":{"height":1.25,"ground":4},"30":{"height":1.25,"ground":4},"31":{"height":0.75,"ground":4},"32":{"height":1,"ground":4},"33":{"height":1.45,"ground":4}},"decor":[{"id":15,"x":3,"y":0,"dx":-2,"dy":2},{"id":16,"x":13,"y":0,"dx":0,"dy":0},{"id":16,"x":25,"y":0,"dx":1,"dy":-1},{"id":16,"x":37,"y":0,"dx":-4,"dy":-2},{"id":16,"x":43,"y":0,"dx":-4,"dy":0},{"id":16,"x":47,"y":0,"dx":-2,"dy":1},{"id":16,"x":13,"y":1,"dx":2,"dy":1},{"id":16,"x":22,"y":1,"dx":3,"dy":1},{"id":16,"x":39,"y":1,"dx":-1,"dy":0},{"id":15,"x":34,"y":2,"dx":3,"dy":-2},{"id":16,"x":47,"y":2,"dx":-4,"dy":0},{"id":16,"x":6,"y":3,"dx":0,"dy":-1},{"id":16,"x":23,"y":3,"dx":-3,"dy":0},{"id":16,"x":12,"y":4,"dx":-1,"dy":-1},{"id":16,"x":15,"y":4,"dx":2,"dy":-1},{"id":16,"x":28,"y":4,"dx":2,"dy":-2},{"id":16,"x":32,"y":4,"dx":0,"dy":2},{"id":15,"x":36,"y":4,"dx":-2,"dy":-1},{"id":16,"x":37,"y":4,"dx":-3,"dy":1},{"id":16,"x":0,"y":5,"dx":1,"dy":-1},{"id":16,"x":23,"y":5,"dx":3,"dy":0},{"id":16,"x":26,"y":5,"dx":3,"dy":-1},{"id":17,"x":31,"y":5,"dx":1,"dy":-1},{"id":16,"x":11,"y":6,"dx":1,"dy":0},{"id":16,"x":18,"y":6,"dx":-3,"dy":0},{"id":16,"x":21,"y":6,"dx":-3,"dy":-2},{"id":16,"x":34,"y":6,"dx":3,"dy":-1},{"id":16,"x":37,"y":6,"dx":-4,"dy":-2},{"id":16,"x":47,"y":6,"dx":-1,"dy":1},{"id":16,"x":4,"y":7,"dx":2,"dy":-1},{"id":17,"x":7,"y":7,"dx":4,"dy":0},{"id":16,"x":11,"y":7,"dx":-4,"dy":0},{"id":16,"x":21,"y":7,"dx":1,"dy":2},{"id":16,"x":1,"y":8,"dx":2,"dy":1},{"id":16,"x":2,"y":8,"dx":1,"dy":-2},{"id":16,"x":14,"y":8,"dx":2,"dy":0},{"id":16,"x":28,"y":8,"dx":4,"dy":-1},{"id":17,"x":41,"y":8,"dx":3,"dy":2},{"id":16,"x":47,"y":8,"dx":-2,"dy":2},{"id":16,"x":17,"y":9,"dx":0,"dy":0},{"id":16,"x":28,"y":9,"dx":3,"dy":0},{"id":16,"x":33,"y":9,"dx":0,"dy":-1},{"id":16,"x":17,"y":10,"dx":-2,"dy":-2},{"id":16,"x":22,"y":10,"dx":3,"dy":-1},{"id":16,"x":28,"y":10,"dx":-1,"dy":1},{"id":16,"x":37,"y":10,"dx":2,"dy":1},{"id":16,"x":8,"y":11,"dx":1,"dy":1},{"id":16,"x":9,"y":11,"dx":-1,"dy":1},{"id":16,"x":17,"y":11,"dx":0,"dy":-1},{"id":16,"x":21,"y":11,"dx":0,"dy":-1},{"id":16,"x":22,"y":11,"dx":-1,"dy":-2},{"id":16,"x":25,"y":11,"dx":-4,"dy":-2},{"id":17,"x":34,"y":11,"dx":-2,"dy":-1},{"id":17,"x":41,"y":11,"dx":3,"dy":0},{"id":16,"x":1,"y":12,"dx":1,"dy":0},{"id":15,"x":9,"y":12,"dx":3,"dy":-2},{"id":16,"x":10,"y":12,"dx":4,"dy":1},{"id":16,"x":16,"y":12,"dx":-3,"dy":1},{"id":16,"x":19,"y":12,"dx":1,"dy":-2},{"id":16,"x":26,"y":12,"dx":4,"dy":1},{"id":16,"x":3,"y":13,"dx":0,"dy":1},{"id":16,"x":31,"y":13,"dx":-1,"dy":0},{"id":16,"x":33,"y":13,"dx":2,"dy":-1},{"id":16,"x":37,"y":13,"dx":1,"dy":1},{"id":17,"x":43,"y":13,"dx":0,"dy":-1},{"id":16,"x":1,"y":14,"dx":-1,"dy":0},{"id":16,"x":5,"y":14,"dx":2,"dy":-2},{"id":15,"x":41,"y":14,"dx":-3,"dy":1},{"id":16,"x":42,"y":14,"dx":-1,"dy":-1},{"id":16,"x":3,"y":15,"dx":4,"dy":-1},{"id":16,"x":13,"y":15,"dx":1,"dy":0},{"id":16,"x":16,"y":15,"dx":0,"dy":2},{"id":16,"x":44,"y":15,"dx":2,"dy":1},{"id":16,"x":41,"y":16,"dx":2,"dy":2},{"id":16,"x":45,"y":16,"dx":-2,"dy":-2},{"id":16,"x":46,"y":16,"dx":4,"dy":-1},{"id":16,"x":45,"y":17,"dx":-4,"dy":1},{"id":16,"x":47,"y":17,"dx":3,"dy":1},{"id":15,"x":38,"y":18,"dx":-2,"dy":0},{"id":16,"x":1,"y":19,"dx":2,"dy":0},{"id":16,"x":40,"y":19,"dx":-3,"dy":-1},{"id":16,"x":3,"y":21,"dx":1,"dy":-1},{"id":15,"x":6,"y":22,"dx":-2,"dy":2},{"id":15,"x":7,"y":22,"dx":2,"dy":-2},{"id":15,"x":9,"y":22,"dx":2,"dy":-1},{"id":16,"x":39,"y":23,"dx":-1,"dy":0},{"id":16,"x":46,"y":23,"dx":0,"dy":0},{"id":16,"x":1,"y":24,"dx":2,"dy":-2},{"id":16,"x":3,"y":24,"dx":-3,"dy":0},{"id":16,"x":2,"y":25,"dx":2,"dy":0},{"id":16,"x":46,"y":26,"dx":2,"dy":1},{"id":16,"x":4,"y":27,"dx":1,"dy":-2},{"id":16,"x":38,"y":27,"dx":-2,"dy":1},{"id":16,"x":41,"y":27,"dx":4,"dy":-1},{"id":16,"x":3,"y":28,"dx":2,"dy":-2},{"id":16,"x":47,"y":28,"dx":-2,"dy":2},{"id":16,"x":41,"y":29,"dx":0,"dy":0},{"id":15,"x":3,"y":30,"dx":-3,"dy":0},{"id":16,"x":9,"y":30,"dx":-3,"dy":1},{"id":16,"x":40,"y":30,"dx":-4,"dy":0},{"id":16,"x":41,"y":30,"dx":0,"dy":-2},{"id":16,"x":43,"y":30,"dx":0,"dy":-1},{"id":16,"x":41,"y":31,"dx":4,"dy":-2},{"id":16,"x":31,"y":32,"dx":-2,"dy":-2},{"id":16,"x":36,"y":32,"dx":-2,"dy":-2},{"id":16,"x":47,"y":32,"dx":-3,"dy":1},{"id":17,"x":36,"y":33,"dx":2,"dy":0},{"id":16,"x":39,"y":33,"dx":2,"dy":0},{"id":16,"x":45,"y":33,"dx":2,"dy":2},{"id":16,"x":7,"y":34,"dx":3,"dy":0},{"id":15,"x":27,"y":34,"dx":1,"dy":1},{"id":16,"x":35,"y":34,"dx":-3,"dy":1},{"id":16,"x":39,"y":34,"dx":1,"dy":0},{"id":16,"x":1,"y":35,"dx":-1,"dy":-1},{"id":16,"x":31,"y":35,"dx":2,"dy":-1},{"id":16,"x":8,"y":36,"dx":-1,"dy":1},{"id":16,"x":12,"y":36,"dx":-3,"dy":0},{"id":17,"x":17,"y":36,"dx":-1,"dy":-1},{"id":16,"x":40,"y":36,"dx":2,"dy":-2},{"id":16,"x":45,"y":36,"dx":-1,"dy":-1},{"id":16,"x":46,"y":36,"dx":3,"dy":2},{"id":16,"x":9,"y":37,"dx":2,"dy":-1},{"id":16,"x":13,"y":37,"dx":-2,"dy":1},{"id":15,"x":16,"y":37,"dx":2,"dy":-1},{"id":16,"x":33,"y":37,"dx":1,"dy":1},{"id":16,"x":39,"y":37,"dx":-1,"dy":0},{"id":16,"x":40,"y":37,"dx":-1,"dy":1},{"id":16,"x":41,"y":37,"dx":-4,"dy":1},{"id":16,"x":10,"y":38,"dx":-3,"dy":1},{"id":16,"x":15,"y":38,"dx":2,"dy":0},{"id":17,"x":18,"y":38,"dx":1,"dy":2},{"id":16,"x":44,"y":38,"dx":-3,"dy":-1},{"id":16,"x":45,"y":38,"dx":-3,"dy":0},{"id":16,"x":5,"y":39,"dx":1,"dy":-1},{"id":16,"x":17,"y":39,"dx":-3,"dy":1},{"id":16,"x":3,"y":40,"dx":-2,"dy":0},{"id":16,"x":16,"y":40,"dx":3,"dy":-2},{"id":16,"x":21,"y":40,"dx":-1,"dy":-1},{"id":16,"x":29,"y":40,"dx":-2,"dy":2},{"id":16,"x":41,"y":40,"dx":2,"dy":0},{"id":16,"x":5,"y":41,"dx":-1,"dy":-1},{"id":16,"x":2,"y":42,"dx":3,"dy":-2},{"id":15,"x":6,"y":42,"dx":3,"dy":0},{"id":16,"x":9,"y":42,"dx":-3,"dy":2},{"id":16,"x":42,"y":42,"dx":-2,"dy":1},{"id":16,"x":43,"y":42,"dx":-3,"dy":2},{"id":16,"x":47,"y":42,"dx":4,"dy":1},{"id":16,"x":5,"y":43,"dx":2,"dy":1},{"id":16,"x":8,"y":43,"dx":3,"dy":2},{"id":16,"x":21,"y":43,"dx":-1,"dy":0},{"id":17,"x":28,"y":43,"dx":2,"dy":-1},{"id":15,"x":32,"y":43,"dx":-1,"dy":-2},{"id":16,"x":38,"y":43,"dx":-2,"dy":-2},{"id":16,"x":41,"y":43,"dx":2,"dy":2},{"id":16,"x":42,"y":43,"dx":-3,"dy":-1},{"id":16,"x":46,"y":43,"dx":-1,"dy":0},{"id":16,"x":0,"y":44,"dx":-1,"dy":1},{"id":16,"x":2,"y":44,"dx":3,"dy":2},{"id":15,"x":30,"y":44,"dx":4,"dy":-1},{"id":16,"x":35,"y":44,"dx":1,"dy":0},{"id":16,"x":37,"y":44,"dx":3,"dy":-1},{"id":16,"x":42,"y":44,"dx":1,"dy":-1},{"id":16,"x":14,"y":45,"dx":1,"dy":0},{"id":16,"x":17,"y":45,"dx":-3,"dy":1},{"id":16,"x":25,"y":45,"dx":-4,"dy":0},{"id":15,"x":29,"y":45,"dx":4,"dy":1},{"id":16,"x":30,"y":45,"dx":-2,"dy":2},{"id":16,"x":36,"y":45,"dx":2,"dy":1},{"id":16,"x":39,"y":45,"dx":-1,"dy":0},{"id":16,"x":40,"y":45,"dx":0,"dy":1},{"id":16,"x":9,"y":46,"dx":-3,"dy":2},{"id":16,"x":11,"y":46,"dx":2,"dy":-2},{"id":16,"x":21,"y":46,"dx":1,"dy":-1},{"id":16,"x":22,"y":46,"dx":-3,"dy":2},{"id":16,"x":24,"y":46,"dx":3,"dy":0},{"id":16,"x":3,"y":47,"dx":-1,"dy":2},{"id":16,"x":18,"y":47,"dx":3,"dy":-1},{"id":16,"x":20,"y":47,"dx":1,"dy":1},{"id":15,"x":30,"y":47,"dx":3,"dy":1}],"props":[]},"zvenets":{"width":56,"height":40,"tiles":[[4,10],[0,4],[4,5],[0,1],[4,12],[0,2],[4,11],[0,3],[4,17],[0,1],[4,3],[0,1],[4,4],[0,2],[4,10],[0,4],[4,11],[0,3],[4,4],[3,2],[4,7],[3,1],[4,3],[0,1],[4,8],[0,3],[4,10],[0,1],[4,10],[0,2],[4,1],[0,3],[4,18],[0,4],[4,16],[0,1],[4,12],[0,2],[4,11],[3,1],[4,41],[0,4],[4,12],[3,1],[0,1],[4,2],[0,5],[4,33],[0,2],[4,9],[3,1],[0,1],[4,1],[0,7],[4,34],[0,2],[4,1],[0,1],[4,7],[3,1],[4,1],[0,1],[4,4],[3,1],[0,3],[4,1],[0,1],[4,8],[29,1],[4,34],[3,1],[4,1],[0,2],[4,3],[0,5],[4,39],[0,1],[4,2],[0,2],[4,2],[0,1],[4,1],[3,1],[4,1],[3,1],[0,6],[4,39],[0,4],[4,6],[3,1],[0,1],[4,1],[0,2],[4,3],[0,1],[4,19],[0,1],[4,4],[27,1],[4,11],[0,4],[3,1],[4,8],[28,1],[4,3],[0,2],[4,19],[0,3],[4,15],[0,5],[4,6],[3,1],[4,5],[0,1],[4,1],[0,1],[7,1],[4,1],[0,3],[1,7],[4,1],[1,13],[4,10],[0,5],[3,1],[4,16],[0,1],[10,1],[0,1],[1,11],[4,1],[1,5],[4,1],[1,4],[4,9],[0,1],[4,1],[0,1],[4,1],[0,1],[4,6],[3,2],[4,9],[0,2],[4,1],[1,1],[0,1],[1,8],[6,1],[1,11],[4,10],[0,1],[4,1],[0,1],[4,17],[0,3],[4,1],[1,5],[4,1],[1,6],[4,1],[1,8],[4,1],[0,2],[4,28],[0,2],[4,1],[0,1],[1,3],[4,1],[1,16],[4,1],[1,1],[4,1],[0,3],[4,8],[3,1],[4,1],[3,1],[4,16],[0,3],[1,3],[4,1],[1,18],[0,1],[4,1],[0,1],[4,8],[3,2],[4,5],[3,1],[4,5],[0,4],[4,3],[0,2],[4,1],[1,22],[4,1],[0,3],[4,15],[3,1],[4,1],[3,1],[4,2],[0,2],[4,8],[1,5],[4,1],[1,16],[0,3],[26,1],[4,14],[1,21],[4,1],[1,4],[0,1],[4,1],[1,10],[4,11],[3,1],[4,1],[1,2],[4,10],[0,2],[4,5],[0,1],[4,1],[1,22],[4,3],[29,1],[4,12],[0,2],[4,2],[3,1],[4,3],[0,1],[4,2],[0,1],[4,6],[0,1],[1,12],[4,1],[1,2],[4,2],[1,4],[4,11],[3,1],[4,4],[0,2],[4,4],[0,1],[4,2],[0,1],[4,8],[1,12],[4,1],[1,1],[4,1],[1,7],[4,8],[0,3],[4,1],[0,3],[4,1],[0,2],[3,1],[4,4],[0,2],[4,9],[1,8],[4,1],[1,9],[4,1],[1,3],[4,10],[0,1],[3,1],[4,1],[3,1],[0,2],[4,1],[0,1],[4,1],[0,2],[3,1],[0,1],[4,3],[0,1],[4,7],[1,4],[4,1],[1,9],[0,1],[1,1],[0,1],[1,5],[4,8],[0,1],[4,1],[0,1],[3,2],[4,1],[0,1],[3,1],[4,1],[0,1],[3,1],[4,1],[3,1],[4,1],[0,2],[4,1],[0,2],[4,7],[1,16],[4,1],[1,4],[4,9],[0,2],[4,2],[0,2],[4,2],[0,1],[4,1],[0,2],[4,2],[0,1],[4,1],[0,2],[4,8],[0,1],[1,21],[4,9],[0,2],[4,1],[0,1],[4,1],[0,1],[3,1],[0,1],[4,1],[3,1],[0,2],[4,14],[0,4],[24,1],[4,1],[0,1],[4,5],[0,2],[25,1],[4,18],[3,1],[4,3],[0,4],[4,13],[0,1],[4,1],[0,5],[4,7],[0,3],[4,26],[3,1],[4,12],[0,7],[4,7],[0,1],[4,20],[3,1],[4,2],[3,1],[4,2],[3,1],[4,15],[0,1],[4,1],[0,4],[4,2],[0,1],[4,34],[3,1],[4,11],[0,10],[4,24],[3,1],[4,8],[3,1],[4,12],[0,2],[4,2],[0,2],[4,1],[0,1],[4,1],[0,1],[4,11],[29,1],[4,16],[3,1],[0,2],[4,7],[29,1],[4,11],[0,2],[4,1],[0,1],[4,1],[0,1],[4,27],[3,1],[4,1],[0,3],[4,2],[3,1],[4,14],[0,2],[4,1],[0,4],[4,29],[0,3],[4,19],[0,1],[4,34],[3,1],[4,50],[0,1],[4,6],[3,1],[4,9],[28,1],[4,19],[0,2],[4,15],[0,1],[4,1],[0,1],[3,2],[4,4],[3,1],[4,28],[0,3],[4,21]],"blocked":[2,3,6,7,24,25,26,27,9,10,18,21,28,29,30,31,32,33],"tall":{"3":{"height":1.5,"ground":0},"6":{"height":2.5,"ground":1},"7":{"height":1.75,"ground":4,"shadow":true},"9":{"height":1,"ground":8},"18":{"height":0.6,"ground":4},"21":{"height":2.5,"ground":8},"22":{"height":1.5,"ground":0},"23":{"height":1.5,"ground":0},"24":{"height":1.75,"ground":4,"shadow":true},"25":{"height":1.75,"ground":4,"shadow":true},"26":{"height":2.5,"ground":4,"shadow":true},"27":{"height":2.5,"ground":4,"shadow":true},"28":{"height":1.05,"ground":4},"29":{"height":1.25,"ground":4},"30":{"height":1.25,"ground":4},"31":{"height":0.75,"ground":4},"32":{"height":1,"ground":4},"33":{"height":1.45,"ground":4}},"decor":[{"id":16,"x":14,"y":0,"dx":1,"dy":1},{"id":16,"x":23,"y":0,"dx":0,"dy":1},{"id":16,"x":26,"y":0,"dx":3,"dy":1},{"id":16,"x":31,"y":0,"dx":0,"dy":0},{"id":16,"x":37,"y":0,"dx":3,"dy":0},{"id":16,"x":39,"y":0,"dx":3,"dy":2},{"id":16,"x":49,"y":0,"dx":-1,"dy":1},{"id":16,"x":3,"y":1,"dx":1,"dy":0},{"id":16,"x":5,"y":1,"dx":4,"dy":1},{"id":15,"x":13,"y":1,"dx":-1,"dy":-2},{"id":16,"x":39,"y":1,"dx":4,"dy":1},{"id":17,"x":45,"y":1,"dx":0,"dy":0},{"id":16,"x":55,"y":2,"dx":3,"dy":-1},{"id":16,"x":21,"y":3,"dx":1,"dy":-1},{"id":16,"x":27,"y":3,"dx":-2,"dy":-2},{"id":16,"x":34,"y":3,"dx":1,"dy":-2},{"id":16,"x":36,"y":3,"dx":0,"dy":0},{"id":16,"x":3,"y":4,"dx":1,"dy":-1},{"id":16,"x":4,"y":4,"dx":3,"dy":2},{"id":16,"x":11,"y":4,"dx":2,"dy":0},{"id":16,"x":13,"y":4,"dx":2,"dy":1},{"id":16,"x":18,"y":4,"dx":1,"dy":0},{"id":16,"x":51,"y":4,"dx":1,"dy":-1},{"id":16,"x":5,"y":5,"dx":0,"dy":2},{"id":16,"x":19,"y":5,"dx":0,"dy":1},{"id":16,"x":31,"y":5,"dx":-3,"dy":-1},{"id":15,"x":5,"y":6,"dx":4,"dy":0},{"id":16,"x":13,"y":6,"dx":2,"dy":2},{"id":16,"x":18,"y":6,"dx":-3,"dy":-1},{"id":16,"x":24,"y":6,"dx":-2,"dy":2},{"id":16,"x":41,"y":6,"dx":1,"dy":0},{"id":16,"x":48,"y":6,"dx":-2,"dy":1},{"id":15,"x":6,"y":7,"dx":-1,"dy":-1},{"id":16,"x":13,"y":7,"dx":3,"dy":1},{"id":16,"x":20,"y":7,"dx":-4,"dy":2},{"id":16,"x":26,"y":8,"dx":3,"dy":-2},{"id":16,"x":47,"y":8,"dx":3,"dy":-2},{"id":15,"x":9,"y":9,"dx":0,"dy":2},{"id":16,"x":17,"y":10,"dx":3,"dy":2},{"id":16,"x":20,"y":10,"dx":-3,"dy":-2},{"id":16,"x":38,"y":10,"dx":-3,"dy":1},{"id":16,"x":45,"y":10,"dx":0,"dy":-2},{"id":16,"x":12,"y":11,"dx":2,"dy":-1},{"id":16,"x":17,"y":11,"dx":-3,"dy":1},{"id":16,"x":46,"y":11,"dx":0,"dy":1},{"id":15,"x":51,"y":11,"dx":2,"dy":0},{"id":16,"x":8,"y":12,"dx":1,"dy":-1},{"id":16,"x":14,"y":12,"dx":-4,"dy":1},{"id":16,"x":41,"y":12,"dx":3,"dy":2},{"id":16,"x":45,"y":12,"dx":3,"dy":0},{"id":16,"x":0,"y":13,"dx":1,"dy":-2},{"id":16,"x":4,"y":13,"dx":-3,"dy":1},{"id":16,"x":8,"y":13,"dx":3,"dy":-1},{"id":16,"x":42,"y":13,"dx":3,"dy":2},{"id":16,"x":44,"y":13,"dx":0,"dy":2},{"id":16,"x":50,"y":13,"dx":-1,"dy":2},{"id":16,"x":54,"y":13,"dx":-1,"dy":2},{"id":16,"x":49,"y":14,"dx":-3,"dy":2},{"id":16,"x":55,"y":14,"dx":-2,"dy":-2},{"id":16,"x":6,"y":15,"dx":0,"dy":1},{"id":16,"x":8,"y":15,"dx":0,"dy":-1},{"id":16,"x":10,"y":15,"dx":-4,"dy":1},{"id":16,"x":30,"y":15,"dx":-1,"dy":-1},{"id":16,"x":47,"y":15,"dx":3,"dy":-1},{"id":16,"x":51,"y":15,"dx":-1,"dy":-1},{"id":16,"x":6,"y":16,"dx":-1,"dy":2},{"id":16,"x":8,"y":16,"dx":3,"dy":2},{"id":16,"x":11,"y":16,"dx":1,"dy":1},{"id":16,"x":38,"y":16,"dx":2,"dy":1},{"id":16,"x":44,"y":16,"dx":0,"dy":1},{"id":16,"x":47,"y":16,"dx":1,"dy":0},{"id":16,"x":3,"y":17,"dx":2,"dy":1},{"id":16,"x":11,"y":17,"dx":3,"dy":-2},{"id":16,"x":1,"y":18,"dx":2,"dy":-2},{"id":16,"x":14,"y":18,"dx":-2,"dy":-1},{"id":16,"x":48,"y":18,"dx":0,"dy":-1},{"id":16,"x":4,"y":19,"dx":-1,"dy":0},{"id":16,"x":6,"y":19,"dx":3,"dy":-1},{"id":17,"x":9,"y":19,"dx":3,"dy":0},{"id":16,"x":14,"y":19,"dx":-1,"dy":2},{"id":16,"x":54,"y":19,"dx":-1,"dy":2},{"id":16,"x":1,"y":20,"dx":-4,"dy":2},{"id":16,"x":29,"y":20,"dx":1,"dy":1},{"id":16,"x":40,"y":20,"dx":-1,"dy":0},{"id":16,"x":4,"y":21,"dx":-1,"dy":1},{"id":16,"x":8,"y":21,"dx":-1,"dy":-2},{"id":17,"x":16,"y":21,"dx":-4,"dy":1},{"id":16,"x":17,"y":21,"dx":4,"dy":-2},{"id":16,"x":13,"y":22,"dx":2,"dy":-1},{"id":16,"x":52,"y":22,"dx":0,"dy":-2},{"id":16,"x":5,"y":23,"dx":0,"dy":-1},{"id":16,"x":46,"y":23,"dx":1,"dy":-1},{"id":16,"x":55,"y":23,"dx":-3,"dy":1},{"id":16,"x":5,"y":24,"dx":-2,"dy":-1},{"id":16,"x":17,"y":24,"dx":-1,"dy":1},{"id":16,"x":49,"y":24,"dx":4,"dy":-1},{"id":15,"x":3,"y":25,"dx":-2,"dy":-1},{"id":15,"x":10,"y":25,"dx":2,"dy":-2},{"id":16,"x":17,"y":25,"dx":-3,"dy":-2},{"id":15,"x":34,"y":25,"dx":-1,"dy":-1},{"id":16,"x":41,"y":25,"dx":-3,"dy":-1},{"id":16,"x":42,"y":25,"dx":-2,"dy":0},{"id":16,"x":3,"y":26,"dx":2,"dy":0},{"id":17,"x":7,"y":26,"dx":-3,"dy":-1},{"id":16,"x":13,"y":26,"dx":-2,"dy":0},{"id":16,"x":45,"y":26,"dx":-4,"dy":0},{"id":16,"x":51,"y":26,"dx":-2,"dy":0},{"id":16,"x":13,"y":27,"dx":-1,"dy":-1},{"id":16,"x":16,"y":27,"dx":-3,"dy":1},{"id":16,"x":46,"y":27,"dx":3,"dy":-2},{"id":16,"x":53,"y":27,"dx":3,"dy":2},{"id":16,"x":12,"y":28,"dx":2,"dy":1},{"id":17,"x":19,"y":28,"dx":-2,"dy":-2},{"id":16,"x":40,"y":28,"dx":0,"dy":-2},{"id":16,"x":41,"y":28,"dx":2,"dy":1},{"id":15,"x":2,"y":29,"dx":0,"dy":-2},{"id":16,"x":6,"y":29,"dx":-4,"dy":-1},{"id":15,"x":20,"y":29,"dx":-2,"dy":-1},{"id":16,"x":46,"y":29,"dx":0,"dy":1},{"id":16,"x":52,"y":29,"dx":-1,"dy":1},{"id":16,"x":53,"y":29,"dx":-4,"dy":0},{"id":16,"x":54,"y":29,"dx":-3,"dy":1},{"id":16,"x":0,"y":30,"dx":3,"dy":-2},{"id":16,"x":1,"y":30,"dx":2,"dy":-2},{"id":16,"x":25,"y":30,"dx":1,"dy":0},{"id":16,"x":34,"y":30,"dx":-2,"dy":1},{"id":16,"x":36,"y":30,"dx":-2,"dy":1},{"id":16,"x":37,"y":30,"dx":0,"dy":-2},{"id":16,"x":41,"y":30,"dx":4,"dy":-1},{"id":16,"x":48,"y":30,"dx":2,"dy":1},{"id":15,"x":20,"y":31,"dx":2,"dy":1},{"id":16,"x":29,"y":31,"dx":0,"dy":-1},{"id":16,"x":36,"y":31,"dx":-1,"dy":-2},{"id":16,"x":48,"y":31,"dx":1,"dy":1},{"id":16,"x":49,"y":31,"dx":3,"dy":-1},{"id":16,"x":32,"y":32,"dx":0,"dy":0},{"id":16,"x":37,"y":32,"dx":-2,"dy":2},{"id":16,"x":41,"y":32,"dx":-1,"dy":-2},{"id":16,"x":52,"y":32,"dx":-1,"dy":-2},{"id":16,"x":54,"y":32,"dx":3,"dy":1},{"id":16,"x":7,"y":33,"dx":1,"dy":1},{"id":16,"x":10,"y":33,"dx":-1,"dy":0},{"id":16,"x":48,"y":33,"dx":-2,"dy":1},{"id":15,"x":0,"y":34,"dx":4,"dy":-1},{"id":16,"x":12,"y":34,"dx":0,"dy":-1},{"id":16,"x":33,"y":34,"dx":2,"dy":-2},{"id":16,"x":38,"y":34,"dx":3,"dy":1},{"id":16,"x":53,"y":34,"dx":0,"dy":-1},{"id":15,"x":20,"y":35,"dx":-1,"dy":1},{"id":16,"x":35,"y":35,"dx":2,"dy":-2},{"id":16,"x":39,"y":35,"dx":-2,"dy":1},{"id":16,"x":47,"y":35,"dx":-3,"dy":-1},{"id":16,"x":55,"y":35,"dx":-2,"dy":-2},{"id":16,"x":11,"y":36,"dx":1,"dy":-1},{"id":16,"x":14,"y":36,"dx":-2,"dy":0},{"id":16,"x":26,"y":36,"dx":0,"dy":2},{"id":16,"x":39,"y":36,"dx":0,"dy":0},{"id":16,"x":42,"y":36,"dx":0,"dy":-2},{"id":16,"x":45,"y":36,"dx":2,"dy":-1},{"id":16,"x":4,"y":37,"dx":-3,"dy":1},{"id":16,"x":10,"y":37,"dx":3,"dy":1},{"id":16,"x":15,"y":37,"dx":-3,"dy":1},{"id":16,"x":34,"y":37,"dx":0,"dy":-2},{"id":16,"x":47,"y":37,"dx":1,"dy":1},{"id":16,"x":49,"y":37,"dx":-2,"dy":1},{"id":16,"x":53,"y":37,"dx":3,"dy":-1},{"id":16,"x":55,"y":37,"dx":-1,"dy":0},{"id":16,"x":6,"y":38,"dx":-3,"dy":2},{"id":16,"x":7,"y":38,"dx":0,"dy":2},{"id":16,"x":38,"y":38,"dx":-3,"dy":-2},{"id":16,"x":41,"y":38,"dx":-1,"dy":-1},{"id":16,"x":45,"y":38,"dx":2,"dy":0},{"id":16,"x":7,"y":39,"dx":2,"dy":1},{"id":16,"x":16,"y":39,"dx":2,"dy":0},{"id":16,"x":19,"y":39,"dx":-3,"dy":0},{"id":16,"x":22,"y":39,"dx":0,"dy":0},{"id":16,"x":26,"y":39,"dx":2,"dy":-1},{"id":16,"x":38,"y":39,"dx":-4,"dy":-1},{"id":16,"x":43,"y":39,"dx":-2,"dy":-2},{"id":16,"x":44,"y":39,"dx":-3,"dy":-2},{"id":16,"x":46,"y":39,"dx":1,"dy":0},{"id":16,"x":52,"y":39,"dx":-1,"dy":1}],"props":[{"id":34,"x":46,"y":24,"w":2,"h":2,"ground":4}]},"rust_grove":{"width":48,"height":40,"tiles":[[3,8],[4,3],[3,5],[4,1],[3,3],[4,1],[3,1],[4,1],[3,2],[4,1],[3,2],[4,1],[3,1],[4,1],[3,7],[4,1],[3,4],[4,1],[3,3],[4,1],[3,3],[4,2],[3,5],[4,2],[3,1],[4,1],[3,2],[4,1],[3,6],[4,1],[3,2],[4,1],[3,1],[4,1],[3,5],[4,1],[3,7],[4,1],[3,3],[4,1],[3,7],[29,1],[3,2],[28,1],[3,1],[4,1],[3,2],[4,1],[3,1],[0,1],[3,4],[4,3],[3,10],[4,1],[3,6],[4,2],[3,11],[4,1],[3,1],[4,1],[3,2],[4,1],[3,4],[4,3],[3,1],[4,1],[3,4],[4,2],[3,1],[4,1],[3,1],[4,3],[3,2],[4,1],[3,4],[4,2],[3,15],[4,1],[3,4],[29,1],[3,4],[4,4],[3,4],[4,6],[3,3],[4,1],[3,3],[4,1],[3,3],[4,1],[3,6],[4,2],[3,1],[4,3],[3,5],[4,3],[3,2],[4,1],[3,3],[4,1],[3,6],[4,4],[3,1],[4,1],[3,1],[4,1],[3,6],[4,2],[3,6],[4,1],[3,3],[4,2],[3,2],[4,4],[3,4],[4,1],[3,2],[4,1],[3,8],[18,1],[4,2],[3,1],[0,1],[4,2],[3,3],[4,1],[3,4],[4,1],[3,2],[4,1],[3,2],[4,1],[3,4],[4,1],[3,2],[4,1],[3,2],[4,5],[3,1],[4,1],[3,3],[0,1],[3,3],[4,1],[3,1],[4,1],[3,1],[4,3],[3,3],[4,2],[3,1],[4,2],[3,1],[4,1],[3,5],[4,2],[3,1],[4,1],[3,1],[4,1],[3,2],[18,1],[4,1],[3,1],[4,1],[3,1],[4,1],[3,1],[4,2],[3,3],[4,1],[3,3],[4,3],[3,1],[4,2],[3,3],[4,2],[3,8],[4,4],[3,7],[4,1],[18,1],[3,3],[4,6],[3,1],[4,1],[3,1],[4,1],[3,2],[4,1],[3,2],[4,1],[3,3],[4,3],[3,1],[0,1],[3,3],[4,1],[3,6],[4,2],[3,3],[4,1],[3,2],[4,1],[3,1],[4,1],[18,1],[3,1],[0,1],[4,2],[3,2],[4,5],[18,1],[4,1],[3,1],[4,1],[3,2],[4,3],[3,1],[4,2],[3,1],[4,1],[3,2],[4,1],[3,1],[4,2],[3,6],[4,1],[3,4],[4,1],[3,2],[4,2],[3,2],[4,1],[3,3],[4,1],[3,1],[4,3],[3,1],[18,1],[3,1],[4,3],[3,1],[4,1],[3,1],[4,3],[3,4],[0,1],[3,4],[4,1],[3,1],[4,1],[3,1],[4,2],[0,1],[4,3],[3,1],[4,1],[3,1],[4,1],[3,1],[4,3],[3,1],[4,1],[3,3],[4,1],[29,1],[3,5],[4,2],[3,1],[4,1],[3,1],[4,5],[3,2],[4,1],[3,1],[4,1],[3,2],[4,4],[3,1],[4,2],[3,1],[4,1],[3,5],[4,5],[3,2],[4,2],[3,3],[4,3],[3,2],[4,4],[3,1],[4,1],[3,1],[4,4],[3,1],[4,1],[3,4],[4,4],[3,1],[4,1],[3,2],[4,3],[3,2],[4,7],[3,3],[4,4],[3,2],[4,4],[3,2],[4,1],[3,1],[4,5],[3,3],[4,6],[3,5],[4,1],[3,3],[4,3],[3,2],[4,2],[3,3],[4,1],[3,1],[4,15],[3,2],[4,2],[3,2],[4,1],[3,1],[4,2],[3,1],[4,2],[3,2],[4,2],[3,1],[4,5],[3,2],[4,1],[3,1],[4,3],[3,2],[4,11],[3,1],[4,5],[3,2],[4,1],[29,1],[3,1],[4,1],[3,2],[4,2],[3,1],[4,1],[3,6],[4,2],[3,1],[4,2],[3,2],[4,2],[3,1],[4,17],[3,5],[4,1],[3,1],[4,3],[3,4],[4,1],[3,2],[4,2],[3,2],[4,2],[3,1],[4,1],[3,1],[4,3],[3,1],[4,2],[3,2],[4,13],[3,1],[4,1],[3,1],[4,1],[3,1],[4,1],[3,2],[4,1],[3,2],[4,2],[3,1],[4,4],[3,2],[4,1],[3,3],[4,1],[3,1],[4,1],[3,1],[4,2],[3,1],[4,3],[3,1],[4,13],[3,1],[4,1],[3,1],[4,1],[28,1],[3,1],[4,1],[3,1],[4,1],[3,2],[29,1],[4,3],[3,3],[1,17],[4,13],[1,16],[4,3],[3,9],[4,1],[3,1],[4,1],[3,1],[4,2],[3,1],[4,13],[3,2],[4,1],[3,2],[4,3],[3,2],[4,1],[3,5],[4,3],[3,1],[4,1],[3,3],[28,1],[4,1],[3,3],[4,1],[3,2],[4,17],[3,2],[4,1],[3,1],[4,1],[3,3],[4,2],[3,4],[4,3],[3,1],[4,2],[3,4],[4,1],[3,1],[4,1],[3,1],[4,1],[3,1],[4,3],[3,1],[4,14],[3,1],[4,1],[3,1],[4,2],[3,1],[4,2],[3,1],[4,1],[3,3],[4,1],[3,1],[4,1],[0,1],[3,1],[4,1],[3,1],[4,1],[3,3],[4,1],[3,4],[4,1],[3,2],[4,1],[3,1],[4,1],[3,1],[4,9],[3,1],[4,3],[3,2],[4,1],[3,1],[4,1],[29,1],[4,3],[3,3],[4,1],[3,3],[4,1],[3,3],[4,2],[3,1],[4,1],[3,1],[4,2],[3,1],[4,2],[3,1],[4,1],[3,1],[4,1],[3,1],[4,4],[3,1],[4,1],[3,1],[4,2],[3,1],[4,1],[3,5],[4,1],[3,1],[4,4],[3,1],[4,1],[3,2],[4,1],[3,3],[4,1],[3,4],[4,3],[3,1],[4,3],[3,1],[4,4],[3,3],[4,3],[3,3],[4,3],[3,2],[4,3],[3,1],[4,1],[3,3],[4,2],[3,1],[4,2],[3,3],[4,1],[3,8],[0,1],[3,4],[4,1],[3,2],[4,3],[3,1],[4,3],[3,1],[4,2],[3,1],[4,1],[3,5],[4,4],[3,4],[4,3],[3,5],[4,1],[3,1],[4,2],[3,1],[4,1],[3,1],[4,1],[3,1],[4,5],[3,1],[4,2],[3,2],[4,6],[0,1],[3,1],[4,2],[3,1],[4,1],[18,1],[3,1],[18,1],[3,2],[4,2],[3,2],[4,2],[3,11],[4,2],[3,4],[4,1],[3,1],[4,1],[3,4],[4,3],[3,2],[4,2],[3,2],[4,3],[3,3],[4,1],[3,2],[4,1],[3,3],[4,1],[3,4],[4,2],[3,2],[4,1],[3,1],[4,2],[3,6],[4,2],[3,2],[18,1],[3,1],[18,1],[3,1],[4,1],[3,1],[4,3],[3,2],[4,4],[3,1],[4,2],[3,2],[4,1],[3,4],[4,2],[3,3],[4,1],[3,5],[4,1],[3,1],[4,1],[3,2],[4,1],[3,1],[4,2],[3,1],[4,1],[3,1],[4,1],[3,2],[4,1],[3,1],[4,3],[3,1],[4,2],[3,1],[4,3],[3,2],[4,1],[3,3],[4,1],[3,3],[4,1],[3,1],[4,1],[3,2],[4,1],[3,1],[4,3],[3,6],[4,1],[3,3],[4,1],[3,1],[4,1],[3,2],[4,2],[3,1],[4,1],[3,1],[4,2],[3,1],[4,6],[3,1],[4,2],[3,1],[4,1],[3,1],[4,4],[3,4],[4,1],[3,6],[28,1],[3,2],[4,1],[3,1],[4,1],[3,1],[4,4],[3,4],[4,3],[3,1],[4,1],[3,1],[4,2],[3,10],[4,1],[3,11],[4,1],[3,1],[4,1],[3,1],[4,2],[3,2],[4,1],[3,1],[4,4],[3,2],[4,4],[3,1],[4,2],[3,3],[4,1],[3,1],[4,4],[3,1],[4,2],[3,1],[4,1],[3,1],[28,1],[4,2],[3,4],[4,1],[3,7],[4,1],[3,2],[4,1],[3,2],[4,1],[3,1],[4,1],[3,1],[4,1],[3,1],[4,1],[3,2],[4,2],[3,2],[4,1],[3,1],[29,1],[3,1],[4,1],[3,1],[4,2],[3,2],[4,1],[3,3],[4,1],[3,4],[4,2],[3,7],[4,2],[3,1],[4,1],[3,1],[4,4],[3,2],[4,2],[3,1],[4,2],[3,3],[4,1],[3,5],[4,3],[3,2],[4,1],[3,5],[4,1],[3,4],[4,1],[3,4],[4,2],[3,1],[4,2],[3,1],[4,2],[3,3],[4,2],[3,1],[29,1],[3,2],[4,2],[3,1],[4,1],[3,1],[4,1],[3,1],[4,1],[3,1],[4,1],[3,1],[4,2],[3,4],[4,1],[3,7],[4,1],[3,7],[4,3],[3,4],[4,1],[3,2],[4,1],[3,5],[4,6],[3,1],[4,4],[3,2],[4,2],[3,3],[4,1],[3,8],[4,1],[3,6],[4,4],[3,3],[4,2],[3,2],[4,1],[3,1],[4,2],[3,2],[4,1],[3,1],[4,2],[3,4],[4,2],[3,3],[4,1],[3,1],[4,1],[3,5]],"blocked":[2,3,6,7,24,25,26,27,9,10,18,21,28,29,30,31,32,33],"tall":{"3":{"height":1.5,"ground":0},"6":{"height":2.5,"ground":1},"7":{"height":1.75,"ground":4,"shadow":true},"9":{"height":1,"ground":8},"18":{"height":0.6,"ground":4},"21":{"height":2.5,"ground":8},"22":{"height":1.5,"ground":0},"23":{"height":1.5,"ground":0},"24":{"height":1.75,"ground":4,"shadow":true},"25":{"height":1.75,"ground":4,"shadow":true},"26":{"height":2.5,"ground":4,"shadow":true},"27":{"height":2.5,"ground":4,"shadow":true},"28":{"height":1.05,"ground":4},"29":{"height":1.25,"ground":4},"30":{"height":1.25,"ground":4},"31":{"height":0.75,"ground":4},"32":{"height":1,"ground":4},"33":{"height":1.45,"ground":4}},"decor":[{"id":16,"x":10,"y":0,"dx":1,"dy":1},{"id":16,"x":3,"y":1,"dx":3,"dy":2},{"id":16,"x":26,"y":1,"dx":-3,"dy":1},{"id":16,"x":42,"y":1,"dx":-2,"dy":1},{"id":16,"x":46,"y":1,"dx":3,"dy":0},{"id":16,"x":14,"y":2,"dx":3,"dy":1},{"id":16,"x":23,"y":2,"dx":-1,"dy":-2},{"id":16,"x":16,"y":5,"dx":0,"dy":-1},{"id":16,"x":9,"y":6,"dx":2,"dy":1},{"id":16,"x":12,"y":6,"dx":1,"dy":0},{"id":16,"x":20,"y":6,"dx":-2,"dy":-1},{"id":16,"x":47,"y":6,"dx":-3,"dy":1},{"id":16,"x":2,"y":7,"dx":-4,"dy":1},{"id":16,"x":10,"y":7,"dx":3,"dy":2},{"id":16,"x":16,"y":7,"dx":2,"dy":-1},{"id":16,"x":17,"y":7,"dx":-2,"dy":2},{"id":16,"x":20,"y":7,"dx":-2,"dy":0},{"id":16,"x":37,"y":8,"dx":1,"dy":1},{"id":16,"x":0,"y":9,"dx":-1,"dy":-1},{"id":16,"x":15,"y":9,"dx":-2,"dy":2},{"id":16,"x":27,"y":9,"dx":2,"dy":1},{"id":15,"x":38,"y":9,"dx":-3,"dy":-2},{"id":16,"x":23,"y":10,"dx":3,"dy":-1},{"id":16,"x":2,"y":11,"dx":3,"dy":2},{"id":16,"x":21,"y":11,"dx":-2,"dy":0},{"id":16,"x":31,"y":12,"dx":1,"dy":-2},{"id":16,"x":10,"y":13,"dx":-3,"dy":-1},{"id":16,"x":29,"y":13,"dx":-3,"dy":0},{"id":16,"x":36,"y":13,"dx":-3,"dy":1},{"id":16,"x":44,"y":13,"dx":2,"dy":2},{"id":16,"x":45,"y":13,"dx":-1,"dy":-2},{"id":16,"x":13,"y":15,"dx":-2,"dy":1},{"id":16,"x":30,"y":15,"dx":3,"dy":2},{"id":16,"x":33,"y":15,"dx":-2,"dy":0},{"id":16,"x":22,"y":16,"dx":2,"dy":-2},{"id":16,"x":26,"y":16,"dx":-3,"dy":0},{"id":16,"x":30,"y":16,"dx":4,"dy":1},{"id":16,"x":32,"y":16,"dx":3,"dy":0},{"id":16,"x":37,"y":16,"dx":0,"dy":-1},{"id":16,"x":12,"y":17,"dx":-4,"dy":0},{"id":16,"x":27,"y":17,"dx":-2,"dy":-1},{"id":16,"x":19,"y":18,"dx":-3,"dy":-2},{"id":16,"x":29,"y":18,"dx":3,"dy":-1},{"id":16,"x":19,"y":19,"dx":2,"dy":-1},{"id":16,"x":43,"y":19,"dx":2,"dy":-2},{"id":16,"x":44,"y":19,"dx":-2,"dy":1},{"id":16,"x":27,"y":20,"dx":0,"dy":-2},{"id":16,"x":20,"y":21,"dx":2,"dy":0},{"id":16,"x":21,"y":21,"dx":-2,"dy":-2},{"id":16,"x":24,"y":21,"dx":1,"dy":0},{"id":16,"x":1,"y":22,"dx":3,"dy":1},{"id":16,"x":16,"y":22,"dx":1,"dy":0},{"id":16,"x":19,"y":22,"dx":1,"dy":-2},{"id":16,"x":46,"y":22,"dx":0,"dy":1},{"id":16,"x":47,"y":22,"dx":0,"dy":0},{"id":16,"x":2,"y":23,"dx":-3,"dy":0},{"id":16,"x":20,"y":23,"dx":4,"dy":0},{"id":16,"x":33,"y":23,"dx":2,"dy":1},{"id":16,"x":41,"y":23,"dx":3,"dy":-1},{"id":16,"x":47,"y":23,"dx":-2,"dy":-2},{"id":15,"x":0,"y":24,"dx":-3,"dy":-1},{"id":16,"x":16,"y":24,"dx":1,"dy":2},{"id":16,"x":27,"y":24,"dx":0,"dy":0},{"id":16,"x":6,"y":25,"dx":-3,"dy":0},{"id":16,"x":38,"y":25,"dx":-2,"dy":-1},{"id":16,"x":2,"y":26,"dx":2,"dy":-1},{"id":16,"x":13,"y":26,"dx":1,"dy":1},{"id":16,"x":8,"y":28,"dx":0,"dy":2},{"id":16,"x":31,"y":28,"dx":-1,"dy":-1},{"id":16,"x":7,"y":30,"dx":2,"dy":0},{"id":16,"x":32,"y":30,"dx":2,"dy":-1},{"id":16,"x":43,"y":30,"dx":1,"dy":1},{"id":16,"x":23,"y":31,"dx":-2,"dy":2},{"id":16,"x":24,"y":31,"dx":0,"dy":1},{"id":16,"x":25,"y":31,"dx":1,"dy":1},{"id":16,"x":17,"y":32,"dx":-3,"dy":0},{"id":16,"x":21,"y":32,"dx":4,"dy":0},{"id":16,"x":14,"y":33,"dx":2,"dy":2},{"id":16,"x":16,"y":33,"dx":2,"dy":1},{"id":16,"x":22,"y":33,"dx":2,"dy":0},{"id":16,"x":14,"y":34,"dx":-1,"dy":1},{"id":16,"x":22,"y":34,"dx":2,"dy":1},{"id":16,"x":12,"y":35,"dx":-2,"dy":1},{"id":16,"x":21,"y":35,"dx":1,"dy":1},{"id":16,"x":11,"y":36,"dx":4,"dy":-1},{"id":16,"x":17,"y":37,"dx":-2,"dy":-2},{"id":16,"x":23,"y":37,"dx":-2,"dy":2},{"id":16,"x":24,"y":37,"dx":-3,"dy":-1},{"id":16,"x":34,"y":37,"dx":0,"dy":-1},{"id":16,"x":0,"y":38,"dx":1,"dy":2},{"id":16,"x":27,"y":38,"dx":1,"dy":0},{"id":16,"x":29,"y":38,"dx":-2,"dy":1},{"id":16,"x":34,"y":38,"dx":-2,"dy":-1}],"props":[]},"old_siding":{"width":48,"height":40,"tiles":[[4,10],[0,1],[4,89],[0,1],[4,42],[3,1],[4,1],[3,1],[4,43],[3,1],[4,11],[0,1],[4,24],[29,1],[4,4],[0,1],[4,4],[3,1],[4,7],[3,1],[4,19],[29,1],[4,24],[3,1],[4,11],[28,1],[4,8],[19,3],[4,16],[0,1],[4,7],[3,1],[4,20],[19,2],[4,1],[19,1],[4,44],[19,2],[4,2],[19,1],[4,8],[29,1],[4,11],[3,1],[4,1],[0,1],[4,1],[3,1],[4,1],[3,1],[4,16],[19,2],[4,3],[19,1],[4,7],[0,1],[4,7],[3,1],[4,26],[19,2],[4,4],[19,1],[4,20],[3,1],[4,20],[19,2],[4,5],[19,1],[28,1],[4,22],[3,1],[4,9],[0,1],[7,1],[4,5],[19,2],[4,6],[19,1],[4,39],[19,2],[4,7],[19,1],[4,4],[28,1],[4,10],[3,1],[4,22],[19,2],[4,8],[19,1],[4,20],[3,1],[4,16],[19,2],[4,3],[0,1],[4,5],[19,1],[4,10],[3,2],[4,2],[3,1],[4,2],[3,1],[4,18],[19,2],[4,10],[19,1],[4,12],[3,1],[4,3],[3,1],[4,18],[19,2],[4,11],[19,1],[4,1],[0,1],[4,11],[3,1],[4,20],[19,2],[4,11],[0,1],[19,1],[4,7],[3,1],[4,8],[3,1],[4,8],[0,1],[4,7],[19,2],[4,13],[19,1],[4,7],[0,1],[4,3],[1,20],[4,1],[19,2],[1,3],[4,11],[19,1],[4,7],[3,1],[4,23],[19,2],[4,2],[1,1],[4,43],[19,2],[4,2],[1,1],[4,43],[19,2],[4,2],[1,1],[4,17],[0,1],[4,25],[19,2],[4,2],[1,1],[4,5],[7,1],[4,2],[29,1],[4,7],[0,1],[4,7],[29,1],[4,3],[28,1],[4,14],[19,2],[4,2],[1,1],[4,17],[3,1],[4,13],[9,1],[4,11],[19,2],[4,2],[1,1],[4,22],[3,2],[4,19],[19,2],[4,2],[1,1],[4,43],[19,2],[4,2],[1,1],[4,25],[3,2],[4,16],[19,2],[4,2],[1,1],[4,22],[0,1],[4,19],[0,1],[19,2],[4,2],[1,2],[4,15],[3,1],[4,26],[19,2],[4,3],[1,1],[20,1],[4,41],[19,2],[4,27],[3,1],[4,11],[0,1],[4,69],[28,1],[4,29],[0,1],[4,24],[3,1],[4,6],[3,1],[4,42],[0,1],[4,21],[29,1],[4,22],[3,1],[4,2],[3,1],[4,4],[3,1],[4,40],[3,1],[4,5],[3,1],[4,40],[3,1],[4,4]],"blocked":[2,3,6,7,24,25,26,27,9,10,18,21,28,29,30,31,32,33],"tall":{"3":{"height":1.5,"ground":0},"6":{"height":2.5,"ground":1},"7":{"height":1.75,"ground":4,"shadow":true},"9":{"height":1,"ground":8},"18":{"height":0.6,"ground":4},"21":{"height":2.5,"ground":8},"22":{"height":1.5,"ground":0},"23":{"height":1.5,"ground":0},"24":{"height":1.75,"ground":4,"shadow":true},"25":{"height":1.75,"ground":4,"shadow":true},"26":{"height":2.5,"ground":4,"shadow":true},"27":{"height":2.5,"ground":4,"shadow":true},"28":{"height":1.05,"ground":4},"29":{"height":1.25,"ground":4},"30":{"height":1.25,"ground":4},"31":{"height":0.75,"ground":4},"32":{"height":1,"ground":4},"33":{"height":1.45,"ground":4}},"decor":[{"id":16,"x":5,"y":0,"dx":-1,"dy":0},{"id":16,"x":13,"y":0,"dx":-4,"dy":0},{"id":16,"x":18,"y":0,"dx":-2,"dy":-1},{"id":16,"x":24,"y":0,"dx":-2,"dy":-2},{"id":16,"x":28,"y":0,"dx":0,"dy":-1},{"id":16,"x":30,"y":0,"dx":-2,"dy":2},{"id":16,"x":13,"y":1,"dx":3,"dy":1},{"id":16,"x":17,"y":1,"dx":0,"dy":-2},{"id":16,"x":37,"y":1,"dx":1,"dy":1},{"id":16,"x":39,"y":1,"dx":1,"dy":1},{"id":16,"x":43,"y":1,"dx":-1,"dy":2},{"id":16,"x":2,"y":2,"dx":-2,"dy":-1},{"id":16,"x":8,"y":2,"dx":-3,"dy":-1},{"id":16,"x":12,"y":2,"dx":-1,"dy":-1},{"id":16,"x":22,"y":2,"dx":-3,"dy":-1},{"id":16,"x":43,"y":2,"dx":0,"dy":0},{"id":16,"x":46,"y":2,"dx":0,"dy":0},{"id":16,"x":19,"y":3,"dx":-3,"dy":1},{"id":16,"x":24,"y":3,"dx":1,"dy":-1},{"id":16,"x":17,"y":4,"dx":1,"dy":0},{"id":16,"x":19,"y":4,"dx":-1,"dy":-1},{"id":16,"x":22,"y":4,"dx":1,"dy":1},{"id":16,"x":29,"y":4,"dx":2,"dy":0},{"id":16,"x":1,"y":5,"dx":-3,"dy":0},{"id":16,"x":3,"y":5,"dx":-3,"dy":1},{"id":16,"x":41,"y":5,"dx":-2,"dy":1},{"id":16,"x":44,"y":5,"dx":-1,"dy":-1},{"id":16,"x":27,"y":6,"dx":2,"dy":0},{"id":16,"x":31,"y":6,"dx":-2,"dy":1},{"id":16,"x":17,"y":7,"dx":-2,"dy":-2},{"id":16,"x":27,"y":7,"dx":0,"dy":1},{"id":16,"x":32,"y":7,"dx":-3,"dy":1},{"id":16,"x":45,"y":7,"dx":-2,"dy":-1},{"id":16,"x":5,"y":8,"dx":-3,"dy":0},{"id":16,"x":27,"y":8,"dx":-3,"dy":-2},{"id":16,"x":28,"y":8,"dx":3,"dy":2},{"id":16,"x":8,"y":9,"dx":1,"dy":0},{"id":16,"x":14,"y":9,"dx":-4,"dy":-1},{"id":16,"x":31,"y":9,"dx":-1,"dy":1},{"id":16,"x":38,"y":9,"dx":0,"dy":1},{"id":16,"x":0,"y":10,"dx":3,"dy":-2},{"id":16,"x":2,"y":10,"dx":-4,"dy":0},{"id":16,"x":4,"y":10,"dx":0,"dy":0},{"id":16,"x":10,"y":10,"dx":-3,"dy":-1},{"id":16,"x":21,"y":10,"dx":-4,"dy":-1},{"id":16,"x":40,"y":10,"dx":-3,"dy":-2},{"id":16,"x":42,"y":10,"dx":3,"dy":1},{"id":16,"x":9,"y":11,"dx":-2,"dy":2},{"id":16,"x":12,"y":11,"dx":0,"dy":1},{"id":16,"x":13,"y":11,"dx":3,"dy":2},{"id":16,"x":31,"y":11,"dx":-3,"dy":-1},{"id":16,"x":41,"y":11,"dx":-4,"dy":-2},{"id":16,"x":42,"y":11,"dx":3,"dy":-1},{"id":16,"x":43,"y":11,"dx":-1,"dy":1},{"id":16,"x":1,"y":12,"dx":1,"dy":2},{"id":16,"x":4,"y":12,"dx":0,"dy":2},{"id":16,"x":7,"y":12,"dx":-1,"dy":2},{"id":16,"x":25,"y":12,"dx":1,"dy":0},{"id":16,"x":13,"y":13,"dx":-2,"dy":1},{"id":16,"x":16,"y":13,"dx":1,"dy":1},{"id":16,"x":20,"y":13,"dx":2,"dy":2},{"id":16,"x":4,"y":14,"dx":-3,"dy":-1},{"id":16,"x":44,"y":14,"dx":0,"dy":-2},{"id":16,"x":3,"y":15,"dx":3,"dy":1},{"id":16,"x":19,"y":15,"dx":-1,"dy":0},{"id":16,"x":20,"y":15,"dx":0,"dy":-2},{"id":16,"x":21,"y":15,"dx":-1,"dy":0},{"id":16,"x":28,"y":15,"dx":-2,"dy":-2},{"id":16,"x":35,"y":15,"dx":-1,"dy":1},{"id":16,"x":38,"y":15,"dx":0,"dy":2},{"id":16,"x":46,"y":15,"dx":-4,"dy":-1},{"id":16,"x":2,"y":16,"dx":4,"dy":1},{"id":16,"x":11,"y":16,"dx":3,"dy":0},{"id":16,"x":21,"y":16,"dx":3,"dy":0},{"id":16,"x":7,"y":17,"dx":-1,"dy":2},{"id":16,"x":31,"y":17,"dx":1,"dy":1},{"id":16,"x":32,"y":17,"dx":-4,"dy":1},{"id":16,"x":42,"y":17,"dx":3,"dy":0},{"id":16,"x":27,"y":18,"dx":-3,"dy":-1},{"id":16,"x":39,"y":18,"dx":-1,"dy":0},{"id":16,"x":40,"y":18,"dx":1,"dy":2},{"id":16,"x":41,"y":18,"dx":-3,"dy":-2},{"id":16,"x":21,"y":19,"dx":2,"dy":1},{"id":16,"x":30,"y":19,"dx":1,"dy":-1},{"id":16,"x":0,"y":21,"dx":3,"dy":-1},{"id":16,"x":35,"y":21,"dx":-2,"dy":1},{"id":16,"x":37,"y":21,"dx":3,"dy":-2},{"id":16,"x":29,"y":22,"dx":-3,"dy":-2},{"id":16,"x":38,"y":22,"dx":4,"dy":1},{"id":16,"x":42,"y":22,"dx":-1,"dy":1},{"id":16,"x":14,"y":23,"dx":-1,"dy":-2},{"id":16,"x":33,"y":23,"dx":0,"dy":2},{"id":16,"x":36,"y":23,"dx":-2,"dy":-1},{"id":16,"x":42,"y":23,"dx":-1,"dy":-2},{"id":16,"x":10,"y":24,"dx":-1,"dy":-1},{"id":16,"x":21,"y":24,"dx":1,"dy":1},{"id":16,"x":30,"y":24,"dx":3,"dy":1},{"id":16,"x":31,"y":24,"dx":2,"dy":2},{"id":16,"x":34,"y":24,"dx":-2,"dy":-1},{"id":16,"x":46,"y":24,"dx":3,"dy":1},{"id":16,"x":1,"y":25,"dx":0,"dy":0},{"id":16,"x":4,"y":25,"dx":2,"dy":2},{"id":16,"x":20,"y":25,"dx":-1,"dy":-2},{"id":16,"x":33,"y":25,"dx":-2,"dy":2},{"id":16,"x":1,"y":26,"dx":3,"dy":2},{"id":16,"x":25,"y":26,"dx":-3,"dy":1},{"id":16,"x":0,"y":27,"dx":3,"dy":1},{"id":16,"x":3,"y":27,"dx":4,"dy":0},{"id":16,"x":6,"y":27,"dx":3,"dy":0},{"id":16,"x":11,"y":27,"dx":-2,"dy":-1},{"id":16,"x":14,"y":27,"dx":2,"dy":-2},{"id":16,"x":20,"y":27,"dx":-4,"dy":-2},{"id":16,"x":25,"y":27,"dx":3,"dy":-2},{"id":16,"x":29,"y":27,"dx":1,"dy":0},{"id":16,"x":36,"y":27,"dx":-1,"dy":-2},{"id":16,"x":39,"y":27,"dx":-4,"dy":-1},{"id":16,"x":46,"y":27,"dx":-3,"dy":-1},{"id":16,"x":14,"y":28,"dx":-4,"dy":2},{"id":16,"x":29,"y":28,"dx":-1,"dy":1},{"id":16,"x":38,"y":28,"dx":0,"dy":2},{"id":16,"x":40,"y":28,"dx":0,"dy":1},{"id":16,"x":42,"y":29,"dx":0,"dy":-1},{"id":17,"x":1,"y":30,"dx":1,"dy":1},{"id":16,"x":4,"y":30,"dx":2,"dy":1},{"id":16,"x":7,"y":30,"dx":-1,"dy":1},{"id":15,"x":21,"y":30,"dx":0,"dy":1},{"id":16,"x":30,"y":30,"dx":-2,"dy":-1},{"id":16,"x":41,"y":30,"dx":-1,"dy":0},{"id":16,"x":8,"y":31,"dx":-1,"dy":1},{"id":16,"x":15,"y":31,"dx":-2,"dy":0},{"id":16,"x":36,"y":31,"dx":4,"dy":-1},{"id":16,"x":12,"y":32,"dx":2,"dy":2},{"id":16,"x":15,"y":32,"dx":3,"dy":-1},{"id":16,"x":17,"y":32,"dx":-3,"dy":-1},{"id":16,"x":24,"y":32,"dx":2,"dy":1},{"id":16,"x":32,"y":32,"dx":3,"dy":0},{"id":16,"x":36,"y":32,"dx":0,"dy":-2},{"id":16,"x":43,"y":32,"dx":2,"dy":1},{"id":16,"x":4,"y":33,"dx":-1,"dy":1},{"id":16,"x":16,"y":33,"dx":0,"dy":0},{"id":16,"x":26,"y":33,"dx":2,"dy":1},{"id":16,"x":43,"y":33,"dx":4,"dy":0},{"id":16,"x":17,"y":34,"dx":-2,"dy":-2},{"id":16,"x":22,"y":34,"dx":-4,"dy":1},{"id":16,"x":26,"y":34,"dx":2,"dy":1},{"id":16,"x":35,"y":34,"dx":2,"dy":2},{"id":16,"x":2,"y":35,"dx":3,"dy":2},{"id":16,"x":26,"y":35,"dx":3,"dy":1},{"id":16,"x":28,"y":35,"dx":-1,"dy":1},{"id":16,"x":43,"y":35,"dx":-3,"dy":1},{"id":16,"x":10,"y":36,"dx":0,"dy":1},{"id":16,"x":11,"y":36,"dx":2,"dy":1},{"id":16,"x":27,"y":36,"dx":-1,"dy":1},{"id":16,"x":28,"y":36,"dx":-4,"dy":2},{"id":16,"x":31,"y":36,"dx":-1,"dy":1},{"id":16,"x":33,"y":36,"dx":3,"dy":-2},{"id":16,"x":42,"y":36,"dx":-1,"dy":-1},{"id":16,"x":10,"y":37,"dx":-4,"dy":-1},{"id":16,"x":18,"y":37,"dx":2,"dy":1},{"id":16,"x":31,"y":37,"dx":-4,"dy":-1},{"id":16,"x":8,"y":38,"dx":3,"dy":1},{"id":16,"x":11,"y":38,"dx":-1,"dy":1},{"id":16,"x":14,"y":38,"dx":-2,"dy":0},{"id":16,"x":27,"y":38,"dx":-1,"dy":-1}],"props":[]},"machine_hall":{"width":26,"height":22,"tiles":[[9,27],[8,11],[21,2],[8,11],[9,2],[8,24],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,12],[19,1],[8,11],[9,2],[8,24],[9,14],[8,1],[9,12]],"blocked":[2,3,6,7,24,25,26,27,9,10,18,21,28,29,30,31,32,33],"tall":{"3":{"height":1.5,"ground":0},"6":{"height":2.5,"ground":1},"7":{"height":1.75,"ground":4,"shadow":true},"9":{"height":1,"ground":8},"18":{"height":0.6,"ground":4},"21":{"height":2.5,"ground":8},"22":{"height":1.5,"ground":0},"23":{"height":1.5,"ground":0},"24":{"height":1.75,"ground":4,"shadow":true},"25":{"height":1.75,"ground":4,"shadow":true},"26":{"height":2.5,"ground":4,"shadow":true},"27":{"height":2.5,"ground":4,"shadow":true},"28":{"height":1.05,"ground":4},"29":{"height":1.25,"ground":4},"30":{"height":1.25,"ground":4},"31":{"height":0.75,"ground":4},"32":{"height":1,"ground":4},"33":{"height":1.45,"ground":4}},"decor":[],"props":[]},"house_elder":{"width":11,"height":9,"tiles":[[9,12],[8,9],[9,2],[8,9],[9,2],[8,9],[9,2],[8,9],[9,2],[8,9],[9,2],[8,9],[9,2],[8,9],[9,6],[8,1],[9,5]],"blocked":[2,3,6,7,24,25,26,27,9,10,18,21,28,29,30,31,32,33],"tall":{"3":{"height":1.5,"ground":0},"6":{"height":2.5,"ground":1},"7":{"height":1.75,"ground":4,"shadow":true},"9":{"height":1,"ground":8},"18":{"height":0.6,"ground":4},"21":{"height":2.5,"ground":8},"22":{"height":1.5,"ground":0},"23":{"height":1.5,"ground":0},"24":{"height":1.75,"ground":4,"shadow":true},"25":{"height":1.75,"ground":4,"shadow":true},"26":{"height":2.5,"ground":4,"shadow":true},"27":{"height":2.5,"ground":4,"shadow":true},"28":{"height":1.05,"ground":4},"29":{"height":1.25,"ground":4},"30":{"height":1.25,"ground":4},"31":{"height":0.75,"ground":4},"32":{"height":1,"ground":4},"33":{"height":1.45,"ground":4}},"decor":[],"props":[]},"shop":{"width":11,"height":9,"tiles":[[9,12],[8,9],[9,2],[8,9],[9,2],[8,9],[9,2],[8,9],[9,2],[8,9],[9,2],[8,9],[9,2],[8,9],[9,6],[8,1],[9,5]],"blocked":[2,3,6,7,24,25,26,27,9,10,18,21,28,29,30,31,32,33],"tall":{"3":{"height":1.5,"ground":0},"6":{"height":2.5,"ground":1},"7":{"height":1.75,"ground":4,"shadow":true},"9":{"height":1,"ground":8},"18":{"height":0.6,"ground":4},"21":{"height":2.5,"ground":8},"22":{"height":1.5,"ground":0},"23":{"height":1.5,"ground":0},"24":{"height":1.75,"ground":4,"shadow":true},"25":{"height":1.75,"ground":4,"shadow":true},"26":{"height":2.5,"ground":4,"shadow":true},"27":{"height":2.5,"ground":4,"shadow":true},"28":{"height":1.05,"ground":4},"29":{"height":1.25,"ground":4},"30":{"height":1.25,"ground":4},"31":{"height":0.75,"ground":4},"32":{"height":1,"ground":4},"33":{"height":1.45,"ground":4}},"decor":[],"props":[]}}
\ No newline at end of file
diff --git a/v2/apps/game/src/main.ts b/v2/apps/game/src/main.ts
new file mode 100644
index 0000000..a1fcf5c
--- /dev/null
+++ b/v2/apps/game/src/main.ts
@@ -0,0 +1,344 @@
+/**
+ * «Пепельные луга» на v2 (трек 15 «карты и области»): мир областей из
+ * раскладки v1 (maps.json → buildAreaWorld), герой-манекен, переходы
+ * step/click с фейдом и стек возврата, агентный мост для гейтов.
+ * Позиции v1 (спавны/переходы, в тайлах) конвертируются на границе:
+ * tileCenter (tile*UNIT + 1.5). Референс — демка v1.
+ */
+import {
+ VoxelWorld, VoxelRenderer, meshChunk, ISO_YAW,
+ mannequin, walkClip, Character, GameLoop, screenMoveToWorld,
+} from '@rpg/engine';
+import * as THREE from 'three';
+import { AREA_LAYOUTS, UNIT, GROUND_Y, buildAreaWorld, tileCenter } from './areas';
+import { areaOf, type AreaDef, type TransitionDef, type Tile } from './data/areas';
+
+// Слоты палитры сцены: арт-библия v1 (1..10) + тайлы областей (11..17)
+const PALETTE = {
+ colors: {
+ 1: '#4a5340', // G1 — базовый луг
+ 2: '#343b2c', // G0 — тень травы (нижний слой / проплешина)
+ 3: '#626c4c', // G2 — трава, свет (пятна)
+ 4: '#5c3b2e', // R1 — стволы/крыша тень
+ 5: '#7d8662', // G2H — крона, свет
+ 6: '#5b5b66', // P4 — камень, стены
+ 7: '#94949e', // P6 — свет камня
+ 8: '#82503a', // R2 — светлая ржавчина (крыша)
+ 9: '#d99a32', // B1 — цветок (жизнь!)
+ 10: '#44454e', // M1 — металл
+ 11: '#2f2f38', // P2 — пепел
+ 12: '#3e3e48', // P3 — пепел, светлые пятна
+ 13: '#3b5e6b', // W1 — вода
+ 14: '#568590', // W2 — вода, блики
+ 15: '#5a4a3c', // T0 — пол интерьеров
+ 16: '#77634f', // T1 — тропа
+ 17: '#61626c', // M2 — рельсы
+ },
+};
+
+// ---- Рендер: мир области (пересоздаётся на переходе), меш на чанк ----
+const canvas = document.getElementById('scene') as HTMLCanvasElement;
+const renderer = new VoxelRenderer(canvas, { target: [40, 2, 60], viewSize: 17 });
+
+let world = new VoxelWorld(16);
+const chunkMeshes = new Map();
+
+function remeshDirty(): void {
+ for (const key of world.takeDirty()) {
+ const [cx, cz] = key.split(',').map(Number);
+ const old = chunkMeshes.get(key);
+ if (old) {
+ renderer.scene.remove(old);
+ old.geometry.dispose();
+ }
+ const mesh = meshChunk(world, cx, cz, PALETTE);
+ if (mesh.triangleCount > 0) chunkMeshes.set(key, renderer.addMesh(mesh));
+ else chunkMeshes.delete(key);
+ }
+}
+
+/** Снять меши старой области (при переходе мир пересоздаётся целиком). */
+function disposeMeshes(): void {
+ for (const m of chunkMeshes.values()) {
+ renderer.scene.remove(m);
+ m.geometry.dispose();
+ }
+ chunkMeshes.clear();
+}
+
+// ---- Герой: манекен + walk-цикл (как в демке) ----
+const { model: MAN, rig: MAN_RIG } = mannequin();
+const WALK = walkClip({ duration: 0.8, legSwing: 0.55, armSwing: 0.4, bob: 0.25, sway: 0.15 });
+const BODY_SIZE: [number, number, number] = [3, 10, 3];
+
+const hero = new Character(MAN_RIG, MAN, WALK, [40, 60], {
+ size: BODY_SIZE,
+ speed: 4,
+ strideLength: 3.2,
+});
+const heroCloud = renderer.addVoxelCloud(hero.visual());
+
+// ---- Области: текущая, стек возврата, смена области ----
+let currentArea: AreaDef = areaOf('meadows');
+let layout = AREA_LAYOUTS.meadows!;
+const returnStack: { area: string; entry: Tile }[] = [];
+
+/** Смена области: новый мир + мешы, герой на входном тайле. */
+function setArea(id: string, entry: Tile): void {
+ currentArea = areaOf(id);
+ layout = AREA_LAYOUTS[currentArea.id]!;
+ disposeMeshes();
+ world = buildAreaWorld(layout);
+ remeshDirty();
+ const c = tileCenter(entry.x, entry.y);
+ hero.body.pos = [c.x, GROUND_Y, c.z];
+ hero.body.vel = [0, 0, 0];
+ heroCloud.update(hero.visual());
+ prevTile = { ...entry }; // входной тайл — не «только что вошёл» (без триггера)
+ nearClick = null; // подсказка старой области не должна пережить переход
+ showHint('');
+ renderer.setTarget([Math.round(c.x), GROUND_Y, Math.round(c.z)]);
+}
+
+// ---- Фейд и переходы: out → смена области → in (полный = 2×duration) ----
+const fadeEl = document.getElementById('fade') as HTMLDivElement;
+const hintEl = document.getElementById('hint') as HTMLDivElement;
+
+interface FadeState {
+ active: boolean;
+ t: number;
+ dur: number;
+ phase: 'out' | 'in';
+ /** Смена области — в середине перехода (за чёрным кадром). */
+ apply: (() => void) | null;
+}
+const fade: FadeState = { active: false, t: 0, dur: 0.4, phase: 'out', apply: null };
+
+let hintText = '';
+/** Ближайший click-переход (для «E» и подсказки). */
+let nearClick: TransitionDef | null = null;
+/** Тайл героя с прошлого тика (origin возврата + «только что вошёл»). */
+let prevTile: Tile | null = null;
+
+function showHint(text: string): void {
+ hintText = text;
+ hintEl.hidden = text === '';
+ hintEl.textContent = text;
+}
+
+/** Начать переход (фейд out, середина — смена области). */
+function startTransition(tr: TransitionDef, origin: Tile): void {
+ if (fade.active) return; // параллельные переходы глотаются (как в v1)
+ fade.active = true;
+ fade.t = 0;
+ fade.dur = tr.duration ?? 0.4;
+ fade.phase = 'out';
+ const fromArea = currentArea.id;
+ fade.apply = () => {
+ if (tr.target === 'return') {
+ const back = returnStack.pop();
+ if (back) setArea(back.area, back.entry);
+ } else {
+ // origin — откуда вошли (шаг на триггер = прошлый тайл, дверь = стояли)
+ returnStack.push({ area: fromArea, entry: origin });
+ setArea(tr.target.area, tr.target.entry);
+ }
+ };
+ showHint('');
+}
+
+// ---- Ввод: клавиатура (экранные оси) + виртуальный стик агента ----
+const heldKeys = new Set();
+const KEYMAP: Record = {
+ KeyW: [0, -1], KeyS: [0, 1], KeyA: [-1, 0], KeyD: [1, 0],
+ ArrowUp: [0, -1], ArrowDown: [0, 1], ArrowLeft: [-1, 0], ArrowRight: [1, 0],
+};
+let agentInput: readonly [number, number] | null = null;
+let followHero = true;
+
+window.addEventListener('keydown', (e) => {
+ if (KEYMAP[e.code]) {
+ heldKeys.add(e.code);
+ e.preventDefault();
+ }
+ if (e.code === 'KeyE' && nearClick) {
+ e.preventDefault();
+ startTransition(nearClick, heroTile());
+ }
+});
+window.addEventListener('keyup', (e) => {
+ heldKeys.delete(e.code);
+});
+
+function keyboardMove(): [number, number] {
+ let x = 0, y = 0;
+ for (const code of heldKeys) {
+ const m = KEYMAP[code]!;
+ x += m[0];
+ y += m[1];
+ }
+ return [x, y];
+}
+
+/** Тайл героя (в тайлах v1) — триггеры и снапшоты моста. */
+function heroTile(): Tile {
+ const b = hero.body.pos;
+ return { x: Math.floor(b[0] / UNIT), y: Math.floor(b[2] / UNIT) };
+}
+
+/** Один шаг логики: фейд → ввод → герой → камера → переходы → подсказка. */
+function tick(dt: number): void {
+ if (fade.active) {
+ fade.t += dt;
+ if (fade.phase === 'out') {
+ fadeEl.style.opacity = String(Math.min(1, fade.t / fade.dur));
+ if (fade.t >= fade.dur) {
+ fade.apply?.();
+ fade.phase = 'in';
+ fade.t = 0;
+ }
+ } else {
+ fadeEl.style.opacity = String(Math.max(0, 1 - fade.t / fade.dur));
+ if (fade.t >= fade.dur) {
+ fade.active = false;
+ fadeEl.style.opacity = '0';
+ }
+ }
+ return; // во время перехода мир замер
+ }
+ const move = agentInput ?? keyboardMove();
+ hero.setMove(screenMoveToWorld(move, ISO_YAW));
+ hero.step(world, dt);
+ heroCloud.update(hero.visual());
+ if (followHero) {
+ const b = hero.body.pos;
+ renderer.setTarget([Math.round(b[0]), GROUND_Y, Math.round(b[2])]);
+ }
+ // step-переход: только что вошёл на тайл-триггер
+ const tile = heroTile();
+ const justEntered = prevTile !== null && (tile.x !== prevTile.x || tile.y !== prevTile.y);
+ if (justEntered) {
+ const tr = currentArea.transitions.find(
+ (t) => (t.trigger ?? 'step') === 'step' && t.tile.x === tile.x && t.tile.y === tile.y,
+ );
+ if (tr) {
+ startTransition(tr, prevTile!);
+ return;
+ }
+ }
+ prevTile = tile;
+ // click-переход рядом: подсказка «E — …»
+ nearClick =
+ currentArea.transitions.find(
+ (t) =>
+ t.trigger === 'click' &&
+ Math.max(Math.abs(t.tile.x - tile.x), Math.abs(t.tile.y - tile.y)) <= 1,
+ ) ?? null;
+ showHint(nearClick ? `E — ${nearClick.label ?? 'перейти'}` : '');
+}
+
+// ---- Цикл: фиксированный шаг 60 Гц, ручной режим для гейтов ----
+const loop = new GameLoop(60, {
+ update: (dt) => tick(dt),
+ render: () => {
+ remeshDirty();
+ renderer.render();
+ },
+});
+setArea(currentArea.id, currentArea.spawn); // старт — спавн лугов
+loop.start();
+
+// ---- Агентный мост (DEV): гейты областей ----
+const agent = {
+ /** Ручной/автоматический режим цикла. */
+ setManual(on: boolean): void {
+ loop.setManual(on);
+ },
+ /** Ручные шаги логики + рендер (детерминизм гейта). */
+ stepTicks(n: number): number {
+ return loop.stepTick(n, true);
+ },
+ /** Снапшот героя для гейта. */
+ heroInfo() {
+ const b = hero.body.pos;
+ return {
+ area: currentArea.id,
+ pos: [...b], vel: [...hero.body.vel], grounded: hero.body.grounded,
+ phase: hero.phase, yaw: hero.yaw,
+ tile: heroTile(),
+ camTarget: renderer.currentTarget(),
+ };
+ },
+ /** Телепорт героя (гейт: слежение камеры, непроходимость). */
+ heroTeleport(x: number, z: number): void {
+ hero.body.pos = [x, GROUND_Y, z];
+ hero.body.vel = [0, 0, 0];
+ heroCloud.update(hero.visual());
+ prevTile = heroTile(); // телепорт — не «вход на тайл»
+ renderer.setTarget([Math.round(x), GROUND_Y, Math.round(z)]);
+ },
+ /** Виртуальный стик агента (экранные оси −1..1); null — клавиатура. */
+ setInput(x: number | null, z: number | null): void {
+ agentInput = x === null || z === null ? null : [x, z];
+ },
+ /** Слежение камеры за героем (off — кадр гейта фиксируется). */
+ setFollow(on: boolean): void {
+ followHero = on;
+ if (!on) {
+ const c = tileCenter(currentArea.spawn.x, currentArea.spawn.y);
+ renderer.setTarget([Math.round(c.x), GROUND_Y, Math.round(c.z)]);
+ }
+ },
+ /** Яркость солнца (утро/день/вечер — как в демке). */
+ setSun(k: number): void {
+ renderer.setSun(k);
+ },
+ /** Снапшот области: раскладка, переходы, фейд, стек возврата, меши. */
+ areaInfo() {
+ return {
+ id: currentArea.id,
+ name: currentArea.name,
+ width: layout.width,
+ height: layout.height,
+ spawn: currentArea.spawn,
+ heroTile: heroTile(),
+ transitions: currentArea.transitions.map((t) => ({
+ tile: t.tile,
+ label: t.label ?? null,
+ trigger: t.trigger ?? 'step',
+ target: t.target === 'return' ? 'return' : t.target.area,
+ duration: t.duration ?? 0.4,
+ })),
+ fade: { active: fade.active, phase: fade.phase },
+ returns: returnStack.length,
+ chunks: world.chunkCount,
+ meshes: chunkMeshes.size,
+ };
+ },
+ /** Прямой переход области (гейт: скриншоты и переходы без ходьбы). */
+ gotoArea(id: string, entry?: Tile): void {
+ returnStack.length = 0;
+ fade.active = false;
+ fadeEl.style.opacity = '0';
+ const def = areaOf(id);
+ setArea(id, entry ?? def.spawn);
+ },
+ /** Сплошная ли колонка НАД землёй (вода/объект) — гейт blocked. */
+ solidAt(x: number, z: number): boolean {
+ const fx = Math.floor(x), fz = Math.floor(z);
+ for (let y = GROUND_Y; y <= GROUND_Y + 1; y++) {
+ if (world.get(fx, y, fz) !== 0) return true;
+ }
+ return false;
+ },
+ /** Нажать «E» агентом: активировать click-переход. */
+ interact(): void {
+ if (nearClick && !fade.active) startTransition(nearClick, heroTile());
+ },
+ /** Снапшот взаимодействия: ближайшая дверь и подсказка. */
+ interactInfo() {
+ return { label: nearClick?.label ?? null, hint: hintText };
+ },
+};
+(window as unknown as Record).__agent = agent;
+(window as unknown as Record).__beacon = true;
\ No newline at end of file
diff --git a/v2/apps/game/tools/checks/areas.mjs b/v2/apps/game/tools/checks/areas.mjs
new file mode 100644
index 0000000..19687c8
--- /dev/null
+++ b/v2/apps/game/tools/checks/areas.mjs
@@ -0,0 +1,252 @@
+/**
+ * Скриншот-гейт областей v2 (трек 15 «карты и области»): конвертация v1 →
+ * мир детерминирована (пересбор той же области — тот же кадр), спавн лугов,
+ * blocked-тайлы (вода/дерево сплошные, тропа нет; тело зажимается гранью),
+ * step-переход входом на тайл-триггер (фейд out→swap→in), возврат 'return'
+ * по стеку, click-переход через «E» с подсказкой. Скриншоты: /tmp/v2_areas_*.png.
+ *
+ * Запуск: node apps/game/tools/checks/areas.mjs [--port 5314]
+ */
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { launchBrowser, startDevServer, openGame, parseArgs } from '@rpg/engine/tools/agent-lib.mjs';
+import { decodePng } from '@rpg/engine/tools/png.mjs';
+
+const args = parseArgs(process.argv.slice(2));
+const port = Number(args.port ?? 5314);
+
+const server = startDevServer({ port, cwd: fileURLToPath(new URL('../../', import.meta.url)) });
+const report = { name: 'areas', ok: false, checks: [] };
+const check = (title, ok, info = null) => {
+ report.checks.push({ title, ok, info });
+ console.error(`${ok ? '✓' : '✗'} ${title}${info ? ' — ' + JSON.stringify(info) : ''}`);
+};
+
+/** Тиков на полный переход (out+in) с запасом. */
+const PASS = 70;
+/** Тиков на проход через один тайл с запасом (4 вокс/с, тайл = 3 вокс). */
+const CROSS = 120;
+
+// ---- Раскладка v1 (maps.json — чистые данные, движок не импортируется) ----
+const maps = JSON.parse(readFileSync(new URL('../../src/data/maps.json', import.meta.url), 'utf8'));
+
+/** RLE → плоский массив тайлов (как в areas.ts). */
+function tilesFlat(id) {
+ const m = maps[id];
+ const out = [];
+ for (const [tid, run] of m.tiles) for (let k = 0; k < run; k++) out.push(tid);
+ return out;
+}
+
+/** Первый тайл области с данным id → {x, y} (в тайлах v1). */
+function findTile(id, tileId) {
+ const m = maps[id];
+ const flat = tilesFlat(id);
+ for (let i = 0; i < flat.length; i++) {
+ if (flat[i] === tileId) return { x: i % m.width, y: Math.floor(i / m.width) };
+ }
+ throw new Error(`${id}: тайл ${tileId} не найден`);
+}
+
+/** Воксельный центр тайла (как tileCenter в игре: tile*UNIT + 1.5). */
+const center = (t) => ({ x: t.x * 3 + 1.5, z: t.y * 3 + 1.5 });
+
+function countDiff(a, b) {
+ let n = 0;
+ for (let i = 0; i < a.length; i += 4) {
+ if (Math.abs(a[i] - b[i]) > 8 || Math.abs(a[i + 1] - b[i + 1]) > 8 || Math.abs(a[i + 2] - b[i + 2]) > 8) n++;
+ }
+ return n;
+}
+function isBlack(rgba) {
+ let bright = 0, n = 0;
+ for (let i = 0; i < rgba.length; i += 4) {
+ n++;
+ if (rgba[i] + rgba[i + 1] + rgba[i + 2] > 30) bright++;
+ }
+ return bright / n < 0.01;
+}
+/** Число различимых цветов (квантование 5 бит/канал). */
+function colorCount(rgba) {
+ const seen = new Set();
+ for (let i = 0; i < rgba.length; i += 4) {
+ seen.add((rgba[i] >> 3) + (rgba[i + 1] >> 3) * 32 + (rgba[i + 2] >> 3) * 1024);
+ }
+ return seen.size;
+}
+
+try {
+ await server.waitReady;
+ const browser = await launchBrowser();
+ try {
+ const client = await openGame(browser, { url: `http://localhost:${port}/` });
+ const errs = client.errors();
+ check('нет pageerror при загрузке', errs.length === 0, errs.slice(0, 3));
+
+ await client.call('setManual', true);
+ await client.call('stepTicks', 10);
+
+ // Старт: луга, герой на спавне, меши собраны
+ let ai = await client.call('areaInfo');
+ check('старт: луга, герой на спавне (16,28), меши собраны',
+ ai.id === 'meadows' && ai.heroTile.x === 16 && ai.heroTile.y === 28 && ai.meshes > 0,
+ { id: ai.id, tile: ai.heroTile, meshes: ai.meshes });
+
+ // Детерминизм сборки: пересбор той же области — тот же кадр
+ const spawnC = center({ x: 16, y: 28 });
+ await client.call('setFollow', false);
+ const shotA = await client.screenshot('/tmp/v2_areas_meadows.png');
+ await client.call('gotoArea', 'meadows');
+ await client.call('stepTicks', 5);
+ const shotA2 = await client.screenshot('/tmp/v2_areas_meadows2.png');
+ const detDiff = countDiff(decodePng(readFileSync(shotA)).rgba, decodePng(readFileSync(shotA2)).rgba);
+ check('пересборка области детерминирована (кадры равны)', detDiff === 0, { detDiff });
+
+ // Чистая мировая ось — комбинированный стик: при yaw π/4 экранное
+ // (1,1) = +x мира (hero-гейт демки), (1,-1) = −z, (-1,1) = +z
+ await client.call('heroTeleport', spawnC.x, spawnC.z);
+ await client.call('setInput', 1, 1);
+ await client.call('stepTicks', 10);
+ const hiProbe = await client.call('heroInfo');
+ await client.call('setInput', null, null);
+ check('стик (1,1) — чистый +x мира (без дрейфа по z)',
+ hiProbe.pos[0] - spawnC.x > 0.3 && Math.abs(hiProbe.pos[2] - spawnC.z) < 0.2,
+ { dx: Math.round((hiProbe.pos[0] - spawnC.x) * 100) / 100,
+ dz: Math.round((hiProbe.pos[2] - spawnC.z) * 100) / 100 });
+
+ // blocked: дерево и вода сплошные (хоть одна под-колонка тайла),
+ // тропа — ни одной сплошной под-колонки
+ const solidTile = async (c) => {
+ let n = 0;
+ for (const dx of [-1, 0, 1]) for (const dz of [-1, 0, 1]) {
+ if (await client.call('solidAt', c.x + dx, c.z + dz)) n++;
+ }
+ return n;
+ };
+ const tree = center(findTile('meadows', 3));
+ const water = center(findTile('meadows', 2));
+ const path = center(findTile('meadows', 1));
+ const sTree = await solidTile(tree);
+ const sWater = await solidTile(water);
+ const sPath = await solidTile(path);
+ check('blocked: дерево/вода сплошные, тропа проходима', sTree > 0 && sWater === 9 && sPath === 0,
+ { tree: sTree, water: sWater, path: sPath });
+
+ // Тело зажимается гранью воды (вода — сплошной слой на уровне ног)
+ await client.call('heroTeleport', water.x - 3.6, water.z);
+ await client.call('setInput', 1, 1);
+ await client.call('stepTicks', CROSS);
+ await client.call('setInput', null, null);
+ const hiStop = await client.call('heroInfo');
+ const gap = water.x - hiStop.pos[0]; // центр тела до центра воды
+ check('тело героя останавливается у грани воды', gap > 2.6 && gap < 4.5,
+ { gap: Math.round(gap * 10) / 10 });
+
+ // Скриншоты областей: различаются и не пустые
+ const areas = ['meadows', 'ponds', 'zvenets', 'rust_grove', 'old_siding', 'machine_hall', 'house_elder'];
+ const shots = {};
+ let minDiff = Infinity;
+ let allColorful = true;
+ for (const id of areas) {
+ await client.call('gotoArea', id);
+ await client.call('stepTicks', 5);
+ const p = `/tmp/v2_areas_${id}.png`;
+ const img = decodePng(readFileSync(await client.screenshot(p)));
+ shots[id] = img.rgba;
+ if (isBlack(img.rgba) || colorCount(img.rgba) < 8) allColorful = false;
+ }
+ for (let i = 1; i < areas.length; i++) {
+ minDiff = Math.min(minDiff, countDiff(shots[areas[i - 1]], shots[areas[i]]));
+ }
+ check('скриншоты 7 областей: не пустые, различаются', allColorful && minDiff > 5000,
+ { minDiff, allColorful });
+
+ // step-переход: телепорт у триггера (54,28) → идти на него → Звенец
+ await client.call('gotoArea', 'meadows');
+ await client.call('stepTicks', 5);
+ const trig = center({ x: 54, y: 28 });
+ await client.call('heroTeleport', trig.x - 3, trig.z);
+ await client.call('setInput', 1, 1);
+ await client.call('stepTicks', CROSS);
+ await client.call('setInput', null, null);
+ await client.call('stepTicks', PASS);
+ ai = await client.call('areaInfo');
+ check('step-переход луга→Звенец (фейд, возврат запомнен)',
+ ai.id === 'zvenets' && ai.returns === 1 && !ai.fade.active, { id: ai.id, returns: ai.returns });
+
+ // Вторая пара step-переходов: луга (2,2) → пруды → обратно в луга
+ await client.call('gotoArea', 'meadows');
+ await client.call('stepTicks', 5);
+ const pTrig = center({ x: 2, y: 2 });
+ await client.call('heroTeleport', pTrig.x + 3, pTrig.z);
+ await client.call('setInput', -1, -1);
+ await client.call('stepTicks', CROSS);
+ await client.call('setInput', null, null);
+ await client.call('stepTicks', PASS);
+ ai = await client.call('areaInfo');
+ check('второй step-переход: луга→пруды', ai.id === 'ponds', ai.id);
+ const pBack = center({ x: 3, y: 2 });
+ await client.call('heroTeleport', pBack.x, pBack.z);
+ await client.call('setInput', -1, -1);
+ await client.call('stepTicks', CROSS);
+ await client.call('setInput', null, null);
+ await client.call('stepTicks', PASS);
+ ai = await client.call('areaInfo');
+ check('обратный переход пруды→луга', ai.id === 'meadows', ai.id);
+
+ // return-переход: люк подземелья → зал → люк обратно
+ await client.call('gotoArea', 'old_siding');
+ await client.call('stepTicks', 5);
+ const hatch = center({ x: 28, y: 31 });
+ await client.call('heroTeleport', hatch.x - 3, hatch.z);
+ await client.call('setInput', 1, 1);
+ await client.call('stepTicks', CROSS);
+ await client.call('setInput', null, null);
+ await client.call('stepTicks', PASS);
+ ai = await client.call('areaInfo');
+ const wentDown = ai.id === 'machine_hall' && ai.returns === 1;
+ check('переход в подземелье через люк (step, 0.6 с)', wentDown, { id: ai.id, returns: ai.returns });
+ if (wentDown) {
+ // запад/восток триггера (13,21) — стены; идём с севера (13,20), +z
+ const hall = center({ x: 13, y: 20 });
+ await client.call('heroTeleport', hall.x, hall.z);
+ await client.call('setInput', -1, 1);
+ await client.call('stepTicks', CROSS);
+ await client.call('setInput', null, null);
+ await client.call('stepTicks', PASS);
+ ai = await client.call('areaInfo');
+ check('возврат из подземелья по стеку (на тайле входа)',
+ ai.id === 'old_siding' && ai.returns === 0, { id: ai.id, returns: ai.returns });
+ }
+
+ // click-переход: дверь дома Ирвина в Звенце — подсказка и «E»
+ await client.call('gotoArea', 'zvenets');
+ await client.call('stepTicks', 5);
+ const door = center({ x: 13, y: 12 });
+ await client.call('heroTeleport', door.x + 3, door.z);
+ await client.call('stepTicks', 5);
+ let ii = await client.call('interactInfo');
+ check('подсказка у двери: «E — Дом Ирвина»', ii.label === 'Дом Ирвина' && ii.hint.includes('E'), ii);
+ await client.call('heroTeleport', door.x + 30, door.z); // ~10 тайлов в стороне
+ await client.call('stepTicks', 5);
+ ii = await client.call('interactInfo');
+ check('вдали от двери подсказки нет', ii.label === null && ii.hint === '', ii);
+ await client.call('heroTeleport', door.x + 3, door.z);
+ await client.call('stepTicks', 5);
+ await client.call('interact');
+ await client.call('stepTicks', PASS);
+ ai = await client.call('areaInfo');
+ check('click-переход «E» → дом Ирвина', ai.id === 'house_elder', ai.id);
+ const inside = await client.screenshot('/tmp/v2_areas_inside.png');
+ check('кадр интерьера рисуется (не пустой)', colorCount(decodePng(readFileSync(inside)).rgba) >= 8, {});
+ } finally {
+ await browser.close();
+ }
+} catch (e) {
+ report.error = String(e && e.stack ? e.stack : e);
+ console.error(report.error);
+} finally {
+ server.stop();
+}
+report.ok = report.checks.length > 0 && report.checks.every((c) => c.ok);
+console.log(JSON.stringify(report));
\ No newline at end of file
diff --git a/v2/apps/game/tools/convertMaps.mjs b/v2/apps/game/tools/convertMaps.mjs
new file mode 100644
index 0000000..8fe5b14
--- /dev/null
+++ b/v2/apps/game/tools/convertMaps.mjs
@@ -0,0 +1,60 @@
+/**
+ * Конвертация карт v1 (assets/maps/*.map — RLE) в раскладку областей v2:
+ * src/data/maps.json — по области {width, height, tiles (RLE), decor, props,
+ * blocked, tall}. Раскладка тайлов v1 переносится как есть (позиции NPC,
+ * переходов и квестов v1 в тайлах — сходятся с раскладкой), воксельная
+ * генерация делает из неё мир (src/areas.ts). Прогон: node tools/convertMaps.mjs
+ * (от корня v2/apps/game; путь к v1 — по умолчанию ../../v1/apps/game).
+ */
+import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import path from 'node:path';
+
+const ROOT = fileURLToPath(new URL('..', import.meta.url));
+const V1_MAPS = process.argv[2] ?? path.resolve(ROOT, '../../../v1/apps/game/assets/maps');
+const OUT = path.resolve(ROOT, 'src/data/maps.json');
+
+/** Области v1 (порядок = порядок загрузки в игре). */
+const AREAS = ['meadows', 'ponds', 'zvenets', 'rust_grove', 'old_siding', 'machine_hall', 'house_elder', 'shop'];
+
+/** RLE-пары → плоский массив (распаковка на месте — parseMap v1 не импортируется). */
+function rleDecode(pairs) {
+ const out = [];
+ for (const [id, run] of pairs) {
+ for (let k = 0; k < run; k++) out.push(id);
+ }
+ return out;
+}
+
+/** Плоский массив → RLE-пары (на случай raw-карты v1). */
+function rleEncode(flat) {
+ const out = [];
+ for (let i = 0; i < flat.length; i++) {
+ if (i > 0 && flat[i] === flat[i - 1]) out[out.length - 1][1]++;
+ else out.push([flat[i], 1]);
+ }
+ return out;
+}
+
+const maps = {};
+for (const id of AREAS) {
+ const raw = JSON.parse(readFileSync(path.join(V1_MAPS, `${id}.map`), 'utf8'));
+ if (raw.format !== 'rpg-map' || raw.version !== 1) throw new Error(`${id}: не карта rpg-map`);
+ maps[id] = {
+ width: raw.width,
+ height: raw.height,
+ // RLE как есть (сериализация маленькая); генератор распакует при сборке
+ tiles: raw.encoding === 'rle' ? raw.tiles : rleEncode(raw.tiles),
+ // blocked/tall/decor/props — по сути копия (высоты в юнитах уже)
+ blocked: raw.blocked,
+ tall: raw.tall ?? {},
+ decor: raw.decor ?? [],
+ props: raw.props ?? [],
+ };
+}
+
+mkdirSync(path.dirname(OUT), { recursive: true });
+writeFileSync(OUT, JSON.stringify(maps));
+const sizes = Object.entries(maps).map(([k, m]) => `${k} ${m.width}x${m.height}`);
+console.log('ok →', path.relative(ROOT, OUT));
+console.log(sizes.join(', '));
\ No newline at end of file
diff --git a/v2/apps/game/tsconfig.json b/v2/apps/game/tsconfig.json
new file mode 100644
index 0000000..9e6cc82
--- /dev/null
+++ b/v2/apps/game/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "types": ["vite/client"]
+ },
+ "include": ["src", "tools"]
+}
\ No newline at end of file
diff --git a/v2/apps/game/vite.config.ts b/v2/apps/game/vite.config.ts
new file mode 100644
index 0000000..63060fa
--- /dev/null
+++ b/v2/apps/game/vite.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite';
+import { fileURLToPath } from 'node:url';
+
+export default defineConfig({
+ resolve: {
+ alias: [
+ // только regex-алиас (грабля v1): строковый перехватывает и под-пути
+ { find: /^@rpg\/engine$/, replacement: fileURLToPath(new URL('../../packages/engine/src/index.ts', import.meta.url)) },
+ ],
+ },
+});
\ No newline at end of file
diff --git a/v2/package-lock.json b/v2/package-lock.json
index c2dae37..b95403f 100644
--- a/v2/package-lock.json
+++ b/v2/package-lock.json
@@ -531,6 +531,525 @@
}
}
},
+ "apps/game": {
+ "name": "@rpg/game",
+ "version": "0.0.1",
+ "dependencies": {
+ "@rpg/engine": "*",
+ "three": "^0.186.0"
+ },
+ "devDependencies": {
+ "vite": "^6.0.0"
+ }
+ },
+ "apps/game/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "apps/game/node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "apps/game/node_modules/vite": {
+ "version": "6.4.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
+ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@dimforge/rapier3d-compat": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
@@ -1433,6 +1952,10 @@
"resolved": "packages/engine",
"link": true
},
+ "node_modules/@rpg/game": {
+ "resolved": "apps/game",
+ "link": true
+ },
"node_modules/@tweenjs/tween.js": {
"version": "23.1.3",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
diff --git a/v2/package.json b/v2/package.json
index 01ef204..9063ffb 100644
--- a/v2/package.json
+++ b/v2/package.json
@@ -11,7 +11,7 @@
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
- "typecheck": "tsc --noEmit -p packages/engine && tsc --noEmit -p apps/demo",
+ "typecheck": "tsc --noEmit -p packages/engine && tsc --noEmit -p apps/demo && tsc --noEmit -p apps/game",
"check:fast": "npm run typecheck && npm test"
}
}
\ No newline at end of file