// genmaps.js — 生成《崩坏童话：蜜糖村》全部 10 张 Tiled 地图 (.tmx)
// 用法: /Applications/Tiled.app/Contents/MacOS/Tiled -e tools/tiled/genmaps.js
// 说明: -e 模式无编辑器，tiled.open/TileMap 绘制 API 不可用，故用 TextFile 直接写 .tmx XML。
// 导出: Tiled --export-map --embed-tilesets in.tmx out.json （必须 --embed-tilesets，JSON 才有 image 字段）
//
// 约定（与 CONTRACT.md §3 一致）：
//   - 固定三层：ground（瓦片层）/ obstacles（瓦片层，图层属性 collides=true）/ objects（点对象层）
//   - 瓦片 gid = CONTRACT §2 瓦片 id + 1；obstacles 中空格 = gid 0
//   - 点对象坐标 = 所在瓦片中心像素 (tx*16+8, ty*16+8)
//   - door 属性 target=目标地图 key, spawn=目标地图中的 spawn id（= "from_"+本图 key），双向成对

var OUT = '/Users/leidianyayi/Documents/GitHub/ModelTest20260714/Kimi-K3/public/assets/maps/';

// ---- 瓦片 gid 表（id+1）----
var G = {
  grass:1, flowerA:2, flowerB:3, deepGrass:4, dirt:5, stone:6, stoneAlt:7, brick:8,
  waterA:9, waterB:10, shoreN:11, shoreS:12, shoreW:13, shoreE:14,
  bedPink:15, bedYellow:16, berry:17, berryEmpty:18,
  treeTL:19, treeTR:20, treeBL:21, treeBR:22, bush:23, stump:24, rock:25, sign:26,
  fenceH:27, fenceV:28, lamp:29, ribbonR:30, ribbonY:31, ribbonB:32,
  creamWall:33, creamWallWin:34, woodWall:35, roofOL:36, roofOM:37, roofOR:38,
  doorT:39, doorTop:40, window:41, chimney:42, wellTL:43, wellTR:44, wellBL:45, wellBR:46,
  mailbox:47, board:48, floorA:49, floorB:50, wallIn:51, wallInWin:52,
  bedTL:53, bedTR:54, bedBL:55, bedBR:56, counterL:57, counterR:58, oven:59,
  shelfE:60, shelfF:61, table:62, chair:63, rug:64, calendar:65, recipe:66, fileP:67,
  picTL:68, picTR:69, picBL:70, picBR:71, benchL:72, benchR:73, stool:74, cabinet:75,
  fridge:76, sink:77, plant:78, barrel:79, crate:80, fogA:81, fogB:82, fogC:83,
  wFloor:84, wWall:85, wPanel:86, wDoor:87, wBedTL:88, wBedTR:89, wBedBL:90, wBedBR:91,
  wCab:92, wMon:93, wCur:94, wFloor2:95, wLamp:96, ladder:97, bridgeH:98, bridgeV:99,
  mushroom:100, flR:101, flY:102, flB:103, honey:104, bread:105, letter:106, book:107,
  candle:108, basket:109, bucket:110, broom:111, cake:112, roofRL:113, roofRM:114, roofRR:115,
  roofBL:116, roofBM:117, roofBR:118, pinkWall:119, pinkWallWin:120, stoneWall:121,
  stoneWallWin:122, awning:123, clock:124, flag:125, garC:126, garM:127, empty:128
};

