diff --git a/.planning/phases/09-minimum-browser-runnable/09-19-PLAN.md b/.planning/phases/09-minimum-browser-runnable/09-19-PLAN.md
new file mode 100644
index 0000000..e9a9927
--- /dev/null
+++ b/.planning/phases/09-minimum-browser-runnable/09-19-PLAN.md
@@ -0,0 +1,472 @@
+---
+phase: 09-minimum-browser-runnable
+plan: 19
+type: execute
+wave: 16
+depends_on: [09-18]
+files_modified:
+ - src/content/tile.jsonc
+ - src/content/enemy.jsonc
+ - src/content/item.jsonc
+ - src/content/maps/.list.jsonc
+ - src/content/maps/empty.jsonc
+ - src/content/maps/MT0.jsonc
+ - src/content/maps/sample0.jsonc
+ - src/content/maps/sample1.jsonc
+ - src/content/maps/sample2.jsonc
+ - src/content/texture/.list.jsonc
+ - src/content/texture/*.png
+ - script/migrate-project-textures.ts
+ - .planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-UNMAPPED.md
+ - .planning/phases/09-minimum-browser-runnable/09-19-SUMMARY.md
+autonomous: false
+requirements: []
+estimate:
+ tokens: 90000
+ raw_tokens: 45000
+ tasks: 4
+ confidence: low
+must_haves:
+ truths:
+ - "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03 / D-76)**:Task 0 必须先汇报「本计划 = 任务 9 · 旧引擎数据基础移植」的**要做什么 / 为什么 / 大致怎么做 / 影响范围**,**必须列明:六类基础字段范围与三类判定(映射候选可确定 / §8.2 待定不移植 / §8.1 无目标字段进 unmapped 文档)、`num` 策略 A 的具体分配方案、PNG 生成可行性结论(零依赖 Node `zlib` 方案)与 48px 素材合并(U-9 / U-17)待裁定项、地图图层键口径、产出文件清单、消费接线不在本增量、静态门禁方式与 `open_items`(OI-1..OI-7)**,然后必须**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行本计划任何一步、不得派发执行子代理,且不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行。"
+ - "**基础字段范围(D-76):** 只移植盘点台账 §1.2 / §3.1 / §4.2 / §6.2 中「**映射候选(可确定)**」的字段:tile 的 `id` / `cls→type`(§1.1)/ `cannotOut`+`cannotIn`→`pass.outPass`+`pass.inPass`;enemy 的 `num` / `attribute`(`name`/`hp`/`atk`/`def`/`money`/`exp`/`point`)/ `special`;item 的 `num` / `category`(§4.1)/ `name` / `text` / `hideInToolbox`;map 的 `floorId` / `width` / `map` / `layerAlias`。**§8.2「待定」不当移植**(tile `trigger` / `canPass`;enemy `value`/`range`/`zoneSquare`/`add`/`n`/`bigImage`/`notBomb`/`atkValue`/`defValue`;item `itemEffect`/`itemEffectTip`/`useItemEffect`/`useItemEvent`/`canUseItemEffect`/`equip` 子字段;map `events` / `bgmap`/`fgmap`/`bg2map`/`fg2map`)。**§8.1「无目标字段」不当移植且只记入 `09-DATA-MIGRATION-UNMAPPED.md`**(tile `canBreak`/`script`/`doorInfo`/`animate`/`name`/`faceIds`/`autotileConnection`;enemy `faceIds`;item `hideInReplay`;map `title`/`name`/`height`/`firstArrive`/`eachArrive`/`parallelDo`/`changeFloor`/`afterBattle`/`afterGetItem`/`afterOpenDoor`/`beforeBattle`/`cannotMove`/`cannotMoveIn`/`autoEvent`/`images`/`bgm`/`weather`/`ratio`/`defaultGround`/`canFlyTo`/`canFlyFrom`/`canUseQuickShop`/`cannotViewMap`/`upFloor`)。"
+ - "**`num` 策略 A(D-79)与跨文件一致性:** 图块 `num` 全部重新分配,按 `TileType` 分段(段序 `Terrain` / `Animate` / `Item` / `Enemy` / `Npc` / `Autotile`,段内按旧 `num` 升序),`num` 从 1 起连续(0 保留空图块),唯一约束 `id` ↔ `num` 一致;同一 `id` 的新 `num` **必须在 `tile.jsonc` / `enemy.jsonc` / `item.jsonc` / `texture/.list.jsonc` 的 `map` / `maps/*.jsonc` 图层数值五处一致**(enemy/item 的 `num` = 对应图块 `num`;地图与贴图 `map` 中旧 `num` 经「旧 `num`→`id`→新 `num`」重映射)。"
+ - "**贴图素材(D-72 / D-81):** `src/content/texture/` 生成 42 张 `materials` 拆分图(A `terrains0..2` 单帧 4×4;B `terrainsAnim0..7` 多帧 4 帧列×4 行;C `items0..3` 单帧 4×4;D `enemy0..20` 多帧 2 帧列×4 行;E `npc0..*` 多帧 2 帧列×4 行)+ 逐字节复制 `autotiles/*.png`(4)与 `tilesets/magictower.png`(1);`animates` 并入 `terrains`、不设独立 `animates` 素材组(D-72③);`texture/.list.jsonc` 用 D-80 已批准的类型/字段(`ITextureListConfig` 及 `grid`/`rowAnimate`/`autotile`/`tileset` 条目;字段 `type`/`source`/`map`/`width`/`height`/`identifier`/`autotileType`/`cellWidth`/`cellHeight`/`textures`),且每个 `source` 文件存在、`map`/`identifier` 覆盖对应图块 `num`。"
+ - "**地图范围(D-82):** 全部 5 楼层 `empty` / `MT0` / `sample0` / `sample1` / `sample2` 写入 `src/content/maps/{id}.jsonc`,`maps/.list.jsonc` 列出 5 个 id;每个楼层 `map` 的**每个图层数组长度 = `width` × `height`**(`mapState.fromRaw` 约束),图层内数值均为 `0` 或有效新图块 `num`。"
+ - "**unmapped 说明文档(D-78):** `.planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-UNMAPPED.md` **仅**收录 §8.1「无目标字段」项(路径/命名待 Task 0 批准);§8.2 待定项**不写入**该文档(保留在盘点台账 `09-DATA-MIGRATION-AUDIT.md`)。"
+ - "**消费接线不在本增量(D-70 / D-83):** 不把配置写入 store、不把 list 写入 `TextureManager`、不改 `packages*/` 与 `src/`(除 `src/content/**`)、不改 `public/project/**`;沿用 D-72③ 与「旧 `events`/`trigger` 等无对应目标字段者不移植」。"
+ artifacts:
+ - path: src/content/tile.jsonc
+ provides: "235 条图块原始数据(`num` 策略 A、`id`、`type`(`TileType`)、`pass`(`cannotOut`/`cannotIn` 可确定映射)、`events`/`eventPass` 默认),替换现有样例"
+ - path: src/content/enemy.jsonc
+ provides: "81 条怪物原始数据(`num` 对应图块、`attribute` 七字段、`special` 数字键),替换现有样例"
+ - path: src/content/item.jsonc
+ provides: "62 条道具原始数据(`num` 对应图块、`category`、`name`、`text`、`hideInToolbox`),替换现有样例"
+ - path: src/content/maps/.list.jsonc
+ provides: "5 个楼层 id 列表(`empty`/`MT0`/`sample0`/`sample1`/`sample2`)"
+ - path: src/content/maps/empty.jsonc
+ provides: "`empty` 楼层 `IMapRawData`(flattened 图层)"
+ - path: src/content/maps/MT0.jsonc
+ provides: "`MT0` 楼层 `IMapRawData`(替换现有样例)"
+ - path: src/content/maps/sample0.jsonc
+ provides: "`sample0` 楼层 `IMapRawData`"
+ - path: src/content/maps/sample1.jsonc
+ provides: "`sample1` 楼层 `IMapRawData`"
+ - path: src/content/maps/sample2.jsonc
+ provides: "`sample2` 楼层 `IMapRawData`(26×26)"
+ - path: src/content/texture/.list.jsonc
+ provides: "D-80 已批准的 `ITextureListConfig`,覆盖拆分图 + 4 autotile + 1 tileset"
+ - path: src/content/texture/*.png
+ provides: "42 张 D-72 拆分图(`terrains*`/`terrainsAnim*`/`items*`/`enemy*`/`npc*`)+ 复制的 `autotile*`(4)+ `magictower.png`(1)"
+ - path: script/migrate-project-textures.ts
+ provides: "一次性零依赖 PNG 生成脚本(Node `zlib` 解码/编码;路径/命名待批准)"
+ - path: .planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-UNMAPPED.md
+ provides: "§8.1 无目标字段说明文档(路径/命名待批准)"
+ - path: .planning/phases/09-minimum-browser-runnable/09-19-SUMMARY.md
+ provides: "本计划执行摘要:范围与映射口径、`num` 分配、素材产出、门禁结果、`open_items`"
+ key_links:
+ - "`src/content/tile.jsonc` 的 `num`(策略 A)↔ `src/content/texture/.list.jsonc` 的 `map`/`identifier` ↔ `src/content/enemy.jsonc`/`src/content/item.jsonc` 的 `num` ↔ `src/content/maps/*.jsonc` 图层数值 —— 五处 `id`↔`num` 全局一致(D-79 唯一硬约束)"
+ - "`src/content/texture/.list.jsonc` 条目 ↔ `packages-user/client-base/src/material/types.ts`(`addGrid`:261 / `addRowAnimate`:275 / `addAutotile`:289 / `addTileset`:302 / `AutotileType`:12 / `IIndexedIdentifier`:50)—— D-80 已批准 schema 的能力依据"
+ - "`src/content/{tile,enemy,item}.jsonc`/`maps/*.jsonc` 字段 ↔ `packages-user/data-common/src/store/types.ts`(`ITileRawData`:47 / `ITilePassData`:38 / `IEnemyRawData`:319 / `IItemRawData`:176 / `IMapRawData`:260)—— 基础字段判定的唯一依据"
+ - "`src/content/maps/*.jsonc` 图层数组长度 ↔ `packages-user/data-base/src/map/mapState.ts`:40-95(`fromRaw`:各层等长、`length % width === 0`)—— 地图结构的硬约束"
+ - "`src/content/texture/*.png` ↔ `public/project/materials|autotiles|tilesets/*.png`(D-72 拆分/复制来源)"
+ - "`src/content/*.jsonc` 解析 ↔ `packages-user/data-state/src/loader/jsoncProcessor.ts`(`// --- SYSTEM PREFIX END --- //` 之后必须是严格 JSON,不得含注释)"
+ assumptions:
+ - "**PNG 零依赖方案可行(执行日已核实):** 仓库无 `pngjs`/`sharp`/`canvas`/`jimp` 等 PNG 库(`package.json` 与 `node_modules` 均已核实);Node 内置 `zlib`(`inflateSync`/`deflateSync`)可用;源 PNG 全部为 **8 位、非隔行**(`materials`/`tilesets` 为 color type 6 RGBA,`autotiles` 为 color type 3 调色板),可用「解析 IHDR/IDAT + `zlib` 解压 + 反滤镜」纯 Node 解码、并以「滤镜 + `zlib` 压缩 + CRC32」编码写回。故本计划**不新增任何依赖**。"
+ - "**48px 素材合并待裁定(U-9 / U-17):** `enemys.png` 为 32×32 单元格(2 帧列)、`enemy48.png` 为 64×48 单元格,`npcs.png` 为 32×32、`npc48.png` 为 64×48;D-72① 要求合并,但两种单元格尺寸无法同图,按单元格分组会使 D-81 的 `npc0..5`(6 张)变为 7 张。本计划将该项作为 Task 0 决策点,默认「按单元格尺寸分组、48px 单独成图、在 `open_items`/SUMMARY 记录与 D-81 计数的偏差」,待用户裁定。"
+ - "**地图图层键语义待确认:** `mapState.fromRaw` 显示 `IMapRawData.map` 每个键是**整张展平网格**(`length = width × height`,行主序),并非盘点 §6.2 候选所称「层键即行索引」;故本计划按「旧二维 `map` → 单张展平图层」处理,图层键取值(默认 `0`)与 `layerAlias` 记入 `open_items`。"
+ - "**现有样例的省略策略有效:** `src/content/{tile,enemy,item}.jsonc` 现有样例已省略部分必填字段(如 item 省略 `effect`/`equip`),说明加载/默认合并允许省略;本计划对 §8.2 待定字段沿用同样省略策略,不臆造取值。"
+ - "**行号可能漂移:** 本计划引用行号为规划日实测;门禁以「文件存在 + 结构断言」为准,不硬编码行号。"
+ prohibitions:
+ - "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理。"
+ - "**写入边界:** 只允许写本 `files_modified` 所列路径(`src/content/**` 数据与 PNG、`script/migrate-project-textures.ts`、`09-DATA-MIGRATION-UNMAPPED.md`、`09-19-SUMMARY.md`);**不得**改 `packages*/**`、`src/**`(除 `src/content/**`)、`public/project/**`。"
+ - "**`.planning/**` 边界:** 不得改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / `09-DATA-MIGRATION-AUDIT.md` / 任何既有 PLAN 或 SUMMARY;只允许新增 `09-19-PLAN.md`(规划期已产出)、`09-DATA-MIGRATION-UNMAPPED.md`(待批准)与 `09-19-SUMMARY.md`。"
+ - "**不移植边界:** §8.1 无目标字段**不得**出现在 `tile.jsonc`/`enemy.jsonc`/`item.jsonc`/`maps/*.jsonc` 中(只进 unmapped 文档);§8.2 待定字段**不得**移植、也**不得**进 unmapped 文档。"
+ - "**命名边界(AGENTS.md / D-73):** 任何未批准的新命名(一次性脚本路径、unmapped 文档路径/命名、`source` 路径形态、地图图层键口径、48px 处理方式)**先入 `open_items`,由 Task 0 报备待批准**;D-80 已批准的类型/字段名可直接使用。"
+ - "**不臆造:** 不得编造字段取值 / 映射关系 / PNG 尺寸;无法从盘点台账与类型定义确定者入 `open_items`,不得落入数据文件。"
+ - "**不新增依赖:** 不得改 `package.json`、不得安装 `pngjs`/`sharp`/`canvas`/`jimp` 等;PNG 生成必须用 Node 内置 `zlib` 零依赖方案。"
+ - "**不改既有注释 / jsDoc:** 不改 `src/content/**` 既有文件头 `// Type:` 注释行;所有生成文件头部 `// Type:` 前缀不得改动。"
+ - "**jsonc 严格性:** `// --- SYSTEM PREFIX END --- //` 之后必须是严格 JSON(无注释、无尾逗号),否则加载器 `JSON.parse` 失败。"
+ - "不得改 `client.jsonc` / `core.jsonc` / `defaults/**`(本增量只写 tile/enemy/item/maps/texture)。"
+ - "不得把门禁建立在「全仓 `pnpm check:type`」「`pnpm build`」之上;不得为「验证」改动源码或数据。"
+ - "不得触碰、暂存、提交用户的并发改动(D-56,当前 `AGENTS.md` / `09-CONTEXT.md` 处于 `M`);提交前 `git status` 逐条核对暂存面,只暂存本计划 `files_modified` 的条目。"
+ open_items:
+ - "**OI-1(unmapped 文档路径/命名):** 提议 `.planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-UNMAPPED.md`(D-78 提议)。**待用户报备批准**;未批准前不得写入。"
+ - "**OI-2(一次性脚本路径/命名):** 提议 `script/migrate-project-textures.ts`(小驼峰代码文件命名)。**待用户报备批准**;如使用其它命名须先获批。"
+ - "**OI-3(48px 素材合并处理 = U-9 / U-17):** `enemy48`/`npc48` 的 64×48 单元格与 32×32 无法同图;默认「按尺寸分组、48px 单独成图」会使 `npc` 目标图由 D-81 的 6 张变为 7 张(`enemy` 仍 21 张)。**待用户裁定**(A 按尺寸分组=默认;B 缩放/留白并入以保持 D-81 计数;C 本增量先只出 32px、48px 延后)。"
+ - "**OI-4(地图图层键 / `layerAlias` 口径):** 盘点 §6.2「层键即行索引」与 `mapState.fromRaw`(整张展平网格)不符;默认「旧二维 `map` → 单张展平图层(键 `0`),`layerAlias` = `{}`」。**待用户确认**(额外图层 `bgmap`/`fgmap` 等属 §8.2 U-16b 本增量不移植)。"
+ - "**OI-5(`texture/.list.jsonc` 的 `source` 路径形态):** 默认用相对 `texture/` 的文件名(如 `terrains0.png`),与 `client.jsonc` 的 `textureDir: texture` 对齐。**待用户确认**是否需含目录前缀。"
+ - "**OI-6(autotile / tileset 参数):** `autotile1.png`(384×128)的 `AutotileType` 无法由尺寸唯一判定(U-7),`magictower.png` 的 `cellWidth`/`cellHeight` 与 `identifier.index` 待确认(U-8);默认 `autotile.png`/`autotile2.png`/`autotile3.png` 取 `Big3x4`、`autotile1.png` 暂取 `Big3x4`、`identifier` 取图块 `num`(20/151/152/153),tileset 取 `32/32`、`index: 0`、`alias: magictower`。**待用户确认。**"
+ - "**OI-7(写操作方式声明):** 本计划执行含**文件写入**(数据文件、PNG、脚本、文档);执行须按 AGENTS.md「修改方式须预先声明」(内联 / 派发子代理)。"
+---
+
+
+Phase 9 · 任务 9 · 第 19 个计划 —— **旧引擎数据基础移植**:把 `public/project` 的**基础字段与数据**移植到 `src/content`(jsonc 数据 + PNG 素材),替换现有样例。
+
+**要解决的问题:** Phase 9 要在已完成的数据端之上产出最小浏览器可运行版本;当前 `src/content` 只有手写样例(`tile.jsonc` 1 条、`enemy.jsonc` 1 条、`item.jsonc` 1 条、`maps/MT0.jsonc` 1 张、`texture/.list.jsonc` 为空 `{}`),无法驱动真实的 235 图块 / 81 怪物 / 62 道具 / 5 楼层。任务 8(`09-17`/`09-18`)已产出只读盘点台账 `09-DATA-MIGRATION-AUDIT.md`,逐字段界定了「映射候选(可确定)」「§8.2 待定」「§8.1 无目标字段」三类。本计划据该台账,把**可确定的基础字段**落地为 `src/content` 数据与素材;**无对应目标字段者**(§8.1)只形成说明文档(D-78);**§8.2 待定项不移植**(D-76)。
+
+**大致解决方案(本计划 = 09-19):**
+- **Task 0(`checkpoint:decision@blocking-human`)= 预执行汇报关卡**:汇报要做什么 / 为什么 / 大致怎么做 / 影响范围(含 PNG 零依赖可行性结论、`num` 策略 A 方案、48px 合并与地图图层键等待裁定项),停下等待用户「可以执行」。
+- **Task 1 = 三类实体 jsonc**:按 `num` 策略 A 一次性决定全局 `id`↔`num`;写 `tile.jsonc`(235 条)、`enemy.jsonc`(81 条)、`item.jsonc`(62 条)——**建立跨文件唯一的 `num` 基准**。
+- **Task 2 = 贴图素材 + `.list.jsonc`**:写一次性零依赖 PNG 脚本(Node `zlib`),按 D-72 拆分 42 张图并复制 autotile/tileset,据 Task 1 的新 `num` 写 `texture/.list.jsonc`。
+- **Task 3 = 地图 + 说明文档 + 收口**:写 5 楼层 jsonc 与 `maps/.list.jsonc`(旧 `num` 重映射为新 `num`)、写 `09-DATA-MIGRATION-UNMAPPED.md`(仅 §8.1)、跑最终静态门禁并写 `09-19-SUMMARY.md`。
+
+**为何分三个执行任务:** 数据依赖为「先定 `num`(Task 1)→ 贴图 list 复用该 `num`(Task 2)→ 地图把旧 `num` 重映射为新 `num`(Task 3)」;三类互不冲突、按依赖顺序最稳,且避免同一文件反复改写。
+
+**为何不设 `tracer` 任务:** 本增量是**批量数据搬运**(伞阶段增量派发,D-01),交付物为数据文件而非分层功能;「薄垂直切片」在数据搬运上不额外降低架构风险,且与本阶段既有只读范式(`09-17`/`09-18`)一致。故按「实体 / 素材 / 地图」三类横向拆分。
+
+**修改方式声明(AGENTS.md):** 本计划执行含**文件写入**,将由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 所列路径外无任何写入。
+
+**PNG 生成可行性(本规划已核实):** 仓库**无** PNG 库(`package.json` 与 `node_modules` 均核实缺失 `pngjs`/`sharp`/`canvas`/`jimp`);Node 内置 `zlib`(`inflateSync`/`deflateSync`)可用;源 PNG 均为 **8 位非隔行**(材料为 RGBA type 6、autotile 为调色板 type 3)。故采用**零依赖**手写 PNG 编解码(解析 IHDR/IDAT + `zlib` 解压 + 反滤镜;编码则滤镜 + `zlib` 压缩 + CRC32),**不新增任何依赖**。
+
+**API coverage:** `detected:false`。**`No external API integration: 本计划为本地数据/素材移植,不涉及任何外部 API。`**
+
+
+@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
+@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md
+@.planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-AUDIT.md
+@.planning/phases/09-minimum-browser-runnable/09-18-SUMMARY.md
+@.planning/phases/09-minimum-browser-runnable/09-19-PLAN.md
+@AGENTS.md
+@dev.md
+
+
+
+
+
+ Task 0: 预执行汇报关卡 —— 汇报「任务 9 · 旧引擎数据基础移植」的要做什么 / 为什么 / 大致怎么做 / 影响范围,等待用户回复「可以执行」
+ `09-DATA-MIGRATION-AUDIT.md`(09-17/09-18 产出)已存在且含 §1.5.1 `id` 全清单与 §8.1 / §8.2 分区;`packages-user/data-common/src/store/types.ts`、`packages-user/client-base/src/material/types.ts`、`packages-user/data-state/src/loader/jsoncProcessor.ts`、`packages-user/data-base/src/map/mapState.ts` 均存在;`public/project/{maps,enemys,items}.js` 与 `public/project/floors/*.js`、`public/project/{materials,autotiles,tilesets}/*.png` 均存在;`src/content/texture/.list.jsonc` 存在(现状 `{}`);`09-DATA-MIGRATION-UNMAPPED.md` 与 `09-19-SUMMARY.md`**尚不存在**;Node 内置 `zlib.inflateSync`/`deflateSync` 可用。
+ (只读汇报,不修改任何文件)
+
+ - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-01 / D-03 / D-04 / D-76 / D-77 / D-78 / D-79 / D-80 / D-81 / D-82 / D-83)
+ - .planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-AUDIT.md(§1.2 / §1.5.1 / §2 / §3.1 / §4.2 / §5.1 / §6.2 / §7.1 / §8.1 / §8.2 / §9)
+ - .planning/phases/09-minimum-browser-runnable/09-19-PLAN.md(本计划全文,含映射口径、OI-1..OI-7)
+ - .planning/phases/09-minimum-browser-runnable/09-18-SUMMARY.md(上游摘要,只读参考)
+ - packages-user/data-common/src/store/types.ts(`ITileRawData` / `ITilePassData` / `IEnemyRawData` / `IItemRawData` / `IMapRawData`)
+ - packages-user/client-base/src/material/types.ts(`addGrid` / `addRowAnimate` / `addAutotile` / `addTileset` / `AutotileType` / `IIndexedIdentifier`)
+ - AGENTS.md(执行前须获批、命名须先反馈、修改方式须预先声明);dev.md(命名规则、注释规范、模块原则)
+
+
+ **停下并向用户汇报,等待明确回复「可以执行」后才继续;汇报本身只读。**
+
+ ① **要解决的问题:** `src/content` 目前只有样例(tile 1 条 / enemy 1 条 / item 1 条 / maps 仅 MT0 / texture list 空),无法驱动真实数据。本计划据只读台账 `09-DATA-MIGRATION-AUDIT.md`,把 `public/project` 的**基础字段**移植为 `src/content` 的 jsonc 数据与 PNG 素材,替换样例。
+
+ ② **范围(六类,D-71 / D-77):** 图块 tile(235)、怪物 enemy(81)、道具 items(62)、NPC 图块(并入 tile 的 `Npc` 段)、地图 maps(5 楼层)、贴图素材 textures(42 张拆分图 + 复制 4 autotile + 1 tileset)。
+
+ ③ **基础字段判定(D-76,逐类):**
+ - **映射候选(可确定)→ 移植:** tile 的 `id` / `cls→type`(§1.1)/ `cannotOut`+`cannotIn`→`pass.outPass`+`pass.inPass`(位掩码取反);enemy 的 `num` / `attribute`(`name`/`hp`/`atk`/`def`/`money`/`exp`/`point`)/ `special`(数字数组→数字键映射);item 的 `num` / `category`(§4.1)/ `name` / `text` / `hideInToolbox`;map 的 `floorId` / `width` / `map` / `layerAlias`。
+ - **§8.2 待定 → 不移植:** tile `trigger` / `canPass`;enemy `value`/`range`/`zoneSquare`/`add`/`n`/`bigImage`/`notBomb`/`atkValue`/`defValue`;item `itemEffect`/`itemEffectTip`/`useItemEffect`/`useItemEvent`/`canUseItemEffect`/`equip` 子字段;map `events` / `bgmap`/`fgmap`/`bg2map`/`fg2map`。
+ - **§8.1 无目标字段 → 只进 unmapped 文档:** tile `canBreak`/`script`/`doorInfo`/`animate`/`name`/`faceIds`/`autotileConnection`;enemy `faceIds`;item `hideInReplay`;map `title`/`name`/`height`/`firstArrive`/`eachArrive`/`parallelDo`/`changeFloor`/`afterBattle`/`afterGetItem`/`afterOpenDoor`/`beforeBattle`/`cannotMove`/`cannotMoveIn`/`autoEvent`/`images`/`bgm`/`weather`/`ratio`/`defaultGround`/`canFlyTo`/`canFlyFrom`/`canUseQuickShop`/`cannotViewMap`/`upFloor`。
+
+ ④ **`num` 策略 A 的具体方案(D-79):** 图块 `num` 全部重新分配,按 `TileType` 分段、段序 `Terrain / Animate / Item / Enemy / Npc / Autotile`,段内按旧 `num` 升序,从 `num=1` 起连续分配(`0` 保留空图块);`enemy`/`item` 的 `num` = 对应图块新 `num`;地图与贴图 `map` 中旧 `num` 经「旧 `num`→`id`→新 `num`」重映射。
+
+ ⑤ **PNG 生成可行性结论:** 仓库无 PNG 库(已核实);采用**零依赖** Node `zlib` 手写 PNG 编解码(源 PNG 均 8 位非隔行,可解码);**不新增依赖**。**待裁定项:** `enemy48`/`npc48` 的 64×48 单元格与 32×32 不同图(U-9),按尺寸分组会使 `npc` 图数由 D-81 的 6 变 7(OI-3,默认 A 方案);`autotile1.png` 类型(U-7)与 `magictower.png` 切格(U-8)暂取候选值(OI-6)。
+
+ ⑥ **地图图层键:** `mapState.fromRaw` 要求每层为整张展平网格(`length = width × height`),与台账 §6.2 候选「层键即行索引」不符;默认「旧二维 `map` 单张展平图层、键 `0`、`layerAlias` = `{}`」,额外图层(`bgmap` 等)属 §8.2 U-16b 不移植(OI-4)。
+
+ ⑦ **产出文件:** `src/content/tile.jsonc` / `enemy.jsonc` / `item.jsonc`(替换样例);`src/content/maps/{empty,MT0,sample0,sample1,sample2}.jsonc` + `maps/.list.jsonc`;`src/content/texture/*.png`(42 拆分图 + 5 复制素材);`src/content/texture/.list.jsonc`;`script/migrate-project-textures.ts`(待批准);`.planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-UNMAPPED.md`(待批准);`09-19-SUMMARY.md`。
+
+ ⑧ **不在本增量(D-70 / D-83):** 不实现消费接线(写 store / 写 `TextureManager`);不改 `packages*/`、`src/`(除 `src/content/**`)、`public/project/**`、`ROADMAP.md`/`STATE.md`;不移植 §8.2 待定与 §8.1 无目标字段。
+
+ ⑨ **验证方式:** 静态门禁——jsonc 可解析(`// --- SYSTEM PREFIX END --- //` 后严格 JSON);tile 235 条且 `id`/`num` 唯一、`type` 合法;enemy 81 / item 62 条且 `num` 存在于 tile 且类型匹配;`texture/.list.jsonc` 每个 `source` 文件存在、`map`/`identifier` 覆盖对应图块 `num`;PNG 尺寸/数量符合 D-72;maps 5 楼层齐备且每层数组长度 = `width`×`height`、数值均为 `0` 或有效 `num`;unmapped 文档存在且仅含 §8.1。**不设全仓类型 / 构建门禁。**
+
+ ⑩ **修改方式声明(AGENTS.md):** 本计划执行含文件写入,由**派发子代理执行**(`gsd-executor`),非内联执行。
+
+ ⑪ **开放项:** OI-1(unmapped 文档路径/命名)、OI-2(脚本路径/命名)、OI-3(48px 合并处理)、OI-4(地图图层键/`layerAlias`)、OI-5(`source` 路径形态)、OI-6(autotile/tileset 参数)、OI-7(写操作方式声明)。
+
+ ⑫ **关卡性质:** **无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者 / 子代理必须停下等待用户明确回复「可以执行」四个字**;回复其它内容视为未通过;若用户就 OI-1..OI-6 给出指定,按指定执行。
+
+ 是否按本计划(09-19)执行「任务 9 · 旧引擎数据基础移植」——以 `num` 策略 A 写入 tile/enemy/item jsonc、按 D-72/D-81 生成贴图素材与 `texture/.list.jsonc`、写入 5 楼层与 `maps/.list.jsonc`、产出 unmapped 说明文档;基础字段全量、§8.2 待定与 §8.1 无目标字段不落数据文件;不实现消费接线
+
+ AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期为**写操作**(新增/替换 `src/content/**` 数据与 PNG、新增脚本与两份文档)。执行期工作树存在用户并发改动(`AGENTS.md` / `09-CONTEXT.md`,D-56),提交时须逐条核对暂存面。行号为规划日实测、可能漂移,门禁以结构断言为准。
+
+
+
+
+
+
+
+ 回复「可以执行」四个字(如需调整 OI-1..OI-6 的处置,请在回复中指明;修订意见请按 action ⑫ 处理)
+
+ node -e "const fs=require('fs');const z=require('zlib');const bad=[];const doc='.planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-AUDIT.md';if(!fs.existsSync(doc))bad.push('missing audit doc');for(const p of ['packages-user/data-common/src/store/types.ts','packages-user/client-base/src/material/types.ts','packages-user/data-state/src/loader/jsoncProcessor.ts','packages-user/data-base/src/map/mapState.ts','public/project/maps.js','public/project/enemys.js','public/project/items.js','src/content/texture/.list.jsonc']){if(!fs.existsSync(p))bad.push('missing ref: '+p)}for(const p of ['.planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-UNMAPPED.md','.planning/phases/09-minimum-browser-runnable/09-19-SUMMARY.md']){if(fs.existsSync(p))bad.push('post-state already exists: '+p)}if(typeof z.inflateSync!=='function'||typeof z.deflateSync!=='function')bad.push('node zlib inflate/deflate unavailable');if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK pre-execution state: audit doc present; unmapped+summary absent; refs present; node zlib available')"
+ non-zero exit, 或 stderr 含 "PRESTATE FAILED"(被移植来源或参考文件缺失,或产出文件已存在,或 Node `zlib` 不可用)
+ 用户在对话中明确回复「可以执行」四个字(人工核对回复原文;用户若指定 OI-1..OI-6 的非默认处置,须在汇报与后续任务中据其指定执行)
+
+
+ - 用户回复中明确包含「可以执行」四个字
+ - 汇报内容涵盖:要解决的问题、六类范围、三类基础字段判定(映射候选可确定 / §8.2 待定 / §8.1 无目标字段)、`num` 策略 A 具体方案、PNG 零依赖可行性结论与 48px 待裁定项、地图图层键口径、产出文件清单、消费接线不在本增量、验证方式(静态门禁,无全仓门禁)、OI-1..OI-7
+ - 汇报中已原样告知:**写操作边界(除 `files_modified` 外不写;不改 `packages*/`、`src/`(除 `src/content/**`)、`public/project/**`、`ROADMAP.md`/`STATE.md`/既有计划与台账)**、**不新增依赖**、**§8.1/§8.2 不落数据文件**、**命名待批准项先入 `open_items`**
+ - 汇报中已原样告知**关卡性质**(必须停下等待,不因触发方式或 `yolo` / `auto_advance` 自动放行)
+
+ 用户明确回复「可以执行」;在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理
+
+
+
+ Task 1: 建立全局 `id`↔`num` 基准并写三类实体 jsonc —— `tile.jsonc`(235)/ `enemy.jsonc`(81)/ `item.jsonc`(62)
+ Task 0 已闭环:用户明确回复「可以执行」四个字(若用户就 OI-1..OI-6 另有指定,按指定执行)。
+ src/content/tile.jsonc, src/content/enemy.jsonc, src/content/item.jsonc
+
+ - .planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-AUDIT.md(§1.1 `cls`→`TileType`、§1.2 tile 字段分类、§1.3 `num`/`id` 全清单、§1.5.1 `id` 全清单、§3.1 enemy 字段、§3.2 enemy 全清单、§4.1 `cls`→`ItemCategory`、§4.2 item 字段、§4.4 item 全清单)
+ - packages-user/data-common/src/store/types.ts:6-63(`TileType`:6 / `PassBit`:27 / `ITilePassData`:38 / `ITileRawData`:47)、:125-213(`ItemCategory`:125 / `IItemRawData`:176)、:319-326(`IEnemyRawData`:319)—— 字段判定唯一依据
+ - src/content/defaults/tile.jsonc、src/content/defaults/enemy.jsonc、src/content/defaults/item.jsonc(必填字段 `@required` 与骨架默认)
+ - src/content/tile.jsonc、src/content/enemy.jsonc、src/content/item.jsonc(现状样例,确认省略策略与 `// Type:` 前缀格式)
+ - public/project/maps.js(`var maps_` 字面对象)、public/project/enemys.js(`var enemys_`)、public/project/items.js(`var items_`)—— 源数据
+ - dev.md(命名规则、`num` 语义);AGENTS.md(不得臆造、命名先报备)
+
+
+ (0) **一次性计算全局 `id`↔`num` 映射(策略 A,D-79):**
+ - 用 `node` 只读求值 `public/project/maps.js` 得到 235 条图块(键=旧 `num`,值含 `cls` / `id`),据 §1.1 得每条的目标 `TileType`(`animates`→`Animate`=3,`terrains`→`Terrain`=2,`autotile`→`Autotile`=7,`items`→`Item`=4,`enemys`+`enemy48`→`Enemy`=5,`npcs`+`npc48`→`Npc`=6)。
+ - 分段顺序 `Terrain → Animate → Item → Enemy → Npc → Autotile`;段内按旧 `num` 升序;从 `num=1` 起**连续**分配(`0` 保留空图块);产出 `oldNum → id → newNum` 与 `id → newNum` 两张表(内存中使用,不写额外文件)。
+ - **硬约束:** 新 `num` 全体唯一、`id` 全体唯一;后续所有文件(含 Task 2 的 `texture/.list.jsonc` `map`、Task 3 的 maps 数值与 enemy/item `num`)必须复用本表。
+
+ (1) **写 `src/content/tile.jsonc`(235 条,替换样例):** 保留既有头部 `// Type:` 注释与 `// --- SYSTEM PREFIX END --- //` 分隔行不动(其后必须是严格 JSON)。每条图块对象字段:
+ - `num` = 上表新 `num`;`id` = 旧 `id`;`type` = 上表 `TileType` 数值;`events` = `{}`;`eventPass` = `false`。
+ - `pass`:默认 `{ "onlyEvents": true, "outPass": 15, "inPass": 15 }`;若旧图块含 `cannotOut`/`cannotIn`(§1.2 映射候选可确定),按「可离开/可进入方向 = 全方向位 & ~(被禁方向位)」计算 `outPass`/`inPass`(方向位 `Up=1`/`Right=2`/`Down=4`/`Left=8`,全方向 = 15)。
+ - **不写** §8.1 字段(`canBreak`/`script`/`doorInfo`/`animate`/`name`/`faceIds`/`autotileConnection`)与 §8.2 字段(`trigger`/`canPass`);不确定的 `pass` 取值沿用默认并在 SUMMARY 记 `open_items`。
+
+ (2) **写 `src/content/enemy.jsonc`(81 条,替换样例):** 保留头部注释与分隔行。每条:
+ - `num` = 该怪物图块 `id`(如 `greenSlime`)在 (0) 表中的新 `num`(即 tile.jsonc 中 `Enemy` 类图块的 `num`)。
+ - `attribute` = `{ "name": <旧 name>, "hp": <旧 hp>, "atk": <旧 atk>, "def": <旧 def>, "money": <旧 money>, "exp": <旧 exp>, "point": <旧 point> }`(缺省按 `defaults/enemy.jsonc` 的 0 骨架)。
+ - `special` = 由旧 `special` 数字数组生成的对象,键为数组中每个数字、值为 `null`(如 `[16,18]` → `{ "16": null, "18": null }`;空数组 → `{}`)。
+ - **不写** §8.2 字段(`value`/`range`/`zoneSquare`/`add`/`n`/`bigImage`/`notBomb`/`atkValue`/`defValue`)与 §8.1 字段(`faceIds`)。
+
+ (3) **写 `src/content/item.jsonc`(62 条,替换样例):** 保留头部注释与分隔行。每条:
+ - `num` = 该道具图块 `id`(如 `yellowKey` / `I331`)在 (0) 表中的新 `num`(即 tile.jsonc 中 `Item` 类图块的 `num`)。
+ - `category` 按 §4.1:`tools`→`Consumable`=2、`items`→`Consumable`=2、`equips`→`Equipment`=4、`constants`→`Constant`=1(剑盾 `items` 类分类待确认,默认 `Consumable`,记 `open_items`)。
+ - `name` / `text` / `hideInToolbox`(缺省 `false`)按旧值直接对应;保留 `hideInToolbox` 的 `true`(5 条)。
+ - **不写** §8.2 字段(`itemEffect`/`itemEffectTip`/`useItemEffect`/`useItemEvent`/`canUseItemEffect`)与 `equip` 子字段值、§8.1 字段(`hideInReplay`);沿用现有样例的**省略策略**(`effect`/`equip` 省略,由默认合并兜底),在 SUMMARY 记录该策略与 `open_items`。
+
+ (4) **收口:** 三份 jsonc 的 `// --- SYSTEM PREFIX END --- //` 之后必须是严格 JSON(无注释、无尾逗号);文件头 `// Type:` 前缀行不得改动;不改任何其它文件;`num`/`id` 全局唯一且五处基准可复用。
+
+ 替换 `src/content` 三份样例数据文件;`git checkout` / 重新生成可整体回退;未触及任何源码与既有注释。
+
+ node -e "const fs=require('fs');const root='src/content/';const read=p=>{const t=fs.readFileSync(root+p,'utf8');const parts=t.split('// --- SYSTEM PREFIX END --- //');if(parts.length!==2)throw new Error(p+' :: missing/duplicated SYSTEM PREFIX END marker');return JSON.parse(parts[1])};const bad=[];let tile,enemy,item;try{tile=read('tile.jsonc')}catch(e){bad.push('tile.jsonc: '+e.message)}try{enemy=read('enemy.jsonc')}catch(e){bad.push('enemy.jsonc: '+e.message)}try{item=read('item.jsonc')}catch(e){bad.push('item.jsonc: '+e.message)}const numToId=new Map(),idSet=new Set();if(tile){if(!Array.isArray(tile))bad.push('tile.jsonc not an array');else{if(tile.length!==235)bad.push('tile count '+tile.length+' != 235');for(const t of tile){const keys=Object.keys(t).sort().join(',');if(keys!=='eventPass,events,id,num,pass,type')bad.push('tile '+t.id+' unexpected keys: '+keys);if(![2,3,4,5,6,7].includes(t.type))bad.push('tile '+t.id+' bad type '+t.type);if(numToId.has(t.num))bad.push('dup num '+t.num);if(idSet.has(t.id))bad.push('dup id '+t.id);numToId.set(t.num,t.id);idSet.add(t.id);if(!t.pass||typeof t.pass.outPass!=='number'||typeof t.pass.inPass!=='number')bad.push('tile '+t.id+' bad pass')}}}const enemyTiles=new Set(),itemTiles=new Set();if(tile)for(const t of tile){if(t.type===5)enemyTiles.add(t.num);if(t.type===4)itemTiles.add(t.num)}if(enemy){if(!Array.isArray(enemy)||enemy.length!==81)bad.push('enemy count '+(enemy?enemy.length:'?')+' != 81');else for(const e of enemy){if(!enemyTiles.has(e.num))bad.push('enemy num '+e.num+' not an Enemy tile');if(!e.attribute||typeof e.attribute.name!=='string'||typeof e.attribute.hp!=='number')bad.push('enemy '+e.num+' bad attribute');if(typeof e.special!=='object'||e.special===null||Array.isArray(e.special))bad.push('enemy '+e.num+' special not an object')}}if(item){if(!Array.isArray(item)||item.length!==62)bad.push('item count '+(item?item.length:'?')+' != 62');else for(const it of item){if(!itemTiles.has(it.num))bad.push('item num '+it.num+' not an Item tile');if(typeof it.name!=='string'||typeof it.text!=='string')bad.push('item '+it.num+' bad name/text');if(![0,1,2,3,4].includes(it.category))bad.push('item '+it.num+' bad category')}}if(bad.length>0){console.error('ENTITY GATE FAILED :: '+bad.slice(0,20).join(' ; '));process.exit(1)}console.log('OK entities: tile=235 unique id/num, type valid; enemy=81 num in Enemy tiles; item=62 num in Item tiles')"
+ non-zero exit, 或 stderr 含 "ENTITY GATE FAILED"(jsonc 缺分隔标记 / 解析失败 / tile≠235 或字段集不符 / `id`/`num` 重复 / `type` 非法 / enemy≠81 或 `num` 非 `Enemy` 图块 / enemy `attribute`/`special` 形状错误 / item≠62 或 `num` 非 `Item` 图块 / item `name`/`text`/`category` 错误)
+ node -e "const fs=require('fs');const t=fs.readFileSync('src/content/tile.jsonc','utf8');const lines=t.split(/\r?\n/);const i=lines.findIndex(l=>l.includes('SYSTEM PREFIX END'));if(i<1||!lines.slice(0,i).some(l=>l.startsWith('// Type:'))){console.error('PREFIX GATE FAILED :: tile.jsonc missing // Type: header before marker');process.exit(1)}console.log('OK tile.jsonc retains header // Type: line before SYSTEM PREFIX END')"
+ non-zero exit, 或 stderr 含 "PREFIX GATE FAILED"(`tile.jsonc` 头部 `// Type:` 注释行被删除或分隔标记缺失)
+
+
+ - `tile.jsonc` 恰 235 条;每条键集恰为 `num`/`id`/`events`/`type`/`eventPass`/`pass`;`num` 与 `id` 各自唯一;`type` ∈ {2,3,4,5,6,7};含 `cannotOut`/`cannotIn` 的图块其 `pass.outPass`/`inPass` 按位掩码取反
+ - `enemy.jsonc` 恰 81 条;每条 `num` 均为 tile 中 `Enemy`(type=5)图块的 `num`;`attribute` 七字段齐备;`special` 为对象(键=旧数字数组元素,值 `null`)
+ - `item.jsonc` 恰 62 条;每条 `num` 均为 tile 中 `Item`(type=4)图块的 `num`;`category` 按 §4.1;`name`/`text`/`hideInToolbox` 就位
+ - 三份 jsonc 分隔标记后为严格 JSON;`// Type:` 头部注释行保留;未写任何 §8.1/§8.2 字段;未改任何其它文件
+
+ 三份实体 jsonc 落盘且门禁全绿;全局 `oldNum→id→newNum` 表建立并唯一一致
+
+
+
+ Task 2: 一次性零依赖 PNG 脚本 + 按 D-72 生成贴图素材 + 写 `texture/.list.jsonc`
+ Task 1 已完成(`tile.jsonc` 235 条与全局 `id`↔`num` 基准就位,可被本任务复用)。
+ script/migrate-project-textures.ts, src/content/texture/.list.jsonc, src/content/texture/*.png
+
+ - .planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-AUDIT.md(§0.5 D-72 五规则 + tilesets/autotiles 例外、§0.5.1 `animates` 并入 `terrains`、§1.4 图块→贴图归属、§2.1 `materials` 尺寸、§2.2 autotiles、§2.3 tilesets、§2.4 拆分方案、§2.5 命名、§7 / §7.1 schema 与类型)
+ - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-72 / D-73 / D-80 / D-81 / D-83)
+ - packages-user/client-base/src/material/types.ts:12-17(`AutotileType`)、:50-55(`IIndexedIdentifier`)、:261-307(`addGrid`/`addRowAnimate`/`addAutotile`/`addTileset`)—— list schema 能力依据
+ - src/content/client.jsonc:9(`textureListDir: texture/.list.jsonc`、`textureDir: texture`)、src/content/texture/.list.jsonc(现状 `{}`)—— 落点与路径形态参考
+ - src/content/tile.jsonc(Task 1 产出:新 `num` 与 `id`)
+ - public/project/materials/*.png、public/project/autotiles/*.png、public/project/tilesets/magictower.png —— 源素材
+ - dev.md(命名规则:代码文件小驼峰、常量全大写、模块无副作用);AGENTS.md(不新增依赖、命名先报备)
+
+
+ (1) **写一次性 PNG 脚本 `script/migrate-project-textures.ts`(路径/命名待批准,OI-2):** 使用 **Node 内置 `zlib`**(`inflateSync`/`deflateSync`)+ `node:fs`,**零外部依赖**:
+ - 解码:解析 PNG 签名与 `IHDR`(宽=偏移 16 起 4 字节大端、高=偏移 20 起 4 字节大端、位深=`b[24]`、颜色类型=`b[25]`、隔行=`b[28]`);拼接全部 `IDAT` 后 `zlib.inflateSync`;按行反滤镜(filter 类型 0–4);支持颜色类型 6(8 位 RGBA,bpp=4)与颜色类型 3(8 位调色板 + `PLTE`/`tRNS`,先展开为 RGBA);**断言隔行=0**(源素材均非隔行)。
+ - 编码:写 8 位 RGBA、非隔行 PNG(`IHDR` + 每行 filter 0 + `zlib.deflateSync`(level 9)的 `IDAT` + `IEND`,各块 CRC32 自实现)。
+ - 该脚本一次性运行生成素材,不纳入构建;脚本不含导出变量、顶层不使用 `try-catch`(dev.md)。
+ - 读取源图:`public/project/materials/{terrains,animates,items,enemys,enemy48,npcs,npc48}.png`。
+ - 生成 A 组 `terrains` 单帧:源 `terrains.png`(32×1120,1 列 × 35 行,每格 32×32)→ 依序填入 4×4 网格,每图 16 格,共 3 图 `terrains0..2.png`(末图剩余格透明填充)。
+ - 生成 B 组(`animates` 并入 `terrains`,D-72③):源 `animates.png`(128×928,4 帧列 × 29 行,每格帧 32×32)→ 每图 4 行(每行 4 帧列),共 8 图 `terrainsAnim0..7.png`(尺寸 128×128)。
+ - 生成 C 组 `items` 单帧:源 `items.png`(32×1984,1 列 × 62 行,每格 32×32)→ 4×4 网格,共 4 图 `items0..3.png`。
+ - 生成 D 组 `enemy`(合并 `enemys`+`enemy48`,D-72①):源 `enemys.png`(64×2336,2 帧列 × 73 行,每格帧 32×32)与 `enemy48.png`(128×384,2 帧列 × 8 行,每格帧 64×48);多帧 → 每图 4 行、每行 2 帧列;**按单元格尺寸分组**(默认 OI-3 A 方案):32px 组 73 个 → 19 图、48px 组 8 个 → 2 图,共 21 图 `enemy0..20.png`(若用户在 Task 0 选 B/C 方案,按其指定处理)。
+ - 生成 E 组 `npc`(合并 `npcs`+`npc48`,D-72①):源 `npcs.png`(64×448,2 帧列 × 14 行,每格帧 32×32)与 `npc48.png`(128×480,2 帧列 × 10 行,每格帧 64×48);按尺寸分组:32px 组 14 → 4 图、48px 组 10 → 3 图,共 7 图 `npc0..6.png`(默认 OI-3 A 方案;此偏离 D-81 的 6 张,须在 SUMMARY 记录;若用户选 B/C 按指定处理)。
+ - 复制(逐字节,不拆分):`public/project/autotiles/{autotile,autotile1,autotile2,autotile3}.png` → `src/content/texture/` 同名;`public/project/tilesets/magictower.png` → `src/content/texture/magictower.png`。
+ - 生成后断言:每张产出图 `width`/`height` 与预期一致;`materials` 拆分图总格数 ≥ 源图块数。
+
+ (2) **写 `src/content/texture/.list.jsonc`(D-80 已批准 schema):** 保留头部注释与分隔行;`// --- SYSTEM PREFIX END --- //` 后为严格 JSON 对象 `{ "textures": [ ... ] }`,条目:
+ - A/C 组 `{ "type": "grid", "source": "<文件名>", "map": [<新 num…>], "width": 32, "height": 32 }`。
+ - B/D/E 组 `{ "type": "rowAnimate", "source": "<文件名>", "map": [<新 num…>], "width": <帧宽>, "height": <帧高> }`。
+ - autotile `{ "type": "autotile", "source": "autotile*.png", "identifier": <20|151|152|153 对应新 num>, "autotileType": }`(参数名用已批准的 `autotileType`;默认 `autotile.png`/`autotile2.png`/`autotile3.png` 取 `Big3x4`=1,`autotile1.png` 暂取 `Big3x4`=1,OI-6)。
+ - tileset `{ "type": "tileset", "source": "magictower.png", "identifier": { "index": 0, "alias": "magictower" }, "cellWidth": 32, "cellHeight": 32 }`(默认候选,OI-6)。
+ - `source` 默认用相对 `texture/` 的文件名(OI-5);`map` 中每个 `num` 必须来自 Task 1 的 `tile.jsonc`;`map` 的并集覆盖所有需要贴图的图块(`Terrain`/`Animate`/`Item`/`Enemy`/`Npc`),autotile 由 `identifier` 覆盖,tileset 由条目覆盖。
+
+ (3) **收口:** 不写 §8.2/§8.1 内容;不改 `client.jsonc`/`defaults/**`;不新增依赖、不改 `package.json`;脚本运行后 `src/content/texture/` 内文件与 `.list.jsonc` 互一致。
+
+ 新增脚本 + 生成 `src/content/texture/**`;可删除生成物或 `git checkout` 回退;未触及任何既有源码与注释。
+
+ node -e "const fs=require('fs');const dir='src/content/texture/';function ihdr(f){const b=fs.readFileSync(dir+f);if(b.readUInt32BE(0)!==0x89504e47)throw new Error(f+' not PNG');const w=b.readUInt32BE(16),h=b.readUInt32BE(20),bd=b[24],il=b[28];if(il!==0)throw new Error(f+' interlaced');if(bd!==8)throw new Error(f+' bitDepth '+bd);return{w,h}}const bad=[];const expect={};for(let i=0;i<3;i++)expect['terrains'+i+'.png']={w:128,h:128};for(let i=0;i<8;i++)expect['terrainsAnim'+i+'.png']={w:128,h:128};for(let i=0;i<4;i++)expect['items'+i+'.png']={w:128,h:128};const tiles=JSON.parse(fs.readFileSync('src/content/tile.jsonc','utf8').split('// --- SYSTEM PREFIX END --- //')[1]);const numSet=new Set(tiles.map(x=>x.num));for(const f of Object.keys(expect)){if(!fs.existsSync(dir+f)){bad.push('missing '+f);continue}const m=ihdr(f);if(m.w!==expect[f].w||m.h!==expect[f].h)bad.push(f+' size '+m.w+'x'+m.h+' != '+expect[f].w+'x'+expect[f].h)}let list;try{const raw=fs.readFileSync(dir+'.list.jsonc','utf8');const parts=raw.split('// --- SYSTEM PREFIX END --- //');if(parts.length!==2)throw new Error('bad marker');list=JSON.parse(parts[1])}catch(e){bad.push('.list.jsonc: '+e.message)}if(list){if(!Array.isArray(list.textures))bad.push('textures not array');else for(const e of list.textures){if(!e||typeof e.source!=='string'){bad.push('entry bad source');continue}if(!fs.existsSync(dir+e.source))bad.push('source missing on disk: '+e.source);if(e.type==='grid'||e.type==='rowAnimate'){if(!Array.isArray(e.map))bad.push(e.source+' map not array');else for(const n of e.map){if(!numSet.has(n))bad.push(e.source+' map num not in tile.jsonc: '+n)}}else if(e.type==='autotile'){if(typeof e.identifier!=='number'||!numSet.has(e.identifier))bad.push(e.source+' identifier not in tile.jsonc');if(typeof e.autotileType!=='number')bad.push(e.source+' autotileType not number')}else if(e.type==='tileset'){if(!e.identifier||typeof e.identifier.alias!=='string')bad.push(e.source+' identifier bad')}else bad.push('unknown entry type '+e.type)}}if(bad.length>0){console.error('TEXTURE GATE FAILED :: '+bad.slice(0,20).join(' ; '));process.exit(1)}console.log('OK textures: PNG headers valid & sized; .list.jsonc parses; sources exist; map/identifier nums all in tile.jsonc')"
+ non-zero exit, 或 stderr 含 "TEXTURE GATE FAILED"(PNG 缺失/非 PNG/隔行/位深≠8/尺寸不符预期 / `.list.jsonc` 标记缺失或解析失败 / `textures` 非数组 / 条目 `type` 未知 / `source` 文件在磁盘不存在 / `map` 或 autotile `identifier` 的 `num` 不在 `tile.jsonc` / `autotileType` 非数值 / tileset `identifier.alias` 缺失)
+ node -e "const fs=require('fs');const dir='src/content/texture/';const need=['autotile.png','autotile1.png','autotile2.png','autotile3.png','magictower.png'];const bad=[];for(const f of need){if(!fs.existsSync(dir+f))bad.push('missing copied '+f)}const a=fs.readFileSync('public/project/autotiles/autotile.png');const b=fs.existsSync(dir+'autotile.png')?fs.readFileSync(dir+'autotile.png'):null;if(b&&!a.equals(b))bad.push('autotile.png not byte-identical');const ta=fs.readFileSync('public/project/tilesets/magictower.png');const tb=fs.existsSync(dir+'magictower.png')?fs.readFileSync(dir+'magictower.png'):null;if(tb&&!ta.equals(tb))bad.push('magictower.png not byte-identical');if(bad.length>0){console.error('COPY GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK autotiles (4) + tileset (1) copied byte-identical (not re-encoded)')"
+ non-zero exit, 或 stderr 含 "COPY GATE FAILED"(autotile/tileset 复制缺失,或未逐字节复制而重编码)
+ node -e "const fs=require('fs');const p='script/migrate-project-textures.ts';const bad=[];if(!fs.existsSync(p))bad.push('script missing');else{const t=fs.readFileSync(p,'utf8');if(!/node:zlib|require\(['\\u0022]zlib['\\u0022]\)/.test(t))bad.push('script does not use node zlib');if(/from ['\\u0022](pngjs|sharp|canvas|jimp)/.test(t))bad.push('script imports a forbidden png lib')}const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));const all=Object.assign({},pkg.dependencies||{},pkg.devDependencies||{});for(const n of ['pngjs','sharp','canvas','jimp'])if(all[n])bad.push('dependency added: '+n);if(bad.length>0){console.error('DEP GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK zero-dependency: script uses node zlib; no png lib added to package.json')"
+ non-zero exit, 或 stderr 含 "DEP GATE FAILED"(脚本缺失 / 未使用 Node `zlib` / 脚本引入被禁 PNG 库 / `package.json` 新增了 PNG 依赖)
+
+
+ - `script/migrate-project-textures.ts` 存在,仅用 Node `zlib` + `fs`(零外部依赖),未改 `package.json`
+ - `src/content/texture/` 含 42 张拆分图(`terrains0..2` / `terrainsAnim0..7` / `items0..3` / `enemy0..20` / `npc0..6`)与 5 张逐字节复制素材;每张 PNG 为非隔行 8 位、尺寸符合 D-72 方案(默认 A:`npc` 为 7 张,偏差记入 SUMMARY)
+ - `texture/.list.jsonc` 为 D-80 已批准 schema(`textures` 数组,条目 `type` ∈ {`grid`,`rowAnimate`,`autotile`,`tileset`});每个 `source` 文件存在;`map`/`identifier` 的 `num` 全部来自 `tile.jsonc`
+ - 分隔标记后为严格 JSON;头部注释保留;未写 §8.1/§8.2 内容
+
+ 42 张拆分图 + 5 张复制素材生成;`texture/.list.jsonc` 落盘且 `source`/`map`/`identifier` 与 `tile.jsonc` 一致;零依赖门禁全绿
+
+
+
+ Task 3: 写 5 楼层地图 + `maps/.list.jsonc` + `09-DATA-MIGRATION-UNMAPPED.md`(仅 §8.1),跑最终门禁并写 SUMMARY
+ Task 1 / Task 2 已完成(全局 `id`↔`num` 基准与 `tile.jsonc` 就位,供地图旧 `num` 重映射)。
+ src/content/maps/empty.jsonc, src/content/maps/MT0.jsonc, src/content/maps/sample0.jsonc, src/content/maps/sample1.jsonc, src/content/maps/sample2.jsonc, src/content/maps/.list.jsonc, .planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-UNMAPPED.md, .planning/phases/09-minimum-browser-runnable/09-19-SUMMARY.md
+
+ - .planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-AUDIT.md(§6.1 楼层登记、§6.2 map 字段分类、§8.1 U-16 无目标字段清单、§8.2 U-15/U-16b 待定)
+ - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-76 / D-78 / D-82 / D-83)
+ - packages-user/data-common/src/store/types.ts:260-271(`IMapRawData`);packages-user/data-base/src/map/mapState.ts:40-95(`fromRaw`:各层等长、`length % width === 0`)
+ - src/content/defaults/map.jsonc(`@required` = `floorId`;`map` / `layerAlias` / `events` 骨架);src/content/maps/MT0.jsonc(现状样例);src/content/maps/.list.jsonc(现状 `["MT0"]`)
+ - src/content/tile.jsonc(Task 1 产出:新 `num` 与 `id`,用于旧→新重映射)
+ - public/project/floors/{empty,MT0,sample0,sample1,sample2}.js —— 源楼层
+ - dev.md(Markdown 连字符命名、注释规范);AGENTS.md(不臆造、命名先报备)
+
+
+ (1) **写 5 份楼层 `src/content/maps/{empty,MT0,sample0,sample1,sample2}.jsonc`(D-82):** 保留头部 `// Type:` 注释与分隔行;`// --- SYSTEM PREFIX END --- //` 后为严格 JSON `IMapRawData`:
+ - `floorId` = 楼层 id;`width` = 旧 `width`(13 或 26)。
+ - `map`:把旧二维 `map`(`[row][col]`)按**行主序展平**为**一张图层数组**(`length = width × height`),图层键默认 `0`(OI-4);数组内每个值经「旧 `num`→`id`→新 `num`」重映射(`0` 保持 `0`)。
+ - `layerAlias` = `{}`(默认);`events` = `{}`(§8.2 U-15 不移植)。
+ - **不写** §8.2 的 `bgmap`/`fgmap`/`bg2map`/`fg2map`(U-16b)与 §8.1 的 `title`/`name`/`height`/`firstArrive`/`eachArrive`/`parallelDo`/`changeFloor`/`afterBattle`/`afterGetItem`/`afterOpenDoor`/`beforeBattle`/`cannotMove`/`cannotMoveIn`/`autoEvent`/`images`/`bgm`/`weather`/`ratio`/`defaultGround`/`canFlyTo`/`canFlyFrom`/`canUseQuickShop`/`cannotViewMap`/`upFloor`。
+ - **硬约束:** 每个图层数组长度必须 = `width × height`(`mapState.fromRaw` 要求 `length % width === 0` 且各层等长)。
+
+ (2) **写 `src/content/maps/.list.jsonc`:** 保留头部注释与分隔行;`// --- SYSTEM PREFIX END --- //` 后为严格 JSON 字符串数组,含 5 个 id:`["empty","MT0","sample0","sample1","sample2"]`(不带 `.jsonc` 后缀)。
+
+ (3) **写 `09-DATA-MIGRATION-UNMAPPED.md`(路径/命名待批准,OI-1;仅 §8.1):** 采用连字符命名、裸 LF。内容仅收录「无目标字段 → 只记录、不移植」项,按维度分组并附「所查接口与缺失字段」依据(引用 `09-DATA-MIGRATION-AUDIT.md` §8.1):tile(`canBreak`/`script`/`doorInfo`/`animate`/`name`/`faceIds`/`autotileConnection`)、enemy(`faceIds`)、item(`hideInReplay`)、map(`title`/`name`/`height`/`firstArrive`/`eachArrive`/`parallelDo`/`changeFloor`/`afterBattle`/`afterGetItem`/`afterOpenDoor`/`beforeBattle`/`cannotMove`/`cannotMoveIn`/`autoEvent`/`images`/`bgm`/`weather`/`ratio`/`defaultGround`/`canFlyTo`/`canFlyFrom`/`canUseQuickShop`/`cannotViewMap`/`upFloor`)。**不得**收录 §8.2 待定项——它们保留在盘点台账。
+
+ (4) **写 `09-19-SUMMARY.md`:** 记录本计划范围与逐类映射口径、`num` 策略 A 的分段/取值、素材产出(42 拆分图 + 5 复制 + `npc` 计数偏差)、地图图层键与旧→新重映射口径、消费接线未实现(D-70/D-83)、门禁结果、`open_items`(OI-1..OI-7 的最终处置)。
+
+ (5) **收口:** 不改任何源码 / `public/project/**` / `ROADMAP.md` / `STATE.md` / 既有台账与计划;提交前 `git status` 逐条核对,仅暂存本计划 `files_modified` 条目(避开用户并发改动 D-56)。
+
+ 新增/替换 `src/content/maps/**` 与两份文档;`git checkout` / 删除可回退;未触及任何既有源码与注释。
+
+ node -e "const fs=require('fs');const dir='src/content/maps/';const bad=[];const tiles=JSON.parse(fs.readFileSync('src/content/tile.jsonc','utf8').split('// --- SYSTEM PREFIX END --- //')[1]);const numSet=new Set(tiles.map(x=>x.num));const ids=['empty','MT0','sample0','sample1','sample2'];for(const id of ids){const p=dir+id+'.jsonc';if(!fs.existsSync(p)){bad.push('missing '+id+'.jsonc');continue}const parts=fs.readFileSync(p,'utf8').split('// --- SYSTEM PREFIX END --- //');if(parts.length!==2){bad.push(id+': marker');continue}let m;try{m=JSON.parse(parts[1])}catch(e){bad.push(id+': parse '+e.message);continue}if(m.floorId!==id)bad.push(id+': floorId '+m.floorId);if(typeof m.width!=='number')bad.push(id+': width');if(typeof m.layerAlias!=='object')bad.push(id+': layerAlias');if(typeof m.events!=='object')bad.push(id+': events');const keys=Object.keys(m.map||{});if(keys.length<1)bad.push(id+': no map layers');for(const k of keys){const a=m.map[k];if(!Array.isArray(a)){bad.push(id+': layer '+k+' not array');continue}if(a.length%m.width!==0)bad.push(id+': layer '+k+' length '+a.length+' % width '+m.width+' != 0');for(const v of a){if(v!==0&&!numSet.has(v)){bad.push(id+': value '+v+' not a tile num');break}}}}let list;try{list=JSON.parse(fs.readFileSync(dir+'.list.jsonc','utf8').split('// --- SYSTEM PREFIX END --- //')[1])}catch(e){bad.push('.list.jsonc: '+e.message)}if(list&&(!Array.isArray(list)||list.length!==5||!ids.every(x=>list.includes(x))))bad.push('.list.jsonc not the 5 floor ids');const up='.planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-UNMAPPED.md';if(!fs.existsSync(up))bad.push('unmapped doc missing');else{const u=fs.readFileSync(up,'utf8');for(const tok of ['doorInfo','faceIds','hideInReplay','firstArrive','changeFloor','defaultGround']){if(u.indexOf(tok)<0)bad.push('unmapped missing section-8.1 token: '+tok)}for(const tok of ['canPass','zoneSquare','itemEffect','bgmap']){if(u.indexOf(tok)>=0)bad.push('unmapped contains a section-8.2 (deferred) token: '+tok)}}if(!fs.existsSync('.planning/phases/09-minimum-browser-runnable/09-19-SUMMARY.md'))bad.push('summary missing');if(bad.length>0){console.error('MAP GATE FAILED :: '+bad.slice(0,20).join(' ; '));process.exit(1)}console.log('OK maps: 5 floors; every layer length % width === 0; all values 0 or valid tile num; .list.jsonc=5 ids; unmapped doc has only section-8.1 fields; summary present')"
+ non-zero exit, 或 stderr 含 "MAP GATE FAILED"(楼层文件缺失/标记或解析失败 / `floorId` 不符 / `map` 无图层或图层非数组 / 图层长度不能被 `width` 整除 / 数值非 `0` 且非有效 `tile` `num` / `.list.jsonc` 非 5 个 id / unmapped 文档缺失或含 §8.2 项 / SUMMARY 缺失)
+ node -e "const {execSync}=require('child_process');const status=execSync('git status --porcelain',{encoding:'utf8'}).split(/\r?\n/).filter(Boolean);const bad=[];for(const l of status){const p=l.slice(3).trim().replace(/^\u0022|\u0022$/g,'');if(p==='AGENTS.md'||p==='.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md')continue;if(p==='ROADMAP.md'||p.endsWith('/ROADMAP.md')||p.endsWith('STATE.md')||p.endsWith('/09-DATA-MIGRATION-AUDIT.md'))bad.push('forbidden planning edit: '+p);else if(p.startsWith('packages/')||p.startsWith('packages-user/')||p.startsWith('public/project/'))bad.push(p);else if(p.startsWith('src/')&&!p.startsWith('src/content/'))bad.push(p);else if(/\.(ts|tsx)$/.test(p)&&p!=='script/migrate-project-textures.ts'&&!p.startsWith('src/content/'))bad.push(p)}if(bad.length>0){console.error('SCOPE GATE FAILED :: out-of-scope changes :: '+bad.join(' | '));process.exit(1)}console.log('OK scope: only src/content/** + migration script + planning docs changed (user concurrent changes ignored)')"
+ non-zero exit, 或 stderr 含 "SCOPE GATE FAILED"(本计划执行期改动了 `packages*/`、`src/`(除 `src/content/**`)、`public/project/**`、`ROADMAP.md`/`STATE.md`/盘点台账,或新增了 `files_modified` 外的 `.ts`/`.tsx`)
+
+
+ - 5 份楼层 jsonc 齐备(`empty`/`MT0`/`sample0`/`sample1`/`sample2`),`floorId`/`width`/`map`/`layerAlias`/`events` 就位;每个图层数组长度 = `width`×`height`;数值均为 `0` 或有效新 `num`
+ - `maps/.list.jsonc` 恰 5 个 id;旧 `num` 已重映射为新 `num`
+ - `09-DATA-MIGRATION-UNMAPPED.md` 存在且仅含 §8.1 项(含 tile/enemy/item/map 四组,含 `src`/接口依据),不含 §8.2 项
+ - `09-19-SUMMARY.md` 已写(含范围、`num` 分配、素材产出、地图口径、门禁、`open_items`)
+ - 未改任何源码 / `public/project/**` / 既有台账与计划;未写 §8.2/§8.1 进数据文件
+
+ 5 楼层 + `maps/.list.jsonc` + unmapped 文档 + SUMMARY 落盘;地图/作用域门禁全绿
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| 写入边界 | 本增量是写操作;只允许写 `files_modified` 白名单(`src/content/**` 数据与 PNG + 迁移脚本 + 两份规划文档) |
+| 范围边界 | 不得改 `packages*/`、`src/`(除 `src/content/**`)、`public/project/**`、`ROADMAP.md`/`STATE.md`/既有台账与计划 |
+| 判定边界 | §8.2「待定」与 §8.1「无目标字段」均不得落入数据文件;§8.1 只入 unmapped 文档,§8.2 只留台账 |
+| `num` 一致性边界 | `id`↔`num` 跨 `tile`/`enemy`/`item`/`texture.list`/`maps` 五处必须一致(D-79) |
+| 依赖边界 | 不得新增依赖;PNG 生成须零依赖 Node `zlib` |
+| 命名边界 | 未批准命名(脚本路径、unmapped 文档路径、`source` 形态、图层键、48px 方案)先入 `open_items` 待批准(D-73 / AGENTS.md) |
+| 提交边界 | 不得把用户并发改动(D-56,`AGENTS.md` / `09-CONTEXT.md`)纳入暂存 / 提交 |
+
+**威胁级别说明(security_enforcement = ASVS L1,block on high):** 本计划为**本地数据/素材移植**,不执行第三方代码、不新增依赖、不引入网络面,**不存在 `high` 级威胁**;下列寄存器均为 `medium` 及以下,并逐条给出缓解。
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-09-19-01 | Tampering | 越界写入生产源码 / 数据(`packages*/`、`src/`(除 `src/content/**`)、`public/project/**`) | medium | mitigate | `files_modified` 白名单 + prohibitions + Task 3 `SCOPE GATE`(`git status` 逐条校验);Task 2 `DEP GATE` 校验 `package.json` 未新增依赖 |
+| T-09-19-02 | Tampering | 移植了 §8.2「待定」或 §8.1「无目标字段」,或 unmapped 文档混入 §8.2 | medium | mitigate | Task 1 门禁校验 tile 键集恰为 6 键(挡 §8.1/§8.2 字段);Task 3 门禁校验 unmapped 文档含 §8.1 token 且不含 §8.2 token |
+| T-09-19-03 | Tampering | `id`↔`num` 跨文件不一致(贴图 `map`/地图数值/实体 `num` 失配) | medium | mitigate | Task 1 建立唯一基准表;Task 2 门禁校验 `list` 的 `map`/`identifier` `num` 均在 `tile.jsonc`;Task 3 门禁校验地图数值均为 `0` 或有效 `num` |
+| T-09-19-04 | Tampering | 生成 PNG 尺寸 / 数量偏离 D-72 方案或损坏(编造像素) | medium | mitigate | Task 2 `TEXTURE GATE` 校验每张 PNG 为非隔行 8 位且尺寸符合预期;`COPY GATE` 校验 autotile/tileset 逐字节复制 |
+| T-09-19-05 | Tampering | 地图结构非法(图层长度非 `width×height` 倍数、含越界 `num`) | medium | mitigate | Task 3 `MAP GATE` 校验每层 `length % width === 0` 且数值 ∈ {0} ∪ `tile` nums |
+| T-09-19-06 | Elevation of Privilege | 未经批准即执行 / 使用未报备命名 / 新增依赖 | medium | mitigate | Task 0 `blocking-human`;命名候选入 `open_items`;Task 2 `DEP GATE` 禁依赖新增 |
+| T-09-19-07 | Tampering | 破坏加载:`// --- SYSTEM PREFIX END --- //` 之后写入注释 / 尾逗号(`JSON.parse` 失败) | medium | mitigate | 各门禁均以 `split(marker)` 后 `JSON.parse` 校验;prohibitions 明列严格 JSON |
+| T-09-19-08 | Information Disclosure | 改动既有 `// Type:` 头部注释 / 既有注释(AGENTS.md 禁改既有注释) | low | mitigate | Task 1 保留头部门禁;prohibitions 明列不改 `// Type:` 前缀与既有注释 |
+| T-09-19-09 | Repudiation | 未跑门禁即收口 / 未记录 TWO PASS 结果与 `open_items` | low | mitigate | Task 1/2/3 各有自动化门禁 + `09-19-SUMMARY.md` 记录范围与 `open_items` |
+| T-09-19-SC | Tampering | npm/pip/cargo installs | high | mitigate | 本增量**不新增依赖**;Task 2 `DEP GATE` 阻断任何 PNG 依赖写入 `package.json`;如需新增依赖须回 Task 0 报备获批 |
+
+
+
+- **Task 0**:预执行状态(台账 + 类型/加载/地图/源数据 + `node zlib` 就位;unmapped/summary 不存在);人工关卡(用户明确回复「可以执行」)。
+- **Task 1**:三份实体 jsonc 分隔标记后严格 JSON;tile 235 且 `id`/`num` 唯一、`type` ∈ {2,3,4,5,6,7}、键集恰 6 键;enemy 81 且 `num` ∈ `Enemy` 图块、`attribute`/`special` 形状正确;item 62 且 `num` ∈ `Item` 图块;头部 `// Type:` 保留。
+- **Task 2**:42 张拆分图 + 复制素材存在、PNG `IHDR` 合法(8 位非隔行)、尺寸符合 D-72;`texture/.list.jsonc` 解析成功、`source` 存在、`map`/`identifier` `num` 均在 `tile.jsonc`;autotile/tileset 逐字节复制;脚本零依赖、`package.json` 未变。
+- **Task 3**:5 楼层齐备、每层长度 = `width×height`、数值 ∈ {0} ∪ `tile` nums;`maps/.list.jsonc` = 5 id;unmapped 文档仅含 §8.1;SUMMARY 存在;`SCOPE GATE` 无越界改动。
+- **一致性**:五处 `id`↔`num` 全局一致(D-79);maps 5 楼层;unmapped 文档只含 §8.1。
+- **无全仓门禁**:全程不使用 `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数。
+- **人工复核**:结论写入 `09-19-SUMMARY.md`(含范围与映射口径、`num` 分配、素材产出与 `npc` 偏差、地图口径、门禁结果、`open_items`)。
+
+
+
+- **实体(D-76):** `tile.jsonc` 235 / `enemy.jsonc` 81 / `item.jsonc` 62 条,基础字段按「映射候选(可确定)」全量、按 `num` 策略 A 分配;§8.2/§8.1 字段均未落入数据文件。
+- **素材(D-72/D-81):** 42 张拆分图 + 4 autotile + 1 tileset 落 `src/content/texture/`;`animates` 并入 `terrains`;零依赖生成;`texture/.list.jsonc` 用 D-80 已批准 schema 且与 `tile.jsonc` `num` 一致。
+- **地图(D-82):** 5 楼层全部写入;每层展平数组长度 = `width×height`;旧 `num` 重映射为新 `num`。
+- **unmapped(D-78):** 文档仅含 §8.1;§8.2 未落入文档且未移植。
+- **边界(D-70/D-83):** 未实现消费接线;未改 `packages*/`、`src/`(除 `src/content/**`)、`public/project/**`、`ROADMAP.md`/`STATE.md`/既有台账与计划;未新增依赖。
+- **门禁:** 全部静态门禁(结构 / 标记 / 尺寸 / 一致性 / 作用域)全绿;产出经 Task 0 获批。
+
+
+
+
+## Artifacts this phase produces
+
+| 符号 / 路径 | 类型 | 说明 |
+|---|---|---|
+| `src/content/tile.jsonc`(替换样例) | 数据 | 235 条图块(`num` 策略 A、`id`、`type`、`pass`) |
+| `src/content/enemy.jsonc`(替换样例) | 数据 | 81 条怪物(`num` 对应图块、`attribute`、`special`) |
+| `src/content/item.jsonc`(替换样例) | 数据 | 62 条道具(`num` 对应图块、`category`、`name`、`text`、`hideInToolbox`) |
+| `src/content/maps/*.jsonc`(新增 4 + 替换 MT0) | 数据 | 5 楼层 `IMapRawData` |
+| `src/content/maps/.list.jsonc`(替换) | 数据 | 5 楼层 id 列表 |
+| `src/content/texture/*.png`(新增) | 素材 | 42 张拆分图 + 4 autotile + 1 tileset |
+| `src/content/texture/.list.jsonc`(替换) | 数据 | D-80 已批准 `ITextureListConfig` |
+| `script/migrate-project-textures.ts`(新增,待批准) | 脚本 | 一次性零依赖 PNG 生成 |
+| `.planning/phases/09-minimum-browser-runnable/09-DATA-MIGRATION-UNMAPPED.md`(新增,待批准) | 文档 | 仅 §8.1 无目标字段说明 |
+| `.planning/phases/09-minimum-browser-runnable/09-19-SUMMARY.md`(新增) | 摘要 | 执行结果与 `open_items` |
+
+**命名说明(AGENTS.md / D-73):** 本计划不引入未获批的源码命名;`texture/.list.jsonc` 类型/字段名用 D-80 已批准项;一次性脚本路径(OI-2)、unmapped 文档路径/命名(OI-1)、`source` 路径形态(OI-5)、地图图层键(OI-4)、48px 处理(OI-3)**均入 `open_items`,由 Task 0 报备待批准**。
+
+## Source Coverage Audit
+
+| 来源 | 项 | 处置 |
+|------|----|------|
+| GOAL | Phase 9(ROADMAP)「在已完成的数据端之上完成渲染端框架,产出可在浏览器中运行并验证的最小版本」 | COVERED(增量·任务 9 子切片:基础数据移植,为可运行版本提供真实数据) |
+| REQ | Phase 9 ROADMAP「Requirements: TBD」,无需求 ID | N/A(`requirements: []` 如实标注) |
+| RESEARCH | 无 RESEARCH.md(本阶段为增量派发,无独立研究产物) | N/A(PNG 可行性已在本规划直接核实并记入 assumptions) |
+| CONTEXT | D-76(本次规划单元 = 基础移植;§8.1 不移植) | COVERED(Task 1/3 + prohibitions + 门禁键集) |
+| CONTEXT | D-77(产出形态:tile/enemy/item/maps/texture + 六类基础字段全量) | COVERED(Task 1/2/3) |
+| CONTEXT | D-78(unmapped 文档仅 §8.1;§8.2 不移植不入文档) | COVERED(Task 3 + `MAP GATE` token 校验) |
+| CONTEXT | D-79(`num` 策略 A,`id`↔`num` 一致) | COVERED(Task 1 (0) + 跨文件一致性门禁) |
+| CONTEXT | D-80(`texture/.list.jsonc` 类型/字段命名批准) | COVERED(Task 2 schema) |
+| CONTEXT | D-81(素材落点与命名) | COVERED(Task 2;48px 偏差经 OI-3 报备) |
+| CONTEXT | D-82(maps 全部 5 楼层) | COVERED(Task 3) |
+| CONTEXT | D-83(消费接线不在本增量;`animates` 并入 `terrains`;events/trigger 不移植) | COVERED(Task 2 B 组 + Task 3 + prohibitions) |
+| CONTEXT | D-70 / D-73 / D-75(落点与接线边界 / 命名先报备 / 素材由脚本生成) | COVERED(Task 0 + `open_items` + Task 2 脚本) |
+| CONTEXT | D-01 / D-03 / D-04 / D-06(增量派发 / 执行前获批 / 命名获批 / 不扩范围) | COVERED(Task 0 + prohibitions) |
+| 缺口 | 无(六类基础字段已逐项落到 Task 1/2/3 与门禁) | CLOSED |
+
+> **范围外(非缺口):** animation 素材(D-71)、`images`/`sounds`/`bgms`/`fonts`、§8.2 待定项、消费接线(D-70/D-83)—— 依决策不属本增量。
\ No newline at end of file