ginka-generator/docs/special-door-clean-design.md
2026-05-27 22:29:44 +08:00

16 KiB
Raw Blame History

机关门语义与数据集清洗修正文档

背景

当前数据集清洗已经能处理连续门团、连续怪团、无用分支和一部分闲置分支,但还有一类常见且重要的结构没有被正确表达:机关门驱动的战斗房间

这类结构的共同点是:

  1. 地图中存在机关门。
  2. 门前或门附近会摆放一组怪物。
  3. 这些怪物在局部上未必直接守护资源,也未必在拓扑上形成“打掉以后暴露新区域”的普通分支。
  4. 但它们仍然有明确设计意义,因为它们承担了“开机关门”的推进语义。

一个典型例子可以概括为:上方是一扇机关门,下方是一个小房间,房间里放若干怪物和空地。若只按当前“邻居数”或“后侧资源收益”来判断,这些怪物很容易被视为闲置怪;但从实际玩法上看,它们显然不是噪声结构。

因此,这个问题本质上不是“过滤阈值太松或太紧”,而是当前标签空间里没有把机关门作为独立语义类保留下来,导致后续清洗算法看不到这层结构信息。


问题概述

当前异常现象

在当前规则下,存在一批地图会因为“闲置怪”而被过滤,但人工复核时会发现这些怪物实际上属于机关门房间的一部分。它们看起来像是:

  1. 站在一片空地边缘。
  2. 不直接守护资源。
  3. 拓扑上可能只连接到一个邻接节点。
  4. 但所在区域同时存在机关门,击败这些怪物本身就是推进条件的一部分。

也就是说,这些怪物虽然满足了“局部无影响分支”的几何特征,却不满足“没有设计意义”这一真正的清洗目标

误判根因

这个问题目前至少有三层原因:

  1. 标签层压扁:机关门在原始塔数据中可被识别,但在当前主配置里没有变成独立 tile而是与普通门共用同一标签。
  2. 拓扑层压扁:拓扑图只区分 DoorEnemy 两种分支,不区分普通门和机关门。
  3. 过滤层失语义:闲置怪、重复守卫、无用分支等规则只能看到“门/怪/空地/资源”的局部拓扑,看不到“这个怪物与机关门联动”的语义。

只要这三层没有拆开,即使继续微调 allowIdleBranch 一类阈值,也很难稳定解决问题。


当前实现现状

转换层已经能识别机关门,但没有保留独立标签

当前实现中,原始塔数据里的 specialDoor 已经能在转换阶段被识别出来;这一点说明问题不是“完全不知道机关门存在”,而是识别到了,但在标签化输出时被折叠掉了

现状可以概括为:

  1. 转换阶段会把原始 specialDoor 映射到 tiles.specialDoors[0]
  2. 但当前主配置里,specialDoorscommonDoors 实际共用了同一个标签值。
  3. 共享常量里,specialDoorTiles 也与普通门使用同一个集合值。

这带来两个直接后果:

  1. 训练数据里的地图矩阵无法区分“普通门”和“机关门”。
  2. 当前的 specialDoorCount 统计并不是真正意义上的“机关门数量”,而只是复用了普通门的标签统计,观测值没有独立语义。

换句话说,当前代码里虽然存在 specialDoors 这一字段,但它还没有真正成为独立的数据集类别

拓扑层只有 Door/Enemy 两类分支

当前拓扑图中的分支类型只有两种:

  1. Door
  2. Enemy

这意味着:

  1. 连续门检测会把普通门和机关门都看成同一类门分支。
  2. 闲置门检测也无法区分普通门和机关门。
  3. 更重要的是,怪物侧根本无法知道“我所在的这个房间是否与机关门有关”。

对于本问题来说,真正缺失的并不是“门是不是门”,而是“这个门是不是机关门”。

当前闲置筛选为什么会误杀

现有闲置规则里,最敏感的是这两类:

  1. neighbors.size === 1 的闲置分支规则。
  2. 基于后侧收益缺失的无用分支规则。