// ---- 基础工具（ES5）----
function grid(w, h, v) {
  var a = [];
  for (var y = 0; y < h; y++) {
    var r = [];
    for (var x = 0; x < w; x++) r.push(v);
    a.push(r);
  }
  return a;
}
function newMap(w, h) { return { w: w, h: h, ground: grid(w, h, 0), obs: grid(w, h, 0), objs: [] }; }
function fill(l, x, y, w, h, g) {
  for (var j = y; j < y + h; j++)
    for (var i = x; i < x + w; i++) l[j][i] = g;
}
function put(l, x, y, g) { l[y][x] = g; }
// 确定性散点随机数（固定种子，结果可复现）
function rnd(x, y, s) {
  var n = Math.sin(x * 127.1 + y * 311.7 + s * 74.7) * 43758.5453;
  return n - Math.floor(n);
}
function obj(m, name, tx, ty, props) {
  m.objs.push({ name: name, x: tx * 16 + 8, y: ty * 16 + 8, props: props });
}
function spawn(m, tx, ty, id) { obj(m, 'spawn', tx, ty, [['id', id]]); }
function door(m, tx, ty, target, sp) { obj(m, 'door', tx, ty, [['target', target], ['spawn', sp]]); }
function npc(m, tx, ty, id) { obj(m, 'npc', tx, ty, [['id', id]]); }
function inter(m, tx, ty, id) { obj(m, 'interact', tx, ty, [['id', id]]); }
function tree2x2(m, x, y) {
  put(m.obs, x, y, G.treeTL); put(m.obs, x + 1, y, G.treeTR);
  put(m.obs, x, y + 1, G.treeBL); put(m.obs, x + 1, y + 1, G.treeBR);
}
// 草地散点装饰（ground 层，无碰撞）
function scatter(m, seed, d) {
  for (var y = 0; y < m.h; y++) {
    for (var x = 0; x < m.w; x++) {
      var r = rnd(x, y, seed);
      var g = 0;
      if (r < d[0]) g = G.flowerA;
      else if (r < d[1]) g = G.flowerB;
      else if (r < d[2]) g = G.deepGrass;
      else if (r < d[3]) g = [G.flR, G.flY, G.flB][(x + y) % 3];
      if (g) m.ground[y][x] = g;
    }
  }
}
// 室外房屋：5 宽 × 4 高，朝南单门，整体进 obstacles
function houseS(m, x, y, roofL, roofM, roofR, chimney) {
  for (var i = 0; i < 5; i++) {
    var t = (i === 0) ? roofL : (i === 4 ? roofR : roofM);
    put(m.obs, x + i, y, t);
    put(m.obs, x + i, y + 1, t);
  }
  if (chimney) put(m.obs, x + 1, y, G.chimney);
  put(m.obs, x, y + 2, G.creamWall);
  put(m.obs, x + 1, y + 2, G.creamWallWin);
  put(m.obs, x + 2, y + 2, G.doorTop);
  put(m.obs, x + 3, y + 2, G.creamWallWin);
  put(m.obs, x + 4, y + 2, G.creamWall);
  put(m.obs, x, y + 3, G.creamWall);
  put(m.obs, x + 1, y + 3, G.creamWall);
  put(m.obs, x + 2, y + 3, G.doorT);
  put(m.obs, x + 3, y + 3, G.creamWall);
  put(m.obs, x + 4, y + 3, G.creamWall);
}
// 室内外壳：木地板满铺（ground），墙围边（obstacles），底边留门（门瓦片在 ground）
function indoorShell(m, wins, doorX) {
  for (var y = 0; y < m.h; y++)
    for (var x = 0; x < m.w; x++)
      m.ground[y][x] = ((x + y) % 2 === 0) ? G.floorA : G.floorB;
  m.ground[m.h - 1][doorX] = G.doorT;
  for (var x2 = 0; x2 < m.w; x2++) {
    m.obs[0][x2] = G.wallIn;
    if (x2 !== doorX) m.obs[m.h - 1][x2] = G.wallIn;
  }
  for (var i = 0; i < wins.length; i++) m.obs[0][wins[i]] = G.wallInWin;
  for (var y2 = 1; y2 < m.h - 1; y2++) {
    m.obs[y2][0] = G.wallIn;
    m.obs[y2][m.w - 1] = G.wallIn;
  }
}

// ================= 1. house 12×8 小满小屋 =================
function buildHouse() {
  var m = newMap(12, 8);
  indoorShell(m, [3, 8], 5);
  // 床（睡觉过日）+ 床头日历（C1）
  put(m.obs, 1, 1, G.bedTL); put(m.obs, 2, 1, G.bedTR);
  put(m.obs, 1, 2, G.bedBL); put(m.obs, 2, 2, G.bedBR);
  put(m.obs, 3, 1, G.calendar);
  // 桌椅 + 盆栽 + 地毯
  put(m.obs, 8, 4, G.table); put(m.obs, 7, 4, G.chair); put(m.obs, 9, 4, G.chair);
  put(m.obs, 10, 1, G.plant);
  put(m.ground, 5, 3, G.rug); put(m.ground, 6, 3, G.rug);
  put(m.ground, 5, 4, G.rug); put(m.ground, 6, 4, G.rug);
  spawn(m, 3, 3, 'default');
  spawn(m, 5, 5, 'from_plaza');
  door(m, 5, 6, 'plaza', 'from_house');
  inter(m, 1, 3, 'bed');
  inter(m, 3, 2, 'calendar');
  return m;
}

