Files
Ra3ModXmlExt/docs/plan.md
T
2026-08-04 19:14:12 +02:00

422 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 调研结论与实施计划(已按最新代码同步更新)
> 说明:本文档随实现演进持续同步。最近一次同步(2026-08-04)对齐了实现过程中新增的模块与设计变更:BAB 精确搜索路径、manifest 类型/ID 推导、上下文感知元素类型、属性级 refType / Poid 局部引用(`id` 定义点)、精确跳转范围、嵌套 `xi:include`、注入式语法高亮、bit-flag 列表补全(空格触发 / 排除已用 / 追加模式)、simple-content 元素文本引用(补全 / hover / 跳转 / 诊断 / Find All References)等。
## 一、调研结论(带证据)
### 1. Include 解析规则(证据:`check_duplicate_ids.py` + `defaultscript.cs`
`Data/Mod.xml` 递归处理 `<Include type="all">`(以及任意层级的 `xi:include`)。路径解析按 `defaultscript.cs` `getIncludePaths()` 的编译参数(`/data /art /audio``.` 为 SDK 根目录):
- `DATA:`SDK 根 → modGranParent → 项目 `Data``SDK\Mods` → modParentPath → `SDK\SageXml`
- `ART:`SDK 根 → modGranParent → 项目 `Art1` → 项目 `Art``SDK\Mods` → modParentPath → `SDK\Art`
- `AUDIO:`SDK 根 → modGranParent → 项目 `Audio1` → 项目 `Audio``SDK\Mods` → modParentPath → `SDK\Audio`
其中 modParentPath = 项目目录的父目录,modGranParent = modParentPath 的父目录(如 AttachTest 位于 SDK 内时二者都收敛到 SDK 目录)。无前缀路径相对于当前文件目录解析;`ART:` 支持 2 字母前缀匹配。`defaultscript.cs` 第 4 步“建立全局数据”按 `additionalmaps\mapmetadata_*.xml` 逐个编译;第 5 步“建立基础数据”编译 `Data/Mod.xml`
### 2. 文件规模(证据:实测统计)
- Corona `Data`**7540 个 XML / 38.1 MB**
- SDK `SageXml`**5976 个 XML / 21.4 MB**
- 合计约 1.3 万个文件、60 MB → 索引必须后台化、缓存解析结果、按需惰性加载。
### 3. XSD 结构(证据:`Schemas/xsd` 实测)
-**821 个 XSD / 1.5 MB**,入口 `CnC3Types.xsd`
- 根元素 `AssetDeclaration``Tags` / `Includes` / `Defines` + **295 个顶层资产元素**(含内联声明)。
- 每个元素名对应一个 `complexType`,子元素用 `xs:sequence` / `xs:choice` 定义,属性用 `xs:attribute` 定义;复杂类型通过 `xs:extension` 继承(如 `BaseInheritableAsset` 提供 `inheritFrom`)。
- `Includes/Ref.xsd` 定义了大量带 `xas:refType="<资产类型>"` 的引用类型(如 `CommandSet` 引用 `LogicCommandSet`)→ 补全/导航按引用类型过滤的依据。
- `XmlEdit:Default` 提供默认值;`xs:enumeration` 提供枚举值。`xas:refType` 可声明在 simple type 上,也可声明在 `<xs:attribute>` 节点上(模型生成器两者都读、属性级优先)。
- `Poid`"Pipeline Object Id"`xas:isWeakRef="true"`)表示**管线局部标识**`id` 属性定义元素自身(如 `ModuleData@id` → refType `ModuleData`);`ModuleId``AutoResolveBody``SoundRef` 等 Poid 属性引用同一资产/子树内的模块、子对象、材质——它们都不对全局资产索引做 resolved 判定。
- **同名元素在不同父节点下类型不同**(如 `<Weapon>` 在武器槽下是 `WeaponSlot_WeaponData`,在别处可能是 `WeaponRef`)→ 需要上下文感知解析。
### 4. 领域特有约定
- `$NAME` / `=$NAME``<Defines>` 中定义的常量引用(Corona `GlobalData/GlobalDefines.xml` 实测),属性值补全与 hover 支持。
- `xai:joinAction` 实测取值 `Replace``Remove`
- 原版数据双来源:SDK `SageXml` 提供 XML 源码;`reference` 指向的编译 manifest 提供完整资产表(含美术素材)。
### 5. 网络与工具链(证据:本机实测)
- Node v24.16.0、npm 11.13.0、VS Code 1.130 可用;无全局 `vsce`、Python 不可用。
- npm 注册表可用(需提升权限安装依赖);esbuild 原生二进制在沙箱内受限,构建需提升权限。
### 6. manifest 二进制格式(证据:工作区 `OpenSAGE/` 源码)
用户已克隆 OpenSAGE 并切到 `d45d361``src/OpenSage.Game/Data/StreamFS/ManifestFile.cs` 给出完整格式:
- 头部:首 4 字节为 0 时版本固定为 7;否则 `IsBigEndian`(1B) + `IsLinked`(1B) + `Version`(u165/6) + 10 个 u32 缓冲区/计数信息。
- 资产条目(版本 ≥ 6 时每条 48B):`TypeId`/`InstanceId`/`TypeHash`/`InstanceHash`/`AssetReferenceOffset`/`AssetReferenceCount`/`NameOffset`/`SourceFileNameOffset`/`InstanceDataSize`/`RelocationDataSize`/`ImportsDataSize`
- 之后依次是:资产引用缓冲区、被引用 manifest 名缓冲区、资产名字符串缓冲区、源文件名字符串缓冲区。
- `TypeId` 为哈希;OpenSAGE `AssetType.cs` 枚举提供部分哈希→类型名映射(**不完整**:`Global.manifest` 11268 个资产中 2707 个哈希未知,如 `PlayerTemplate`)。
- **实测发现**manifest 资产名是 `类型名:ID` 格式,美术资产还带子类型段(`W3dContainer:W3DContainer:AUANTIVEHICLEVEHICLETECH1_SKN`)。类型可从第一个冒号段推导,可引用 ID 取最后一个冒号段。
**结论**manifest 解析为 P0 能力,用于 reference include 的资产补全/悬停/导航/诊断。
## 二、技术方案
### 选型
- **TypeScript + VS Code 扩展 API(非 LSP**:补全 / hover / 跳转 / 诊断 / 大纲均用原生 provider,无需语言服务器进程。
- **核心与编辑器解耦**`language/``model/``indexer/` 为纯 TS 模块(不 import `vscode`),可单测与复用(呼应 P1 需求 7)。
- **esbuild 打包**,产物 `dist/extension.js`
- **XSD → JSON 模型**:开发期工具 `tools/xsd-to-model.mjs` 把 821 个 XSD 解析成 `schema-model.json`(元素树、属性、文档、枚举、引用类型映射),随插件发布;运行时不再解析 XSD。
- **运行时 XML 解析**:自研带源码偏移的轻量解析器 `language/xmlParser.ts`(标签/属性/值均记录起止偏移,容错解析以支持输入中的补全与诊断;未闭合引号在行尾恢复,避免吞掉整个文档)。`fast-xml-parser` 仅用于开发期 XSD 生成。
- **AssetType 哈希表**`tools/extract-asset-types.mjs` 从 OpenSAGE `AssetType.cs` 提取 `asset-types.json`;哈希未知时以 manifest 名称前缀推导类型。
### 架构
```
src/
extension.ts 激活入口(provider 注册、索引调度、诊断调度)
workspace.ts 项目检测(Data/Mod.xml / mod.babproj)、索引生命周期、状态栏、重建防抖
settings.ts 配置读取(sdkPath、indexSageXml、definitionMode 等)
language/
xmlParser.ts 带源码偏移的轻量 XML 解析器(格式错误定位、容错)
context.ts 补全上下文分析(元素名/属性名/属性值/内容)
typeContext.ts 上下文感知元素类型解析(resolveElementType 沿解析树逐层解析)
semanticTokens.ts 语义 token 兜底高亮(纯 TS:标签/属性/值范围,仅 malformed 时启用)
model/
schemaModel.ts schema-model.json 的类型/属性/子元素查询 + 类型名规范化(纯 TS)
schema-model.json 由 tools/xsd-to-model.mjs 生成
asset-types.json 由 tools/extract-asset-types.mjs 生成(TypeId 哈希→类型名)
indexer/
includeResolver.ts Include 路径解析(纯 TSBAB /data /art /audio 顺序)
existence.ts 文件集存在性快照(目录枚举 Set,替代逐路径 statSync
manifestParser.ts .manifest 二进制解析 + 类型/ID 推导(纯 TS)
fileScanner.ts 目录遍历缓存 + Include source 候选收集
refs.ts 引用目标解析(属性 + 元素文本内容,按 refType / isRef /
inheritFrom 过滤,纯 TS
shallowScan.ts 大体积美术资产(.w3x 等)顶层浅扫描(纯 TS,不建 DOM)
records.ts 每文件紧凑索引记录(资产/Define/Include/xi + 行号)
caches.ts 跨重建持久缓存(DocumentCache / IndexRecordsCache /
IncludeResolveCache+ 失效纪元 InvalidationsEpoch
diskCache.ts 跨会话磁盘缓存(gzip JSON、原子写、多信号 stat 校验)
indexer.ts 工作区索引器(资产/Define/流/manifest/w3x 合并,
分阶段:XML → art,中间快照可发布)
types.ts 共享类型
features/
completion.ts 补全 provider(元素/属性/值,上下文感知;xs:list 多值按当前段过滤)
hover.ts hover provider
navigation.ts 定义/引用/文档链接/大纲
diagnostics.ts 实时诊断
semanticTokens.ts 语义 token provider(文档有解析错误时接管着色)
syntaxes/
ra3modxml.tmLanguage.json 注入 source.xml 的领域高亮(纯注入,不替换 XML 主语法)
tools/
xsd-to-model.mjs XSD → schema-model.json(含 xs:list:继承 item 枚举/引用语义,isList 标记)
extract-asset-types.mjs OpenSAGE AssetType.cs → asset-types.json
test/
fixtures/minimod 样例 Modinclude 各种情形、同名 ID、嵌套 xi:include、manifest 回退)
*.test.mjs 11 个测试文件(xmlParser / context / completion / semanticTokens /
includeResolver / manifestParser / indexer / schemaModel / refs /
typeContext / manifestTypes
```
### 关键设计决策
1. **语言激活范围**:不劫持 `*.xml`。通过 `workspaceContains:**/Data/Mod.xml``**/*.babproj` 激活;语法高亮为**纯注入** grammar(不声明 `language`,避免覆盖内置 XML 语法)。
2. **索引范围与默认值**:索引“项目 Data + additionalmaps + 沿 include 可达的 SageXml 原版源码”;SDK 路径默认 `C:\Apps\RA3-MODSDK-X`(可配置)。`reference` include 解析为 `builtmods` 下对应 manifest(惰性解析、按文件缓存),manifest 缺失/无效时回退到占位 XML。
**美术资产(.w3x**`<Include type="all">` / `ART:` 指向的 `.w3x`(及内容嗅探为
XML 的未知扩展名文件)按其顶层资产入库(`W3DContainer` / `W3DMesh` /
`W3DHierarchy` / `W3DCollisionBox` 等),使 `Model@Name``Hierarchy``Mesh`
等引用可解析;大模型文件**浅扫描**(不建 DOM),结果缓存在 workspace 级、
跨重建复用(详见设计决策 14)。
3. **manifest 资产建模**:类型优先用哈希表,未知时从名称前缀推导;可引用 ID 取最后冒号段;类型名统一走大小写规范化(`W3dContainer``W3DContainer`),类型匹配严格遵循 XSD 继承链。
4. **上下文感知元素类型**:同名元素按父元素类型解析(`resolveElementType` 沿解析树逐层 `childTypeOf`,失败回退全局映射),保证 `<Weapon>` 等元素的属性/引用判定正确。
5. **引用判定与解析**`refType``isRef` 均视为引用;带 `refType` 时严格按类型过滤(同名 ID 不串类型);`inheritFrom` 按可继承类型过滤。**局部作用域例外**(`isLocalReferenceAttribute`):`id` 是元素自身的定义点——无 refType 或 refType 与自身类型兼容时不检查、不解析(`RoadObject@id→Road` 这类跨类型 id 引用保留检查);Poid 类型属性是管线局部引用,全局索引无法判定,不检查、不解析。
6. **重复 ID 诊断**:与 `check_duplicate_ids.py` 一致——SageXml 不参与冲突判定,mod 覆盖原版视为正常。
7. **未解析引用诊断**:按设置严重级别报告(默认 warning);类型不匹配时给出明确文案("有同名 ID 但类型不匹配")。`definitionMode` 设置控制跳转候选:`all`(mod + 原版全部列出,mod 优先)或 `project-only`
8. **跳转精度**XML 定义跳转到 `id` 属性值的精确 Range;manifest 定义映射到源码文件(如 SageXml)时也在文件内精确定位;找不到再回退到记录行。
9. **嵌套 `xi:include`**:任意层级处理——目标缺失产生诊断、目标存在则纳入索引;根级 `xpointer` 容器内容按顶层资产索引。
10. **性能**:索引在后台执行;解析结果 LRU 缓存(约 64 个文档);文件保存后防抖全量重建(1.5s),重建期间的新请求标记脏并在完成后重跑;状态栏显示进度与统计。
11. **`xs:list` 建模与多值补全**:list 简单类型继承 itemType 的枚举 / refType / isRef /
allowsDefine 并标记 `isList``LocomotorSurfaceBitFlags``KindOfBitFlags` 等 79 个
类型、317 处属性声明受益);补全只对“最后一个空格段”过滤,替换范围只覆盖当前段,
支持 `Surfaces="GROUND ` 之后继续输入 `W` 提示 `WATER`。第十三轮(2026-08-04
补全触发与编辑体验:空格注册为触发字符;列表过滤排除已出现的 flag;当前段已是
完整枚举值且没有更长变体时进入“追加模式”(零宽 range + `insertText=" FLAG"`
可直接在闭合值末尾/中间追加);替换范围止于光标,中间插入不会删除尾部 flag。
12. **未闭合引号的行尾恢复**:起始标签扫描到 EOF 且引号未闭合时,在第一个换行处截断
标签并继续解析,未闭合只影响当前行(仍上报 `Unterminated start tag`),后续元素
的补全 / hover / 诊断不中断。第十三轮补充:恢复出的元素带 `recoveredStartTag`
标记并补挂父链;补全上下文对“光标在恢复元素内但越过 `startTagEnd`”的情况按
`text.slice(tagStart, cursor)` 重新解析部分标签,使多行书写的未闭合属性
(如 `Disposition="`)仍可获得 attribute-value 补全,且不影响全局解析。
13. **语义 token 兜底高亮**:TextMate 对未闭合引号会把后续内容当字符串吞掉(任何
XML 编辑器皆然);扩展注册 `DocumentSemanticTokensProvider`,仅当解析报错时用
语义 token 覆盖标签名 / 属性名 / 属性值(标准 token 类型 `type` / `property` /
`string`,主题自带配色)。合法文件返回空,观感与纯 TextMate 完全一致。
14. **大体积美术资产浅扫描 + 跨重建持久缓存**(第八轮,2026-08-02):
- 实测 Corona3788 个 w3x / 2.64 GB163 个超过 4 MB,最大 22.8 MB;大文件是
`W3DHierarchy` + 若干 `W3DMesh``Vertices/V``Triangles/T` 等 unbounded 数值
载荷),顶层记录通常只有几个到二十几个。
- 全量解析内存放大约 17 倍(6.3 MB 文本 → +109 MB DOM),不可接受;`scanXmlShallow`
单次线性扫描只提取顶层 `name+id``<Includes>``<xi:include>``<Defines>`
22 MB 文件 ~600 ms、保留内存≈0。
- 索引按扩展名三分:`.xml` 全量解析(4 MB 上限不变);`.w3x` 浅扫描;
未知扩展名嗅探文件头(`<` 开头、无 NUL)决定按 XML 浅扫描或二进制登记
manifest 是 `*.manifest` 二进制,不存在 `.manifestxml` 源码格式)。
- `DocumentCache` / `ShallowScanCache``ModWorkspace` 持有,每次重建传入新的
`ModIndexer`;按 `mtimeMs + size` 校验,未变化不重读。Corona 第二次构建
w3x 重扫数为 0(4,829 次缓存命中)。
- 读取整个文件不可避免(顶层边界需要全量扫描),但建 DOM 不是;优化的是
"不分配子节点对象"与"跨重建不重读",两者叠加后方案可行。
15. **重建零 stat + 记录驱动索引**(第九轮,2026-08-02v0.1.1):
- 插桩发现每次重建(含信任重建)都有约 11 万次同步 `statSync`
`resolveSource` 的 include 存在性检查),机械盘上占 10-20s;
`IncludeResolveCache` 按(目录 + source)缓存解析结果,内容编辑不清、
创建/删除文件与强制 reindex 才清。信任重建 statSync 降到 0。
- `IndexRecordsCache` 缓存每文件紧凑索引记录(顶层资产 / Define / Include /
xi:include + 1-based 行号),信任重建完全不接触 DOM;`DocumentCache`
双重淘汰(条数 LRU + 元素预算,超预算先淘汰最大树)把 DOM 常驻内存封顶。
- w3x 缓存不再保留 LineMapCorona 全量约 700MB),浅扫描直接产出带行号的记录。
- 实测:Corona 信任二次构建 21s → **2.0s**statSync 0);首建后 2.5GB
堆保留确认为构建期可回收垃圾,常驻 ~100MB;强制 reindex ~5-25s。
- 候选目录扫描并行化;`stats` 新增 `candidatesMs` / `walkMs` / `resolveCalls` /
`resolveCacheHits` 供索引报告定位耗时。
16. **分阶段索引与部分可用性**(第十轮,2026-08-03):索引分两阶段发布——
阶段 Axml)只走 XML + manifest include 链,w3x 只登记进待扫队列;
阶段 B(art)浅扫描队列并继续走 w3x 内的 include。阶段 A 结束即发布
不可变快照(`snapshotIndex` 深拷贝嵌套 Map/数组),XML/枚举/语法类功能
在首建早期即可用;引用类诊断在 `!complete || stale` 时“显示但标注”
code 为 `*-indexing`,消息注明 index incomplete)。快照携带
`complete` / `phase` / `stale`,状态栏显示阶段。
17. **构建中失效与 stale 标记**(第十轮):watcher 的 change / create /
delete 均触发防抖重建;`InvalidationsEpoch` 记录失效次数,快照发布时若
期间有失效则标记 stale,由 dirty 机制随后重建收敛;构建失败保留上一个
快照而非清空索引。
18. **多信号文件 stamp**(第十轮):`IndexedFile.stat` 扩展为
`{ size, mtimeMs, birthtimeMs, ctimeMs }`,任一不匹配即重读,为磁盘
持久化缓存铺路(FAT32 mtime 2s 粒度、工具保留 mtime 等场景)。
19. **文件集快照替代 statSync**(第十一轮,2026-08-03):`ExistenceSnapshot`
用**惰性按目录 readdir**(只读查询到的父目录,按目录缓存)回答 include
存在性,覆盖根之外才 statSync 回退;盘符根不枚举。首版全量递归枚举使
XML 阶段从 27s 涨到 45s,已改为惰性模式(复测 24.0s)。
`stats.snapshotHits / snapshotFallbacks` 入报告;Corona 首建 75,926 次
查询全部由快照回答、0 回退。
20. **磁盘持久化缓存**(第十一轮):`DiskRecordsCache` 持久化 records 缓存
gzip JSON、原子写、identity key);启动时并发 stat 多信号校验,不匹配
丢弃重读;构建后异步回写。Corona 缓存仅 651 KB,冷启动约 11s(原
~2.5 分钟)。
21. **缓存命令**(第十一轮):`ra3modxml.clearCache`(清内存 + 磁盘 +
强制重建)、`ra3modxml.showCacheReport`(路径/大小/校验统计/命中数)。
22. **重建插桩**(第十一轮补充):`buildCount` / `lastBuildTrigger`
initial、save、watcher-*、config、reindex、clear-cache、
dirty-followup)进入索引/缓存报告;“RA3 Mod XML” 输出通道记录每次构建
的触发原因、phase A 发布时间与完成耗时,用于定位“首建后又重建一次”
之类的现象。
23. **watcher 噪声过滤与 URI 日志**(第十一轮补充):watcher 事件把触发
URI 写入输出通道;路径含 `.git` 段的事件直接忽略(后台 fetch /
maintenance 会周期性触碰 `.git`,不应触发重建或 stale 标记)。
24. **watcher 内容白名单**(第十一轮补充):临时文件命名模式
`.git`/`.tmp`/`.lock`/`~`/`.swp`/`.bak`/`.orig` 后缀、`.#`/`.~`
前缀)全部忽略;`onDidChange` 只响应扩展名白名单(`.xml` / `.w3x`
RA3 合理文本格式为 xml/w3x/lualua 暂未索引、manifest 为二进制)或已在
索引中的文件;创建/删除仍响应所有真实文件(影响 include 存在性)。
25. **当前文档局部链 + 逻辑树展开(第十二轮,T1)**:新增 `xpointer.ts` /
`logicalTree.ts` / `localScope.ts`。打开文件时按当前文本建立局部 overlay
(自身资产 / `$DEFINE` / include 链),并生成展开 `xi:include` 的逻辑树;
features 经 `ws.getScope(document)` 拿到 overlay-aware 索引。Poid 引用
`AttachModuleId` / `ModuleId` / `AutoResolveBody` 等)在最近 GameObject
子树内解析;未命中不新增诊断(保守策略,避免跨文件误报)。
顶层 `<Include type="all">` 暂不并入逻辑树(保留为后续扩展)。
26. **属性补全的插入布局与类型化默认值(第十四轮,2026-08-04**
`attributeInsertLayout` 按临近属性的排版决定插入方式——贴引号时补空格、
一行一个属性时补换行 + 缩进、已在新行时用临近缩进替换当前行空白、
inline 风格的手动换行保留用户缩进;`attributeValuePlaceholder` 对引用/
枚举/list/布尔等建议类属性保留 `$1` + 自动触发,对标量属性填 XSD 默认值
或类型示例(`0d` / `0s` / `100%` / `0.0` / `0`),具体默认值不再弹空
suggest。
第十五轮(2026-08-04)最终结论:VS Code 插入含换行的补全文本时会把当前
行基础缩进与文本内嵌缩进相加(3+3=6、6+3=9…),因此换行前缀只插入 `\n`
缩进交给编辑器;同时半截属性名(`hasValue=false`)不再作为缩进锚点,改用
第一个独占一行的完整属性作为规范缩进,插入换行时顺带吞掉触发补全留下的
尾随空格;属性名补全改用 `SnippetString``$1` 成为真正占位符),并新增
输出通道调试日志。
27. **simple-content 元素文本引用(第十六轮,2026-08-04**simple type
子元素(如 `ObjectCreationList` 内嵌套 `<CreateObject>`,类型
`GameObjectWeakRef`)的**标签间文本**就是资产引用。内容区补全现在区分
“复杂元素 → 子元素名”与“简单元素 → 值补全”;用户已输入 `<` 时替换范围从
`<` 开始,杜绝 `<<`simple type 元素片段固定为 `<Name>$1</Name>`(可填
值)并自动触发值补全。hover / Ctrl 跳转 / 诊断 / Find All References
均增加内容 token 分支。只有**带 `xas:refType`** 的 simple 内容按全局引用
处理(291 处子元素声明);无类型 `AssetReference`
`FXShaderConstantTexture@Value``RenderSubObjectReference@Mesh`
真实数据是贴图/子对象名)与 `Poid` 不参与全局解析,避免误报。
补充:真实文件中 `<` 后还有 `</…>` 时,`findTagEnd` 曾把闭合标签的 `>`
当成残缺开始标签的结束,生成空名/半截名伪元素,补全走 element-name 分支
导致 `<<`。修复为引号外遇到 `<` 即视为未闭合(行尾恢复),且
`elementNameItems` 的替换范围包含 `<`。实机再回归:范围含 `<` 会让 VS Code
`<` 做过滤前缀导致菜单为空——改为保留已输入的 `<`、range 从 `<` 之后
开始、插入文本不带开括号;`textContentTokenAt` 对未闭合元素用 `el.end`
作内容边界。
## 三、实施步骤
1. [x] 调研(Include 规则、XSD、示例项目、规模、工具链)——见本文档第一部分。
2. [x] 脚手架:`package.json``tsconfig.json`、esbuild、`.vscodeignore`、README。
3. [x] 生成模型:`schema-model.json`XSD295 顶层元素 / 1851 类型)与 `asset-types.json`79 个 AssetType 哈希)。
4. [x] 纯 TS 核心:include 解析、manifest 解析、XML 解析封装、索引器、引用解析。
5. [x] 功能层:补全、hover、导航、诊断、大纲、高亮 grammar。
6. [x] 单测(fixture Mod73 个用例全绿:含 xs:list 枚举、未闭合引号恢复、list 多值
分段、带 vscode stub 的补全集成、语义 token 兜底)+ 编译 + `vsce package` 打包
ra3-mod-xml-0.1.1.vsix,约 499KB)。
7. [x] 在 AttachTest / GenEvoTest / Corona 上做冒烟验证,并按真实项目反馈修复问题(详见 `docs/analysis-issues.md` 八轮分析)。
8. [x] w3x 美术资产索引(第八轮):新增 `shallowScan.ts` / `caches.ts`w3x 与内容嗅探
XML 走浅扫描,Include source 补全候选加入 w3x,BOM 剥离,缓存跨重建持久化;
AttachTest 报错场景(`AUGunship_SKN`)修复,Corona 首次/二次构建实测验证。
9. [x] Corona 性能与内存优化(第九轮,v0.1.1):`records.ts` 记录驱动索引、
`IncludeResolveCache` 零 stat 重建、DOM 元素预算淘汰、w3x LineMap 移除、
候选并行扫描、阶段计时;Corona 信任重建 2.0s;确认首建 2.5GB 为可回收垃圾。
10. [x] 部分可用性 + 分阶段索引(第十轮,2026-08-03):T0 解耦(语法/模型
诊断、枚举/子元素补全、Include 链接无索引可用)、A/B 分阶段发布、watcher
触发重建 + 失效纪元 stale 标记、stat 多信号扩展;测试 73 → 79。
11. [x] 冷启动提速(第十一轮,2026-08-03):文件集快照替代 statSync、
磁盘持久化缓存(多信号校验、原子写)、clearCache/showCacheReport 命令;
测试 79 → 90Corona 冷启动 ~11s。
12. [x] 惰性存在性快照 + 重建插桩(第十一轮补充,2026-08-03):全量目录枚举
改为惰性按目录 readdirphase A 45s → 24.0s);新增 buildCount /
lastBuildTrigger 与输出通道日志;索引/缓存报告显示构建序号与触发原因。
13. [x] watcher 噪声过滤 + URI 日志(第十一轮补充,2026-08-03):输出通道
记录 `[watcher-*] <path>``.git` 段路径忽略;测试 90 → 91。
14. [x] watcher 内容白名单(第十一轮补充,2026-08-03):临时文件命名模式
过滤 + 内容变更扩展名白名单 + `isIndexedFile` 兜底;测试 91 → 92。
15. [x] T1 当前文档局部链 + include 展开(第十二轮,2026-08-03):局部
overlay(不在任何流里的文件也能解析自身引用)+ `xi:include` 逻辑树展开
+ Poid 局部作用域补全/悬停/跳转;测试 92 → 98。2026-08-04 补充构建期
闸门:`getScope` 在重建进行中只返回 parse-only scope,避免与 indexer
抢盘(版本 0.1.9)。
16. [x] bit-flag 列表补全修复(第十三轮,2026-08-04):空格触发字符、多行
未闭合标签的部分标签重解析(`recoveredStartTag` 标记 + 恢复元素父链)、
闭合值内追加模式与中间插入范围修复;测试 98 → 107(版本 0.1.10)。
17. [x] 属性补全插入体验(第十四轮,2026-08-04):闭合引号后自动补空格、
临近属性缩进对齐、标量属性类型化默认值;测试 107 → 111(版本 0.1.11)。
18. [x] 属性补全缩进叠加修复(第十五轮,2026-08-04):换行前缀只插入 `\n`
(编辑器自动补基础缩进,避免 3+3=6 式叠加)、完整属性锚点 + 首个独占一行
属性为规范缩进、`$1` 改为 SnippetString 占位符、输出通道调试日志、尾随
空格吞除;测试 111 → 113(版本 0.1.120.1.13)。
19. [x] simple-content 文本引用修复(第十六轮,2026-08-04):`<` 后补全范围
覆盖 `<`、simple type 元素片段 `<Name>$1</Name>`、简单元素内容值补全、
`refs.ts` 新增 `isReferenceContentType` / `resolveContentReferenceTargets`
hover / 定义跳转 / 诊断 / Find All References 内容分支;
测试 113 → 121(版本 0.1.14)。实机回归补充:`findTagEnd` 引号外遇 `<`
视为未闭合 + 插入文本不带开括号(range 从 `<` 之后开始);测试 121 → 125,
再回归 125 → 128(未闭合元素内容 token)。
20. [x] simple-content 零宽边界与大列表补全(第十七轮,2026-08-04):
`>` 后光标归入内容区、已闭合元素 `end` 改为开区间;超过 400 条的
id/define/local/include 候选返回 `isIncomplete` 让 VS Code 随输入重请求;
当前文档 local 资产优先、候选 top-N 用堆避免全量排序、Include source
先排序再截断;测试 128 → 136。
## 四、验证结果(实测)
| 项目 | 规模 | 索引耗时 | 资产数 | 说明 |
|---|---|---|---|---|
| AttachTest | 88 文件 | ~1.2s | 35,607manifest 35,322w3x 浅扫 62 | 2 个流(static + mapmetadata);二次构建 354ms / 62 缓存命中 |
| GenEvoTest | 66 文件 | 首次 ~2.6s / 二次 ~0.4s | 35,502manifest 35,322w3x 浅扫 38 | 2 个流、73 个 Define;二次构建 0 重扫 / 38 缓存命中 |
| Corona | 8,976 文件 | 首次 ~250s / 信任二次 ~2s / 强制 ~5-25s | 64,868manifest 35,322w3x 浅扫 4,829 | 3 个流、183 个 Define、0 诊断;二次构建 statSync 0、resolveHits 15,333 |
单元测试覆盖:XML 解析(自闭合/容错/偏移/未闭合引号行尾恢复 + recovered
标记)、补全上下文(未闭合引号仍为 attribute-value、多行未闭合引号、list 多值
分段、闭合引号后为 attribute-name)、补全集成(vscode stub 下
`LocomotorTemplate@Surfaces` 未闭合引号枚举补全、空格后第二段过滤与替换范围、
空格后排除已用 flag、中间插入范围止于光标、闭合值末尾追加模式、`CAN_ATTACK`
前缀保护、多行未闭合 `Disposition` 完整链路、闭合引号后补空格、一行一个属性
换行缩进、新行缩进对齐、标量类型化默认值)、语义 token(标签/属性/值范围、
合法文档返回空、malformed 返回兜底 token)、include 解析(BAB 顺序、SDK 根
优先于 SageXml)、manifest 二进制解析(合成 v5 样本、类型/ID 推导)、索引器
(资产/Define/流/缺失 include/嵌套 xi:include)、XSD 模型(上下文类型、
`childTypeOf`、大小写规范化、属性级 refType、外来命名空间判定、`xs:list`
枚举继承与 `isList` 标记)、引用过滤(`Weapon="X"` 只跳 `WeaponTemplate`
模块 `id` 定义点、Poid 局部引用、`xi:include` 不校验、`Side="Allies"` 命中
manifest 的 `PlayerTemplate`)、simple-content 文本引用(`<` 后补全不产生
`<<``<CreateObject>$1</CreateObject>` 片段、内容值按 refType 过滤、
内容 hover / Ctrl 跳转 / 诊断)。
> 注:D: 盘移动硬盘已恢复连接;Corona 已在第八 / 九轮按上述新数据回归。
## 五、假设与开放问题
- 假设 SDK 路径默认 `C:\Apps\RA3-MODSDK-X`(与 prompts 一致),可在设置中修改。
- 假设补全/导航以“文本语义分析”为主,不做完整 XSD 校验(BAB 才是权威校验器)。
- 开放:是否发布到 VS Code Marketplace(需要 publisher)——本期先保证本地 `vsce package` 可安装。
- 开放:**“宏展开”式虚拟合并**(用户提议,方向已确认):`xi:include` 已按逻辑树
展开(第十二轮,见第六节);顶层 `<Include type="all">``inheritFrom` +
`xai:joinAction` 的深合并仍未实现,后续如需要“当前文档视角的全量合并诊断”再继续。
## 六、include 展开设计备忘(2026-08-01xi:include 部分已实施于第十二轮)
> 目的:集中记录 include 处理相关的现状、结论与设计,下次遇到 include 问题时从这里继续,
> 并在实施后把结果回写本节。
### 1. 现状(截至第五轮,已实现)
| 能力 | 状态 |
|---|---|
| `<Include type="all">` / `instance` 递归索引(顶层资产、流、Define) | 已实现(indexer `walk` |
| `reference` → builtmods manifest 解析 / 缺失回退占位 XML | 已实现 |
| 嵌套 `xi:include`(任意层级):目标可索引、缺失报 `include-not-found`、Ctrl+点击跳转、`href` hover 解析目标 | 已实现(第二轮 + 第五轮) |
| `xi:include` 及其属性不参与 XSD 校验(外来命名空间守卫 `isXsdElementName` / `isXsdAttributeName` | 已实现(第五轮) |
| include 目标内容“虚拟合并”进父文档的逻辑树 | 已实现 `xi:include`(第十二轮);顶层 `<Include type="all">` 仍不展开 |
### 2. 已确认的方向
先展开成不含 include 的文档树(类比 C++ 宏展开),再对展开后的树做 mod XML 解析。
BAB`defaultscript.cs`)编译时正是这样把整个 Mod 合并成一份大 XML 的。
### 3. 关键设计决策:逻辑树拼接,不做文本拼接
- **不要**把 include 目标展开成文本再整体重新解析:源码偏移会断裂,诊断 / 跳转 / hover /
补全全部无法映射回原始文件。
- **要做**的是:解析器逐文件解析(现状不变);展开器把目标文件选中节点按 `xpointer`
子集挂进父元素,节点保留各自的源文件与原始偏移(来源追溯)。后续分析跑在逻辑树上,
范围映射按节点 `sourceFile` 回到对应文件的 lineMap。
- 现有 `parseXml` 已记录标签 / 属性 / 值的起止偏移,`XmlElement` 结构可直接复用;拼接时
用浅拷贝节点壳并重建 parent 链,避免破坏目标文件缓存树自身的 parent 指针。
### 4. 展开范围
| 构造 | 拼入逻辑树 | 理由 |
|---|---|---|
| `xi:include` | ✅ | 内容并入父元素(HeadlightDraw2 场景) |
| EA `<Include type="all">` | ✅ | BAB“内容合并”,等价于复制进来 |
| `type="instance"` | ❌ | 只影响编译可见性;拼树会把 BaseVehicle 的顶层资产错误塞进当前文档 |
| `type="reference"` | ❌ | manifest 编译产物,无文本内容 |
| `inheritFrom` + `xai:joinAction` | 单独一轮 | 元素级继承深合并(Replace/Remove),不是宏展开 |
### 5. 落地位置与接入点
- 新纯模块(与编辑器解耦,呼应 P1):输入 `(parse 树, resolveSource 回调, readDocument 回调)`
输出逻辑树(root + elements 扁平列表,沿用 diagnostics 的遍历形态)。
- 接入:diagnostics / hover / navigation / completion 目前各自 `parseXml(text)`
改为 parse 后过 expander 取逻辑树;范围映射按节点 `sourceFile` 选对应文件的 lineMap。
- 按需展开(当前打开文档)+ 按文件缓存(复用 indexer `readDocument` 的 LRU);
环 / 深度守卫复用现有 `visitedAll` 与深度限制思路(建议最大深度 64)。
- `xpointer`:仅支持 mod 实际使用的 `xmlns(n=...) xpointer(/n:Name/child::*)` 子集
(现有 `findXPointerContainer` 正则已覆盖);完整 XPath 暂不支持,遇到新形式先记录到本节。
### 6. 后续收益(承接第四 / 五轮遗留)
- **GameObject 内模块 id 局部作用域**:展开后 include 进来的兄弟模块(HeadlightDraw2
与本体模块同树,`AttachModuleId` / `ModuleId` / `AutoResolveBody` 等 Poid 引用
才能静态解析与诊断(第四轮遗留);
- 跨 include 的上下文类型解析与结构校验;
- 顶层 `type="all"` 合并后,当前文档视角的重复 id / 引用诊断更接近 BAB 结果。
### 7. 风险与边界
- 大文件性能(Corona 约 7500 文件 / 38MB):只展开当前文档的可达链,不全局展开;
- include 环 / 深度:visited 集合 + 最大深度;
- 被包含内容的诊断上报位置:建议按节点源文件 URI 上报(与偏移一致),包含点的 `href`
上只报“缺失 / 无法解析 / 环”类问题;
- `inheritFrom` 深合并(`xai:joinAction` 的 Replace/Remove 语义)单独设计,别与宏展开混在一轮。
### 8. 下次遇到 include 问题的检查清单
1. 现象发生在哪一层:索引(indexer)、诊断(diagnostics)、导航 / hover、还是补全?
2. 现状能力是否已覆盖(见第 1 节表格);
3. 涉及内容是否跨文件(需要逻辑树)还是本文件内(当前解析树即可);
4. 若要展开:先实现第 5 节的纯模块与单测(fixture 增加 HeadlightDraw2 场景),再接入 feature
5. 把新结论回写本节与 `docs/analysis-issues.md`