它们的问题并不是逻辑错误,而是默认假设了:

  1. 分支的价值主要来自局部守护资源。
  2. 分支的价值主要来自是否暴露新区域。

但机关门关联怪的价值来自第三种来源:全局或半全局的开门触发语义。如果标签空间里没有 Special Door,这类语义就无法进入筛选逻辑,于是就会被误判成“看起来没用的怪”。


目标

本轮文档希望固定以下目标:

  1. 在数据集标签层中,为机关门新增一个独立 tile 类别 Special Door
  2. 在拓扑和清洗阶段,能够识别“机关门关联怪”,避免其被简单当作闲置怪过滤。
  3. 在不大幅重写现有拓扑框架的前提下,尽量复用已有的区域合并、邻接和入口连通逻辑。
  4. 让过滤结果继续保持可解释,至少能回答“为什么这个怪没有再被判成闲置怪”。
  5. 第一版实现优先保守,宁可少杀一些,也不要把常见机关门房间结构大量误删。

非目标与边界

本轮文档暂时不做以下承诺:

  1. 不要求第一版就精确恢复脚本级联动关系。也就是说,暂不要求从原始事件脚本中严格求出“哪一只怪控制哪一扇机关门”。
  2. 不要求立即重写所有门/怪规则。本次重点是修正与机关门相关的误杀,而不是重构整套清洗体系。
  3. 不要求在本轮文档里完成模型改造细节。但需要明确指出:新增 tile 会影响训练数据词表和 mask token 编号。
  4. 不改变机关门在大类上的门属性。例如门密度、门类分支连通块等统计,默认仍然把机关门视为门的一种。

设计一:在标签层新增 Special Door

设计原则

这里需要强调一个重要约束:当前训练数据里 MASK_ID = 6,因此不能直接把新语义无脑塞到现有编号上而不处理 mask token

第一版比较稳妥的编号方案是:

语义 当前主要编号 建议编号
Empty 0 0
Wall 1 1
Common Door 2 2
Resource 3 3
Enemy 4 4
Entry 5 5
Special Door 无独立编号 6
Mask 6 7

这个方案的优点是:

  1. 现有 0 到 5 的主语义尽量不动。
  2. 只为 Special Door 新增一个实际 tile。
  3. MASK_ID 顺延到 7语义清晰不与真实地图 tile 混用。

对数据预处理侧的影响

新增独立标签后,数据预处理侧至少需要满足:

  1. commonDoorTilesspecialDoorTiles 不再共享同一标签值。
  2. doorTiles 仍然是二者并集,用于总门密度等大类统计。
  3. specialDoorCount 才能真正表示机关门数量,而不是“普通门标签的重复统计”。
  4. 主配置中的 specialDoors 需要改成独立编号,而不是继续与普通门共用标签。

对训练侧的影响

虽然本轮不直接修改训练代码,但文档必须明确:新增 Special Door 之后,训练侧至少要同步处理以下问题:

  1. 数据集的 tile 词表大小会加 1。
  2. MASK_ID 需要后移。
  3. Stage 1 / Stage 2 / Stage 3 的退化逻辑需要把 Special Door 视作门类的一部分处理。
  4. 统计目标里的门密度若仍按“大类门”处理,则需要把普通门和机关门一起计入。

也就是说,Special Door 虽然是一个新 tile但它在训练目标上仍然应属于“门超类”中的细分子类而不是完全独立的结构域。


设计二:在拓扑层保留机关门子语义

为什么不能只改 tile不改拓扑

如果只是把地图矩阵里的机关门编号拆出来,但拓扑层仍然把它和普通门都压成同一个 Door 分支,那么清洗算法依然无法利用这条信息。

因此,除了标签层扩展以外,拓扑层也需要补上“门子类型”这一维语义。

建议的数据结构方向

当前更合适的方向,不是把 BranchType 直接从两类扩成三类,而是保留现有大类,再补一个门子类型字段。原因是:

  1. 现有很多逻辑只关心“门 vs 怪”,并不希望被额外分支复杂化。
  2. 连续门团、门密度等统计,仍然需要把普通门和机关门归到门大类里。
  3. 但闲置修正又确实需要知道“这个门是不是机关门”。