// ================= 2. plaza 30×20 村广场（枢纽） =================
function buildPlaza() {
  var m = newMap(30, 20);
  fill(m.ground, 0, 0, 30, 20, G.grass);
  scatter(m, 7, [0.05, 0.10, 0.15, 0.18]);
  // 石路：南北 x=14/15，东西 y=9/10，通往四座房屋的小路
  var y, x;
  for (y = 0; y < 20; y++) {
    put(m.ground, 14, y, (y % 3 === 0) ? G.stoneAlt : G.stone);
    put(m.ground, 15, y, (y % 3 === 0) ? G.stoneAlt : G.stone);
  }
  for (x = 0; x < 30; x++) {
    put(m.ground, x, 9, (x % 3 === 0) ? G.stoneAlt : G.stone);
    put(m.ground, x, 10, (x % 3 === 0) ? G.stoneAlt : G.stone);
  }
  var stubs = [4, 11, 18, 25];
  for (var s = 0; s < stubs.length; s++)
    for (y = 5; y <= 9; y++) put(m.ground, stubs[s], y, G.stone);
  // 中央广场砖（庆典场地核心）
  fill(m.ground, 11, 8, 8, 5, G.brick);
  // 庆典彩带（广场四角，装饰无碰撞；透明瓦片须放 obstacles 层，ground 层会露黑底）
  put(m.obs, 11, 7, G.ribbonR); put(m.obs, 18, 7, G.ribbonY);
  put(m.obs, 11, 13, G.ribbonB); put(m.obs, 18, 13, G.ribbonR);
  // 四周房屋：小屋/杂货店=橙屋顶，面包房/诊所=红屋顶
  houseS(m, 2, 1, G.roofOL, G.roofOM, G.roofOR, true);   // house  门 x=4
  houseS(m, 9, 1, G.roofRL, G.roofRM, G.roofRR, true);   // bakery 门 x=11
  houseS(m, 16, 1, G.roofOL, G.roofOM, G.roofOR, true);  // shop   门 x=18
  houseS(m, 23, 1, G.roofRL, G.roofRM, G.roofRR, false); // clinic 门 x=25
  // 公告板 + 三个花坛 + 双子鸟大树
  put(m.obs, 14, 8, G.board);
  put(m.obs, 10, 8, G.bedPink); put(m.obs, 19, 8, G.bedYellow); put(m.obs, 14, 13, G.bedPink);
  tree2x2(m, 12, 14);
  // 村口邮筒（大宝）+ 路灯 + 装饰树/灌木
  put(m.obs, 2, 8, G.mailbox);
  put(m.obs, 10, 6, G.lamp); put(m.obs, 19, 6, G.lamp);
  tree2x2(m, 1, 6); tree2x2(m, 27, 14); tree2x2(m, 1, 16);
  put(m.obs, 8, 16, G.bush); put(m.obs, 21, 17, G.bush); put(m.obs, 16, 15, G.bush);
  // 地图边缘装饰（留出 4 个路口）
  function skip(x2, y2) {
    return (y2 === 0 && (x2 === 14 || x2 === 15)) || (y2 === 19 && (x2 === 14 || x2 === 15)) ||
           (x2 === 0 && (y2 === 9 || y2 === 10)) || (x2 === 29 && (y2 === 9 || y2 === 10));
  }
  function edge(x2, y2) {
    var r = rnd(x2, y2, 9);
    put(m.obs, x2, y2, r < 0.5 ? G.bush : (r < 0.75 ? G.rock : G.stump));
  }
  for (x = 0; x < 30; x++) { if (!skip(x, 0)) edge(x, 0); if (!skip(x, 19)) edge(x, 19); }
  for (y = 1; y < 19; y++) { if (!skip(0, y)) edge(0, y); if (!skip(29, y)) edge(29, y); }
  // 对象
  spawn(m, 14, 11, 'default');
  spawn(m, 4, 7, 'from_house');   // 落点距门 24px，防回弹
  spawn(m, 11, 7, 'from_bakery');
  spawn(m, 18, 7, 'from_shop');
  spawn(m, 25, 7, 'from_clinic');
  spawn(m, 1, 10, 'from_lake');
  spawn(m, 28, 10, 'from_field');
  spawn(m, 14, 2, 'from_well');   // 落点距门 24px，防回弹
  spawn(m, 15, 18, 'from_fog');
  door(m, 4, 5, 'house', 'from_plaza');
  door(m, 11, 5, 'bakery', 'from_plaza');
  door(m, 18, 5, 'shop', 'from_plaza');
  door(m, 25, 5, 'clinic', 'from_plaza');
  door(m, 0, 9, 'lake', 'from_plaza');
  door(m, 29, 10, 'field', 'from_plaza');
  door(m, 14, 0, 'well', 'from_plaza');
  door(m, 15, 19, 'fog', 'from_plaza');
  npc(m, 15, 8, 'afu');        // 公告板旁
  npc(m, 12, 14, 'bird');      // 双子鸟，树上
  npc(m, 7, 12, 'squirrel');
  npc(m, 22, 14, 'fox');
  npc(m, 3, 8, 'dabao');       // 村口邮筒旁（PRD §4）
  inter(m, 14, 9, 'noticeboard');
  inter(m, 10, 9, 'flowerbed_1');
  inter(m, 19, 9, 'flowerbed_2');
  inter(m, 14, 14, 'flowerbed_3');
  return m;
}

// ================= 3. bakery 15×10 面包房 =================
function buildBakery() {
  var m = newMap(15, 10);
  indoorShell(m, [3, 7, 11], 7);
  fill(m.ground, 5, 5, 3, 2, G.rug);
  // 两段柜台 + 烤炉（C2 配方纸在桌上）
  put(m.obs, 4, 3, G.counterL); put(m.obs, 5, 3, G.counterR);
  put(m.obs, 8, 3, G.counterL); put(m.obs, 9, 3, G.counterR);
  put(m.obs, 12, 1, G.oven);
  put(m.obs, 10, 6, G.table); put(m.obs, 11, 6, G.recipe);
  put(m.obs, 13, 8, G.barrel); put(m.obs, 1, 8, G.plant);
  spawn(m, 7, 6, 'default');
  spawn(m, 7, 7, 'from_plaza');
  door(m, 7, 8, 'plaza', 'from_bakery');
  npc(m, 5, 2, 'yuanyuan');    // 柜台后
  inter(m, 4, 4, 'counter_bread');
  inter(m, 11, 7, 'recipe_paper');
  return m;
}

// ================= 4. shop 15×10 杂货店 =================
function buildShop() {
  var m = newMap(15, 10);
  indoorShell(m, [3, 7, 11], 7);
  fill(m.ground, 6, 6, 3, 2, G.rug);
  // 上排 9 格满货架（C5：同一商品重复 9 格）+ 下排空满混排
  var x;
  for (x = 3; x <= 11; x++) put(m.obs, x, 1, G.shelfF);
  for (x = 3; x <= 11; x++) put(m.obs, x, 4, (x % 3 === 0) ? G.shelfE : G.shelfF);
  put(m.obs, 1, 7, G.crate); put(m.obs, 1, 8, G.barrel); put(m.obs, 13, 1, G.plant);
  spawn(m, 7, 6, 'default');
  spawn(m, 7, 7, 'from_plaza');
  door(m, 7, 8, 'plaza', 'from_shop');
  npc(m, 2, 5, 'mimi');
  npc(m, 11, 7, 'hedgehog');
  inter(m, 7, 2, 'shelf_count');
  return m;
}

// ================= 5. clinic 12×8 诊所 =================
function buildClinic() {
  var m = newMap(12, 8);
  indoorShell(m, [3, 8], 5);
  // 芙医生的桌 + 桌上文件（C8）
  put(m.obs, 8, 2, G.table); put(m.obs, 9, 2, G.fileP);
  put(m.obs, 9, 3, G.chair);
  put(m.obs, 1, 1, G.cabinet); put(m.obs, 10, 6, G.plant);
  spawn(m, 6, 5, 'default');
  spawn(m, 5, 5, 'from_plaza');
  door(m, 5, 6, 'plaza', 'from_clinic');
  npc(m, 8, 1, 'fuyisheng');   // 桌后
  inter(m, 8, 3, 'doctor_desk');
  return m;
}