因此更推荐的方向是:

  1. branch 仍然保持 Door | Enemy
  2. 对门分支额外补一个 doorKind = Common | Special

这样可以同时满足:

  1. 老规则不需要大改。
  2. 新规则在需要的时候可以单独读取机关门语义。

机关门在拓扑统计中的默认归属

当前版本建议固定以下口径:

  1. 大类统计:机关门计入门密度、门连通块、门热力图。
  2. 子类统计:另外单独记录机关门数量,必要时可追加机关门密度。
  3. 异常清洗:仅在与机关门关联怪的识别相关时,读取 doorKind = Special 这一子语义。

这意味着:新增 Special Door 不是为了把机关门彻底从“门”里面剥出去,而是为了给清洗逻辑一个足够可靠的判别信号。


设计三:为“机关门关联怪”增加保守豁免

问题本质

需要修正的并不是“所有机关门附近的怪都要保留”,而是:

如果某只怪所在的局部区域明显属于机关门房间的一部分,那么它不能仅仅因为 neighbors.size === 1 或“背后无资源”就被直接视为闲置怪或无用怪。

换句话说,当前需要补的是一条保守豁免规则,而不是另一条更强的删除规则。

为什么第一版不建议直接解析脚本联动

理论上,最精确的做法是从原始塔事件里直接解析:

  1. 哪扇机关门存在。
  2. 哪些怪物被击败后会触发开门。
  3. 一只怪是否同时参与多个门的触发。

但这个方案的工程代价很高,而且不同塔版本、脚本写法、自定义事件逻辑都可能造成兼容成本。因此,第一版不建议把问题拉到脚本层求精确真值,而是先使用当前拓扑图已经具备的信息,做一个偏保守的结构性修正。

建议主规则:基于“机关门关联区域”的豁免

当前版本更合适的主规则是:

  1. 先沿用现有的非分支区域合并逻辑,把 EmptyResource 节点合并成更大的可进入区域。
  2. 若某个合并区域与至少一个 Special Door 分支相邻,则把这个区域记为“机关门关联区域”。
  3. 若某个怪物分支与某个机关门关联区域相邻,则把该怪记为“机关门关联怪候选”。
  4. 对机关门关联怪候选,不再仅凭 neighbors.size === 1 就直接计入 idleEnemyBranchCount
  5. 对机关门关联怪候选,也不再仅凭“局部无资源收益”就直接计入无用分支命中。

这条规则的核心思想是:

  1. 当前并不试图证明“这只怪一定会开门”。
  2. 当前只要求证明“这只怪所在区域与机关门强相关”。
  3. 一旦存在这种强相关,第一版就保守地不把它当作典型闲置怪删除。

为什么这个规则适合作为第一版

这条规则有几个优点:

  1. 它只依赖现有拓扑图与门子类型,不依赖脚本解析。
  2. 它可以复用当前重复守卫规则里已经存在的“合并非分支区域”思路。
  3. 它解释性很强,可以直接回答“这只怪因为与机关门处在同一关联区域,所以没有被判成闲置怪”。
  4. 它天然偏保守,符合当前“宁可少杀,不要误杀重要结构”的工程目标。

已知代价

这条豁免规则也有明确代价:

  1. 它可能会保留少量“恰好和机关门在同一区域、但其实也不太重要”的怪物。
  2. 它并不能恢复真正的脚本级触发关系,只是利用局部结构做代理。

但当前版本更需要避免的是高频、显眼、结构性明确的误杀。与之相比,这种偏保守的漏删代价是可以接受的。


对现有筛选规则的具体影响

规则一:idleEnemyBranchCount

这是当前最需要优先修正的规则。

建议口径:

  1. 怪物若命中 neighbors.size === 1,先作为普通闲置怪候选。
  2. 再检查它是否属于机关门关联怪。
  3. 若是,则不计入 idleEnemyBranchCount
  4. 若不是,则维持原判。