// ================= 6. lake 24×16 湖畔 =================
function buildLake() {
  var m = newMap(24, 16);
  fill(m.ground, 0, 0, 24, 16, G.grass);
  scatter(m, 21, [0.06, 0.11, 0.15, 0.18]);
  var x, y;
  // 石路：东门（23,10）通向长椅与野餐区
  for (x = 3; x <= 23; x++) put(m.ground, x, 10, (x % 3 === 0) ? G.stoneAlt : G.stone);
  // 野餐布（T7，走上去触发，放 ground 可行走）
  put(m.ground, 15, 10, G.picTL); put(m.ground, 16, 10, G.picTR);
  put(m.ground, 15, 11, G.picBL); put(m.ground, 16, 11, G.picBR);
  put(m.ground, 3, 12, G.mushroom);
  // 湖水：北半边大片湖，水岸瓦片围边（全部进 obstacles）
  for (y = 0; y <= 5; y++)
    for (x = 2; x <= 21; x++)
      put(m.obs, x, y, ((x + y) % 2 === 0) ? G.waterA : G.waterB);
  for (x = 1; x <= 22; x++) put(m.obs, x, 6, G.shoreS);
  for (y = 0; y <= 5; y++) { put(m.obs, 1, y, G.shoreW); put(m.obs, 22, y, G.shoreE); }
  // 长椅（龟爷爷）+ 树木花草装饰
  put(m.obs, 10, 8, G.benchL); put(m.obs, 11, 8, G.benchR);
  tree2x2(m, 1, 13); tree2x2(m, 20, 12);
  put(m.obs, 8, 14, G.bush); put(m.obs, 12, 13, G.bush);
  put(m.obs, 5, 9, G.rock); put(m.obs, 14, 14, G.stump);
  spawn(m, 21, 11, 'default');
  spawn(m, 22, 10, 'from_plaza');
  door(m, 23, 10, 'plaza', 'from_lake');
  npc(m, 12, 8, 'guiyeye');    // 长椅旁
  npc(m, 6, 12, 'pig');
  inter(m, 16, 11, 'picnic_mat');
  return m;
}

// ================= 7. field 20×14 花田 =================
function buildField() {
  var m = newMap(20, 14);
  fill(m.ground, 0, 0, 20, 14, G.grass);
  scatter(m, 33, [0.10, 0.18, 0.24, 0.32]);
  var x, y;
  // 土路横穿（西门进出）
  for (x = 0; x <= 19; x++) put(m.ground, x, 7, G.dirt);
  // 浇花点花丛（T2/T12 用，ground 装饰）
  put(m.ground, 15, 9, G.flR); put(m.ground, 16, 9, G.flY); put(m.ground, 17, 9, G.flB);
  put(m.ground, 15, 10, G.flY); put(m.ground, 16, 10, G.flR); put(m.ground, 17, 10, G.flB);
  // 栅栏围边，西门留口
  for (x = 0; x <= 19; x++) { put(m.obs, x, 0, G.fenceH); put(m.obs, x, 13, G.fenceH); }
  for (y = 1; y <= 12; y++) { if (y !== 7) put(m.obs, 0, y, G.fenceV); put(m.obs, 19, y, G.fenceV); }
  // 5 丛莓果（T6）
  var berries = [[4, 4], [9, 4], [14, 4], [6, 9], [12, 9]];
  for (var b = 0; b < berries.length; b++) put(m.obs, berries[b][0], berries[b][1], G.berry);
  // 树木装饰
  tree2x2(m, 16, 1);
  put(m.obs, 2, 11, G.bush); put(m.obs, 10, 11, G.bush); put(m.obs, 2, 2, G.rock);
  spawn(m, 2, 7, 'default');
  spawn(m, 1, 7, 'from_plaza');
  door(m, 0, 7, 'plaza', 'from_field');
  npc(m, 10, 7, 'chick');
  inter(m, 4, 5, 'berry_1');
  inter(m, 9, 5, 'berry_2');
  inter(m, 14, 5, 'berry_3');
  inter(m, 6, 10, 'berry_4');
  inter(m, 12, 10, 'berry_5');
  inter(m, 16, 10, 'water_point');
  return m;
}