规则二:hasUselessBranch

这条规则也可能误伤机关门关联怪,因为这类怪物往往不守局部资源。

建议口径:

  1. 对普通分支,仍按现有“删除该分支后,后侧是否失去入口连通且没有资源收益”的规则处理。
  2. 对机关门关联怪,第一版默认不参与这条规则的硬过滤。

原因是:机关门关联怪的价值本来就未必体现为“守护后侧资源”,继续套用这条规则容易再次把它们误杀。

规则三:hasRepeatedGuardIdleBranch

这条规则的风险相对次要,但仍需要关注。因为一个机关门房间里可能存在多个同类怪,它们可能被错误理解成“重复守同一连通区域”。

当前建议是:

  1. 第一版先增加观测统计,不急着强行改判。
  2. 如果抽样发现机关门房间经常命中这条规则,再补“机关门关联怪不参与 repeated guard 统计”这一豁免。

也就是说,这条规则暂时列为重点观察项,不一定与 idleEnemyBranchCount 同步收紧。

规则四:连续门团 / 连续怪团

这两条规则当前不建议因机关门而修改。

原因是:

  1. 机关门仍然是门的一种,继续计入门类连通块是合理的。
  2. 机关门关联怪即使有特殊语义,也不意味着它们应当不受连续怪团规则约束。

换句话说,当前修的是“局部价值误判”,不是“所有机关门怪都特殊”。


统计与可观测性要求

为了让后续人工复核和规则调整有抓手,新增机关门语义后,至少应补以下统计:

  1. specialDoorCount:真正意义上的机关门数量。
  2. specialDoorFloorCount:包含机关门的楼层数量。
  3. specialDoorLinkedEnemyCount:被识别为机关门关联怪的数量。
  4. ignoredIdleEnemyBySpecialDoorCount:原本会命中闲置怪,但因机关门关联而被豁免的数量。
  5. ignoredUselessBranchBySpecialDoorCount:原本会命中无用分支,但因机关门关联而被豁免的数量。

当前 filtered 数据集与图片标注体系也应支持这一点,至少能在抽样时回答:

  1. 某张图为什么被保留。
  2. 某张图为什么仍被过滤。
  3. 如果某个闲置怪没有被过滤,是不是因为它属于机关门关联怪。

验收方向

本问题后续落地时,至少应满足以下验收方向:

  1. 数据集标签中,机关门拥有独立 tile 编号,不再与普通门共用同一个语义值。
  2. specialDoorCount 能真正反映机关门数量,而不是普通门的重复统计。
  3. 典型机关门房间中的怪物,不会再仅因 neighbors.size === 1 被直接判成闲置怪。
  4. 对包含机关门的地图进行抽样复核时,能明显看到误杀减少。
  5. 连续门团、连续怪团等其他清洗规则在大方向上保持不变,不因为这次修正而整体失真。

当前建议的实施顺序

从工程风险看,当前更稳妥的顺序是:

  1. 先把 Special Door 从标签层独立出来。
  2. 再在拓扑层补上门子类型。
  3. 优先修正 idleEnemyBranchCount 的误判。
  4. 然后观察 hasUselessBranchhasRepeatedGuardIdleBranch 的命中样本,决定是否追加豁免。

这个顺序的原因是:如果连独立 tile 都没有,后面的筛选修正只能停留在模糊的局部几何启发式上,无法稳定描述“机关门关联怪”这一目标对象。


本文档暂不回答的问题

以下问题在当前版本里先不定稿,留待后续实现与抽样验证后再讨论:

  1. 是否有必要进一步解析原始脚本,恢复精确的“怪 -> 机关门”触发关系。
  2. 机关门关联怪是否应该影响资源密度、怪物密度等统计解释方式。
  3. 是否需要为“机关门房间”本身增加一个独立结构标签,供后续训练条件使用。
  4. 机关门是否需要从普通门的连续门团统计中拆出去单独观测。

当前文档只固定一件事:机关门必须先成为独立语义类,闲置筛选才有可能在这个问题上做对。