// ================= 8. well 14×10 古井空地 =================
function buildWell() {
  var m = newMap(14, 10);
  fill(m.ground, 0, 0, 14, 10, G.grass);
  scatter(m, 45, [0.07, 0.13, 0.17, 0.19]);
  var y;
  // 土场 + 南门土路
  fill(m.ground, 5, 3, 4, 4, G.dirt);
  for (y = 7; y <= 9; y++) { put(m.ground, 6, y, G.dirt); put(m.ground, 7, y, G.dirt); }
  put(m.ground, 3, 4, G.mushroom); put(m.ground, 10, 6, G.mushroom);
  // 中央古井（C7）
  put(m.obs, 6, 4, G.wellTL); put(m.obs, 7, 4, G.wellTR);
  put(m.obs, 6, 5, G.wellBL); put(m.obs, 7, 5, G.wellBR);
  // 四周树木
  tree2x2(m, 1, 1); tree2x2(m, 11, 1); tree2x2(m, 1, 7); tree2x2(m, 11, 7);
  put(m.obs, 4, 2, G.bush); put(m.obs, 9, 2, G.bush);
  put(m.obs, 4, 8, G.bush); put(m.obs, 9, 8, G.bush);
  put(m.obs, 4, 5, G.rock); put(m.obs, 9, 5, G.rock);
  spawn(m, 6, 8, 'default');
  spawn(m, 7, 8, 'from_plaza');
  door(m, 7, 9, 'plaza', 'from_well');
  inter(m, 6, 6, 'ancient_well');
  return m;
}

// ================= 9. fog 20×8 雾墙边界 =================
function buildFog() {
  var m = newMap(20, 8);
  fill(m.ground, 0, 0, 20, 8, G.grass);
  scatter(m, 57, [0.05, 0.09, 0.20, 0.20]);
  var x, y;
  // 雾前深草带 + 北门土路
  for (x = 0; x <= 19; x++) put(m.ground, x, 4, G.deepGrass);
  for (y = 0; y <= 4; y++) { put(m.ground, 9, y, G.dirt); put(m.ground, 10, y, G.dirt); }
  // 远端（南侧）雾墙铺满，进 obstacles（E3 入口）
  for (y = 5; y <= 7; y++)
    for (x = 0; x <= 19; x++)
      put(m.obs, x, y, [G.fogA, G.fogB, G.fogC][(x + y) % 3]);
  // 两侧树木
  tree2x2(m, 1, 1); tree2x2(m, 17, 1);
  put(m.obs, 4, 3, G.bush); put(m.obs, 15, 3, G.bush);
  put(m.obs, 0, 3, G.bush); put(m.obs, 19, 3, G.bush);
  put(m.obs, 6, 2, G.rock);
  spawn(m, 10, 2, 'default');
  spawn(m, 9, 2, 'from_plaza');   // 落点距门 >24px，防回弹
  door(m, 10, 0, 'plaza', 'from_fog');
  inter(m, 10, 4, 'fog_wall');  // 雾前可达格
  return m;
}

// ================= 10. white 20×14 白色层（走廊 + 病房） =================
function buildWhite() {
  var m = newMap(20, 14);
  var x, y;
  // 默认白墙铺底；上半 20×6 走廊 + 左下 10×8 病房刷白地板
  fill(m.ground, 0, 0, 20, 14, G.wWall);
  for (y = 1; y <= 5; y++)
    for (x = 1; x <= 18; x++)
      m.ground[y][x] = (x % 4 === 0 && y % 2 === 0) ? G.wFloor2 : G.wFloor;
  for (y = 7; y <= 12; y++)
    for (x = 1; x <= 8; x++)
      m.ground[y][x] = ((x + y) % 3 === 0) ? G.wFloor2 : G.wFloor;
  put(m.ground, 4, 6, G.wFloor); put(m.ground, 5, 6, G.wFloor); // 走廊↔病房通道格
  // 墙体（obstacles）：北墙（含墙板/天花板灯）、东西墙、南墙、右下实心墙块、病房北/东墙
  for (x = 0; x <= 19; x++) put(m.obs, x, 0, G.wWall);
  put(m.obs, 5, 0, G.wPanel); put(m.obs, 10, 0, G.wPanel); put(m.obs, 15, 0, G.wPanel);
  put(m.obs, 3, 0, G.wLamp); put(m.obs, 8, 0, G.wLamp);
  put(m.obs, 13, 0, G.wLamp); put(m.obs, 18, 0, G.wLamp);
  for (y = 1; y <= 13; y++) { put(m.obs, 0, y, G.wWall); put(m.obs, 19, y, G.wWall); }
  for (x = 1; x <= 9; x++) put(m.obs, x, 13, G.wWall);
  fill(m.obs, 10, 6, 10, 8, G.wWall); // 右下 10×8 实心（房间之外）
  for (x = 1; x <= 9; x++) if (x !== 4 && x !== 5) put(m.obs, x, 6, G.wWall);
  for (y = 6; y <= 13; y++) put(m.obs, 9, y, G.wWall);
  put(m.obs, 9, 8, G.wCur); put(m.obs, 9, 9, G.wCur); // 白窗帘挂在东墙上
  // 病房家具：白床 + 白监护仪 + 白柜
  put(m.obs, 2, 8, G.wBedTL); put(m.obs, 3, 8, G.wBedTR);
  put(m.obs, 2, 9, G.wBedBL); put(m.obs, 3, 9, G.wBedBR);
  put(m.obs, 4, 8, G.wMon);
  put(m.obs, 7, 12, G.wCab);
  spawn(m, 17, 3, 'default'); // 走廊一端
  return m;
}

// ================= 序列化 =================
function csv(l, w, h) {
  var s = '';
  for (var y = 0; y < h; y++) {
    var row = '';
    for (var x = 0; x < w; x++) {
      row += l[y][x];
      if (x < w - 1) row += ',';
    }
    s += row;
    if (y < h - 1) s += ',\n';
  }
  return s;
}
function tmx(m) {
  var s = '<?xml version="1.0" encoding="UTF-8"?>\n';
  s += '<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down"' +
       ' width="' + m.w + '" height="' + m.h + '" tilewidth="16" tileheight="16" infinite="0"' +
       ' nextlayerid="4" nextobjectid="' + (m.objs.length + 1) + '">\n';
  s += ' <tileset firstgid="1" source="tileset.tsx"/>\n';
  s += ' <layer id="1" name="ground" width="' + m.w + '" height="' + m.h + '">\n';
  s += '  <data encoding="csv">\n' + csv(m.ground, m.w, m.h) + '\n  </data>\n </layer>\n';
  s += ' <layer id="2" name="obstacles" width="' + m.w + '" height="' + m.h + '">\n';
  s += '  <properties>\n   <property name="collides" type="bool" value="true"/>\n  </properties>\n';
  s += '  <data encoding="csv">\n' + csv(m.obs, m.w, m.h) + '\n  </data>\n </layer>\n';
  s += ' <objectgroup id="3" name="objects">\n';
  for (var i = 0; i < m.objs.length; i++) {
    var o = m.objs[i];
    s += '  <object id="' + (i + 1) + '" name="' + o.name + '" x="' + o.x + '" y="' + o.y + '">\n';
    s += '   <properties>\n';
    for (var p = 0; p < o.props.length; p++)
      s += '    <property name="' + o.props[p][0] + '" value="' + o.props[p][1] + '"/>\n';
    s += '   </properties>\n   <point/>\n  </object>\n';
  }
  s += ' </objectgroup>\n</map>\n';
  return s;
}
function writeMap(name, m) {
  var f = new TextFile(OUT + name + '.tmx', TextFile.WriteOnly);
  f.write(tmx(m));
  f.commit();
  tiled.log('wrote ' + name + '.tmx (' + m.w + 'x' + m.h + ', ' + m.objs.length + ' objects)');
}

writeMap('house', buildHouse());
writeMap('plaza', buildPlaza());
writeMap('bakery', buildBakery());
writeMap('shop', buildShop());
writeMap('clinic', buildClinic());
writeMap('lake', buildLake());
writeMap('field', buildField());
writeMap('well', buildWell());
writeMap('fog', buildFog());
writeMap('white', buildWhite());
tiled.log('all 10 maps done');
