first commit
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
# 问题分析:AttachTest 高亮异常与误报诊断
|
||||
|
||||
> 日期:2026-08-01。基于在 AttachTest `Data/Mod.xml` 上复现的问题。
|
||||
> 本文件只做分析,**代码尚未修改**,修复方案见文末,待审阅后实施。
|
||||
|
||||
## 一、现象
|
||||
|
||||
1. **语法高亮异常**:
|
||||
- `<?xml version="1.0" encoding="UTF-8"?>` 显示为纯文本颜色;
|
||||
- `<Includes` 之后的 `>` 无高亮;
|
||||
- `<Include type="reference" ...>` 中 `Include` 之后的内容全部无高亮;
|
||||
- 整体表现为"只有少量标签名被着色,其余按纯文本处理"。
|
||||
2. **误报诊断**:
|
||||
- `<Include type="reference">` 的 `type` 值报 `Unresolved reference "reference"`;
|
||||
- `<Include source="DATA:Static.xml">` 的 `source` 值报 `Unresolved reference`;
|
||||
- 同一处 hover 显示解析结果为 `C:\Apps\RA3-MODSDK-X\SageXml\Static.xml`,但实际应按编译参数解析到 SDK 根目录 `C:\Apps\RA3-MODSDK-X\Static.xml`。
|
||||
|
||||
## 二、实测证据
|
||||
|
||||
用当前构建代码对 AttachTest 索引后验证:
|
||||
|
||||
```
|
||||
[resolve] DATA:Static.xml -> C:\Apps\RA3-MODSDK-X\Static.xml ← 解析器结果正确
|
||||
[candidate] 精确匹配 -> C:\Apps\RA3-MODSDK-X\SageXml\Static.xml ← hover 用的是这条
|
||||
[model] Include@type refType: null enum: [reference, instance, all]
|
||||
[model] Include@source refType: null type: anyURI
|
||||
[diag] Include@type='reference' targets: 0
|
||||
```
|
||||
|
||||
三个问题均稳定复现。
|
||||
|
||||
## 三、根因分析
|
||||
|
||||
### 1. 高亮异常:grammar 注册方式覆盖了内置 XML 语法
|
||||
|
||||
`package.json` 中的 grammar 贡献写法:
|
||||
|
||||
```json
|
||||
{
|
||||
"language": "xml",
|
||||
"scopeName": "source.ra3modxml",
|
||||
"injectTo": ["source.xml"]
|
||||
}
|
||||
```
|
||||
|
||||
同时填写 `language` 与 `injectTo` 时,VS Code 会把该 grammar 注册为 **`xml` 语言的主 grammar**(替换内置 XML 语法),而不仅是注入。结果是:
|
||||
|
||||
- 内置 `source.xml` 的完整 token 化规则不再生效;
|
||||
- 我的语法只有几个领域关键词模式,没有 XML 声明、标签括号、属性名/值的规则;
|
||||
- 于是 `<?xml ...?>`、`>`、属性区全部退化为纯文本,只有 `$DEFINE`、`inheritFrom`、结构标签名等被我的模式着色——与现象完全吻合。
|
||||
|
||||
正确的做法是注入式 grammar 不声明 `language`(VS Code 官方注入语法示例即省略该字段)。
|
||||
|
||||
### 2. 误报 `Unresolved reference`:诊断把所有属性值都当成"引用"检查
|
||||
|
||||
诊断代码在 `checkValueReferences` 中先调用 `resolveReferenceTargets(...)`,并以其返回空数组作为"未解析"依据。而 `resolveReferenceTargets` 对**非引用属性**(如 `Include@type` 枚举、`Include@source` 的 `anyURI`)也返回空数组——两者无法区分。
|
||||
|
||||
这是早期实现回归:最初代码有 `isRef`(检查 `refType` 或 `inheritFrom`)守卫,重构为共享函数后丢失了该守卫。
|
||||
|
||||
同理,hover 对非引用属性值也会走到"未找到匹配定义"分支,显示误导性文案。
|
||||
|
||||
### 3. `DATA:Static.xml` hover 路径错误:候选表优先于解析器,且候选表大小写不统一
|
||||
|
||||
- 解析器 `resolveSource` 按搜索路径正确解析到 SDK 根 `Static.xml`;
|
||||
- 但 hover/定义跳转/文档链接都是**先查 `idx.sourceCandidates` 精确匹配**;
|
||||
- 候选表里同一 source 字符串 `DATA:Static.xml` 出现两条:一条来自 `SageXml\Static.xml`(扫描更早、排在前面),一条来自 SDK 根目录浅扫描;
|
||||
- `find` 精确匹配返回第一条 → 显示 SageXml 路径,而按 BAB 顺序应先命中 SDK 根目录。
|
||||
|
||||
修复方向相应调整为:**候选表只服务补全**;hover/跳转/链接一律先走 `resolveSource`(按 BAB 顺序校验文件存在)。候选表本身也按 source 去重并让 SDK 根目录条目排前。
|
||||
|
||||
另外,当前搜索路径来自 `check_duplicate_ids.py` 的简版(`[SDK根, 项目Data, Mods, SDK/SageXml]`),与 BAB 编译参数不完全一致,缺少 `modGranParent` 与 `SDK\Mods` 两个条目。`defaultscript.cs` 的 `getIncludePaths()` 实际参数为:
|
||||
|
||||
```
|
||||
/data: ".;{modGranParent};{0}\Data;.\Mods;{1};.\SageXml"
|
||||
/art: ".;{modGranParent};{0}\Art1;{0}\Art;.\Mods;{1};.\Art"
|
||||
/audio: ".;{modGranParent};{0}\Audio1;{0}\Audio;.\Mods;{1};.\Audio"
|
||||
其中 {0}=ModPath, {1}=modParentPath, {2}=modGranParent, "." = SDK 根目录(编译时 cd 到 SDK)
|
||||
```
|
||||
|
||||
展开后(以 AttachTest 为例):
|
||||
|
||||
| 前缀 | 搜索路径(BAB 顺序) |
|
||||
|---|---|
|
||||
| DATA | SDK根 → modGranParent → Mod\Data → SDK\Mods → modParentPath → SDK\SageXml |
|
||||
| ART | SDK根 → modGranParent → Mod\Art1 → Mod\Art → SDK\Mods → modParentPath → SDK\Art |
|
||||
| AUDIO | SDK根 → modGranParent → Mod\Audio1 → Mod\Audio → SDK\Mods → modParentPath → SDK\Audio |
|
||||
|
||||
`DATA:Static.xml` 在 BAB 顺序下首先命中 SDK 根的占位文件(与用户预期一致)。
|
||||
|
||||
## 四、修复方案
|
||||
|
||||
### 4.1 语法高亮(P0)
|
||||
|
||||
- `package.json` 的 `grammars` 条目**移除 `"language": "xml"`**,只保留 `scopeName`、`path`、`injectTo`,使其成为纯粹的注入语法,叠加在内置 XML 语法之上。
|
||||
- 精简 `syntaxes/ra3modxml.tmLanguage.json`:保留不冲突的模式(`$DEFINE` 常量、`inheritFrom`、`xai:joinAction`、TODO 注释、结构标签名);删除对 `Replace`/`Remove` 的裸词匹配(避免误染普通内容)。
|
||||
- 验证方式:扩展开发宿主中打开 `Data/Mod.xml`,确认 XML 声明、标签、属性恢复完整高亮且领域关键词仍有专属颜色。
|
||||
|
||||
### 4.2 诊断与 hover 的引用判断(P0)
|
||||
|
||||
- 恢复"是否引用属性"守卫:仅当属性为 `inheritFrom` 或模型中有 `refType` 时才进入未解析引用检查;`Include@type`、`Include@source` 等不再误报。
|
||||
- hover 同步加守卫:非引用属性值不再显示"未找到匹配定义"文案;`Include@source` 走专门的"Include 源文件"分支。
|
||||
|
||||
### 4.3 Include 源解析(P0)
|
||||
|
||||
- hover / 定义跳转 / 文档链接统一改为**优先调用 `resolveSource`**(按 BAB 顺序、校验文件存在),候选表只作为兜底与补全来源。
|
||||
- `buildSearchPaths` 对齐 BAB 编译参数:DATA/ART/AUDIO 各补上 `modGranParent` 与 `SDK\Mods` 两个搜索条目,顺序与 `defaultscript.cs` 一致。
|
||||
- 候选表去重改为大小写不敏感(按 `source.toLowerCase()` 去重),避免同一文件因大小写不同出现两条候选。
|
||||
|
||||
### 4.4 测试补充
|
||||
|
||||
- 新增/更新单测:
|
||||
- 非引用属性(`Include@type`、`Include@source`)不会产生 unresolved-reference 目标;
|
||||
- `DATA:Static.xml` 按 BAB 顺序解析到 SDK 根目录而非 SageXml;
|
||||
- `buildSearchPaths` 展开结果与 BAB 参数一致(构造 SDK 内/外两种项目布局断言顺序)。
|
||||
- 回归验证:AttachTest / GenEvoTest / Corona 各索引一次,确认 0 误报、补全/跳转正常。
|
||||
|
||||
### 4.5 不在本次范围
|
||||
|
||||
- P1 搜索与索引复用设计;
|
||||
- manifest 深度解析(资产类型不匹配提示的进一步细化)。
|
||||
|
||||
## 五、风险与影响
|
||||
|
||||
- grammar 改动只影响高亮层,不影响索引与诊断逻辑;
|
||||
- 搜索路径顺序变化可能影响个别 include 的解析结果(更接近 BAB 真实行为),需在三个真实项目上回归;
|
||||
- 候选表去重策略变化理论上减少补全条目重复,不影响条目正确性。
|
||||
|
||||
## 六、实施结果(2026-08-01)
|
||||
|
||||
已按上述方案完成修复并验证:
|
||||
|
||||
1. **grammar 改为纯注入**:`package.json` 的 `grammars` 条目移除 `"language": "xml"`,内置 XML 语法恢复为主语法,领域关键词以注入方式叠加。
|
||||
2. **引用属性守卫恢复**:新增 `isReferenceAttribute()` 纯函数,诊断与 hover 仅对 `inheritFrom` / 带 `refType` 的属性做未解析引用检查;`Include@type`、`Include@source` 不再误报。
|
||||
3. **Include 源解析优先 `resolveSource`**:hover / 定义跳转 / 文档链接统一先按 BAB 顺序解析;候选表仅作补全来源并按 source 去重、SDK 根目录条目排前。
|
||||
4. **搜索路径对齐 BAB 参数**:`buildSearchPaths` 的 DATA/ART/AUDIO 各补入 `modGranParent` 与 `SDK\Mods`,顺序与 `defaultscript.cs` 的 `/data /art /audio` 一致。
|
||||
|
||||
实测(AttachTest,C: 盘):
|
||||
|
||||
```
|
||||
DATA:Static.xml resolves to: C:\Apps\RA3-MODSDK-X\Static.xml (OK,不再指向 SageXml)
|
||||
DATA:static.xml candidates: 1 -> C:\Apps\RA3-MODSDK-X\Static.xml (去重生效)
|
||||
isReferenceAttribute(Include,type) = false
|
||||
isReferenceAttribute(Include,source) = false
|
||||
CommandSet 引用仍能解析到 LogicCommandSet 定义(无回归)
|
||||
```
|
||||
|
||||
单元测试 24/24 通过(新增:BAB 搜索路径顺序、SDK 根优先于 SageXml、`isReferenceAttribute` 判定)。
|
||||
|
||||
> 注:本环境当前无法访问 `D:\Mods\CoronaMod`(D: 盘不可见),GenEvoTest / Corona 的实机回归待 D: 盘可用后补跑;相关逻辑已被 AttachTest 与单元测试覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 七、问题分析(第二轮,2026-08-01)
|
||||
|
||||
用户在 AttachTest `Guardian Tank\GameObject.xml` 上报了三个新问题,均已修复并验证。
|
||||
|
||||
### 问题 A:`Side="Allies"` 误报"未被定义"
|
||||
|
||||
**现象**:`Side="Allies"` 报 unresolved reference,但 `global.manifest` 中存在 `PlayerTemplate:Allies`。
|
||||
|
||||
**根因**:`Side` 属性确实是引用(`PlayerTemplateWeakRef` → `PlayerTemplate`),但 manifest 解析器依赖 OpenSAGE `AssetType.cs` 的 TypeId 哈希表,而该表**不完整**(实测 `Global.manifest` 11268 个资产中有 2707 个哈希未知,`PlayerTemplate` 即缺失)。未知类型被记成 `#哈希`,无法与 `refType=PlayerTemplate` 匹配。
|
||||
|
||||
**修复**:manifest 资产名本身是 `类型名:ID` 格式(如 `PlayerTemplate:Allies`),新增 `deriveAssetType()`——哈希已知时用哈希;未知时从名称前缀推导类型(资产 ID 不允许含冒号,切分无歧义)。修复后 `Side="Allies"` 正确解析到 `PlayerTemplate@Global.manifest`。
|
||||
|
||||
### 问题 B:`<Weapon>` 报没有 `Ordering` 属性
|
||||
|
||||
**现象**:`WeaponSetUpdate → WeaponSlotTurret → Weapon` 链中 `<Weapon Ordering=...>` 报未知属性;XSD 里 `WeaponSlot_WeaponData` 确实有 `Ordering`。
|
||||
|
||||
**根因**:模型用"元素名→类型"的**全局单映射**,同名元素(如 `<Weapon>` 出现在武器槽、慢速死亡、引用等许多上下文)以"先到先得"注册。`Weapon` 被注册成 `WeaponRef`,因此按全局映射查不到 `Ordering`。这是典型的**上下文相关类型**问题。
|
||||
|
||||
**修复**:新增上下文感知类型解析:
|
||||
- `childTypeOf(父类型, 子元素名)` / `elementTypeIn(父元素, 子元素名)` / `attributesOfType(类型)`;
|
||||
- `resolveElementType(元素)` 沿解析树向上逐层用父类型的子元素声明解析真实类型,失败时回退全局映射。
|
||||
|
||||
修复后 `Weapon`(在 `WeaponSlotTurret` 下)解析为 `WeaponSlot_WeaponData`,`Ordering` 合法;且 `Ordering` 本身不是引用,不再被误判。
|
||||
|
||||
### 问题 C:嵌套 `xi:include`(第 248 行)未处理
|
||||
|
||||
**现象**:`<xi:include href="DATA:Includes/HeadlightDraw2.xml" xpointer="...">` 位于元素内部(非 `AssetDeclaration` 直接子级),原实现只处理根级 `xi:include`,嵌套的一律静默忽略。
|
||||
|
||||
**根因**:索引器 `walk()` 只遍历根的直接子元素处理 `xi:include`。
|
||||
|
||||
**修复**:解析后遍历文档全部元素,对**任意层级**的 `xi:include`:解析 `href`(按 BAB 搜索路径);目标缺失时产生 `include-not-found` 诊断(不再静默);目标存在时记录并 walk 目标文件,使其内容/资产进入索引。`href` 的文档链接(Ctrl+点击)此前已可用。
|
||||
|
||||
### 举一反三的测试
|
||||
|
||||
- `test/typeContext.test.mjs`:用真实结构(GameObject → BehaviorModules → WeaponSetUpdate → WeaponSlotTurret → Weapon)验证上下文类型解析;`childTypeOf` 原语断言。
|
||||
- `test/manifestTypes.test.mjs`:`deriveAssetType` 前缀推导(含无冒号/前导冒号边界);模拟 `PlayerTemplate:Allies` 后 `Side` 引用解析成功;`isReferenceAttributeOfType` 判定。
|
||||
- `test/indexer.test.mjs`:新增嵌套 `xi:include` 断言——目标文件被索引、目标资产可用、缺失目标产生诊断。
|
||||
- AttachTest 实机验证:`Side="Allies"` → `PlayerTemplate@Global.manifest`;`Weapon` 类型 → `WeaponSlot_WeaponData`(含 `Ordering`);`HeadlightDraw2.xml` 进入索引、无相关诊断。
|
||||
|
||||
单元测试 28/28 通过,`.vsix` 已重新打包。
|
||||
|
||||
---
|
||||
|
||||
## 八、问题分析(第三轮,2026-08-01)
|
||||
|
||||
用户在 AttachTest `Guardian Tank\GameObject.xml` 上报了三个问题,均已修复并验证。
|
||||
|
||||
### 问题 A:`Locomotor="..."` 误报 "has no definition of type true"
|
||||
|
||||
**现象**:`LocomotorSet/@Locomotor` 引用报错,且类型显示为 **`true`**。
|
||||
|
||||
**根因**:模型生成器把简单类型的 `xas:isRef="true"` 当成了 `refType` 值。`Locomotor` 的类型是 `AssetReference`(只有 `xas:isRef="true"`、没有 `refType`),于是 refType 被写成字符串 `"true"`,导致任何类型匹配都失败。这是生成器 `refType` 取值逻辑的 bug。
|
||||
|
||||
**修复**:
|
||||
- 生成器只从 `xas:refType` 取 refType,`xas:isRef` 只作为布尔标记 `isRef`(属性条目新增该字段);
|
||||
- `isReferenceAttributeOfType` 对 `refType` 或 `isRef` 都视为引用;
|
||||
- `resolveReferenceTargetsForType` 对**无类型引用**(isRef 且无 refType)不做类型过滤,直接匹配同名 id——正是 `Locomotor` 这类"引用任意已声明资产"的语义。
|
||||
|
||||
修复后 `Locomotor="AlliedAntiVehicleVehicleTech1Locomotor"` 正确解析到 mod 的 `LocomotorTemplate`(以及 static.manifest 中的同名原版资产)。
|
||||
|
||||
### 问题 B:`Name="AUAntiVehicleVehicleTech1_SKN"` 误报 BaseRenderAssetType 未定义
|
||||
|
||||
**现象**:W3D 模型名引用报"no definition of type BaseRenderAssetType",但资产确实存在于 static.manifest。
|
||||
|
||||
**根因**:两个叠加原因:
|
||||
1. manifest 名格式是 `类型:子类型:文件名`(如 `W3dContainer:W3DContainer:AUANTIVEHICLEVEHICLETECH1_SKN`),旧的 id 提取只切掉第一个前缀,得到 `W3DContainer:AUANTIVEHICLEVEHICLETECH1_SKN`,无法匹配 XML 里的 `AUAntiVehicleVehicleTech1_SKN`;
|
||||
2. manifest 哈希表里的类型名是 `W3dContainer`(小写 d),而 XSD 类型名是 `W3DContainer`,大小写不一致导致继承链匹配失败。
|
||||
|
||||
**修复**:
|
||||
- `deriveAssetId` 改为取**最后一个冒号段**(资产 ID 不允许含冒号),兼容 `PlayerTemplate:Allies` 与 `W3dContainer:W3DContainer:...` 两种格式;
|
||||
- `canonicalTypeName` 大小写不敏感规范化,`typeChain` / `isAssignableTo` / 属性查询统一走规范化;manifest 资产入库时类型名也规范化。
|
||||
|
||||
类型匹配严格遵循 XSD 继承链:`W3DContainer` / `W3DMesh` → `BaseRenderAssetType` ✓;`W3DHierarchy` → `BaseAssetType`(**不是** BaseRenderAssetType)✗。实测 `_SKN` 只解析出 `W3DContainer@Static.manifest` 一个候选,碰撞盒(id 带 `.OBBOX` 后缀)与层级自然排除。
|
||||
|
||||
### 问题 C:跳转候选"只能打开文件、不能精确定位"
|
||||
|
||||
**现象**:`Template="AlliedAntiVehicleVehicleTech1Cannon"` 有两个候选——mod 定义和 `SageXml\globaldata\weapon.xml`(由 manifest 的 `manifestSource` 映射而来)。后者只打开文件、不定位。
|
||||
|
||||
**修复**:
|
||||
- 所有 XML 定义的跳转改为**精确范围**:用 `id` 属性值的起止偏移构造 Range(找不到时回退到元素起始标签),mod 与 SageXml 一致;
|
||||
- manifest 定义映射到源码文件时,同样在源码文件内查找同名 id 并精确定位(实测定位到 `weapon.xml:2357`);
|
||||
- 新增配置 `ra3modxml.definitionMode`:`all`(默认,mod + 原版全部列出,mod 优先)或 `project-only`(项目内已定义时只跳 mod 定义)。
|
||||
|
||||
### 举一反三的测试(31/31 通过)
|
||||
|
||||
- `test/manifestTypes.test.mjs`:`deriveAssetId` 最后一段提取(含 W3D/纹理双前缀、无冒号边界);大小写规范化后的 `isAssignableTo`(`W3dContainer`→`BaseRenderAssetType` 为真、`W3dHierarchy` 为假)。
|
||||
- `test/refs.test.mjs`:无类型引用(`LocomotorSet/@Locomotor`)解析到任意声明类型的同名 id;非引用属性仍返回空。
|
||||
- AttachTest 实机:三个原始场景全部按预期(Locomotor 命中 LocomotorTemplate、_SKN 命中 W3DContainer、cannon 双候选均可精确定位)。
|
||||
|
||||
`.vsix` 已重新打包(13:33)。D 盘恢复后照旧可补跑 GenEvoTest / Corona 回归。
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
# 调研结论与实施计划(已按最新代码同步更新)
|
||||
|
||||
> 说明:本文档随实现演进持续同步。最近一次同步(2026-08-01)对齐了实现过程中新增的模块与设计变更:BAB 精确搜索路径、manifest 类型/ID 推导、上下文感知元素类型、无类型引用、精确跳转范围、嵌套 `xi:include`、注入式语法高亮等。
|
||||
|
||||
## 一、调研结论(带证据)
|
||||
|
||||
### 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:isRef="true"` 无 `refType` 时为**无类型引用**(匹配任意已声明资产)。
|
||||
- **同名元素在不同父节点下类型不同**(如 `<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`(u16,5/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 沿解析树逐层解析)
|
||||
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 路径解析(纯 TS,BAB /data /art /audio 顺序)
|
||||
manifestParser.ts .manifest 二进制解析 + 类型/ID 推导(纯 TS)
|
||||
fileScanner.ts 目录遍历缓存 + Include source 候选收集
|
||||
refs.ts 引用目标解析(按 refType / isRef / inheritFrom 过滤,纯 TS)
|
||||
indexer.ts 工作区索引器(资产/Define/流/manifest 合并,LRU 解析缓存)
|
||||
types.ts 共享类型
|
||||
features/
|
||||
completion.ts 补全 provider(元素/属性/值,上下文感知)
|
||||
hover.ts hover provider
|
||||
navigation.ts 定义/引用/文档链接/大纲
|
||||
diagnostics.ts 实时诊断
|
||||
syntaxes/
|
||||
ra3modxml.tmLanguage.json 注入 source.xml 的领域高亮(纯注入,不替换 XML 主语法)
|
||||
tools/
|
||||
xsd-to-model.mjs XSD → schema-model.json
|
||||
extract-asset-types.mjs OpenSAGE AssetType.cs → asset-types.json
|
||||
test/
|
||||
fixtures/minimod 样例 Mod(include 各种情形、同名 ID、嵌套 xi:include、manifest 回退)
|
||||
*.test.mjs 8 个测试文件(xmlParser / 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。
|
||||
3. **manifest 资产建模**:类型优先用哈希表,未知时从名称前缀推导;可引用 ID 取最后冒号段;类型名统一走大小写规范化(`W3dContainer` ↔ `W3DContainer`),类型匹配严格遵循 XSD 继承链。
|
||||
4. **上下文感知元素类型**:同名元素按父元素类型解析(`resolveElementType` 沿解析树逐层 `childTypeOf`,失败回退全局映射),保证 `<Weapon>` 等元素的属性/引用判定正确。
|
||||
5. **引用判定与解析**:`refType` 或 `isRef` 均视为引用;带 `refType` 时严格按类型过滤(同名 ID 不串类型);无类型引用匹配任意声明 ID;`inheritFrom` 按可继承类型过滤。
|
||||
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),重建期间的新请求标记脏并在完成后重跑;状态栏显示进度与统计。
|
||||
|
||||
## 三、实施步骤
|
||||
|
||||
1. [x] 调研(Include 规则、XSD、示例项目、规模、工具链)——见本文档第一部分。
|
||||
2. [x] 脚手架:`package.json`、`tsconfig.json`、esbuild、`.vscodeignore`、README。
|
||||
3. [x] 生成模型:`schema-model.json`(XSD,295 顶层元素 / 1851 类型)与 `asset-types.json`(79 个 AssetType 哈希)。
|
||||
4. [x] 纯 TS 核心:include 解析、manifest 解析、XML 解析封装、索引器、引用解析。
|
||||
5. [x] 功能层:补全、hover、导航、诊断、大纲、高亮 grammar。
|
||||
6. [x] 单测(fixture Mod,31 个用例全绿)+ 编译 + `vsce package` 打包(ra3-mod-xml-0.1.0.vsix,约 259KB)。
|
||||
7. [x] 在 AttachTest / GenEvoTest / Corona 上做冒烟验证,并按真实项目反馈修复问题(详见 `docs/analysis-issues.md` 三轮分析)。
|
||||
|
||||
## 四、验证结果(实测)
|
||||
|
||||
| 项目 | 规模 | 索引耗时 | 资产数 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| AttachTest | 71 文件 | ~0.6s | 35,546(manifest 35,322) | 2 个流(static + mapmetadata) |
|
||||
| GenEvoTest | 24 文件 | ~1.8s | 35,392(manifest 35,322) | 项目 ID(alliedmcv 等)正确收录 |
|
||||
| Corona | 3,448 文件(+ 非 XML 资产路径) | ~54.5s | 55,305(manifest 35,322) | 3 个流、183 个 Define、0 诊断 |
|
||||
|
||||
单元测试覆盖:XML 解析(自闭合/容错/偏移)、include 解析(BAB 顺序、SDK 根优先于 SageXml)、manifest 二进制解析(合成 v5 样本、类型/ID 推导)、索引器(资产/Define/流/缺失 include/嵌套 xi:include)、XSD 模型(上下文类型、`childTypeOf`、大小写规范化)、引用过滤(`Weapon="X"` 只跳 `WeaponTemplate`、无类型引用、`Side="Allies"` 命中 manifest 的 `PlayerTemplate`)。
|
||||
|
||||
> 注:`D:\Mods\CoronaMod` 位于移动硬盘,当前未连接;GenEvoTest / Corona 的回归需在 D: 盘可用时补跑(用户会另行通知)。
|
||||
|
||||
## 五、假设与开放问题
|
||||
|
||||
- 假设 SDK 路径默认 `C:\Apps\RA3-MODSDK-X`(与 prompts 一致),可在设置中修改。
|
||||
- 假设补全/导航以“文本语义分析”为主,不做完整 XSD 校验(BAB 才是权威校验器)。
|
||||
- 开放:是否发布到 VS Code Marketplace(需要 publisher)——本期先保证本地 `vsce package` 可安装。
|
||||
- 开放:嵌套 `xi:include` 内容的"虚拟合并"进父文档(用于父文档内的补全/诊断感知被内联内容)——当前仅保证目标文件可索引、可导航、缺失可诊断。
|
||||
@@ -0,0 +1,91 @@
|
||||
# RA3 Mod XML VSCode 插件 — 情况描述与需求清单
|
||||
|
||||
> 由 `prompts` 中的零散描述整理而成。目标产物:一个用于编辑《命令与征服:红色警戒 3》Mod XML 文件的 VS Code 扩展。
|
||||
|
||||
## 一、情况描述(整理后)
|
||||
|
||||
红警 3 的 Mod 数据以 XML 文件组织,由 EA 的 BinaryAssetBuilder(BAB)编译。一个 Mod 项目通常包含两类数据:
|
||||
|
||||
1. **基础数据(Static Data)**:单位、武器、建筑、贴图、音频、AI、UI 等绝大多数内容。入口文件是 `Data/Mod.xml`。
|
||||
2. **全局数据(Global Data)**:无法放进基础数据里的全局配置(游戏设置、全局单例等)。入口是 `Data/additionalmaps/mapmetadata_*.xml` 这类文件——它们原本是 EA 的地图元数据文件,但因为加载最早且允许写任意标签,被 modder 用作“全局数据”入口。
|
||||
|
||||
XML 之间的组织靠 `<Include>` 标签,共有三种语义:
|
||||
|
||||
| type | 语义 | 说明 |
|
||||
|---|---|---|
|
||||
| `reference` | 引用预编译 manifest | 不展开内容。典型用法是 `DATA:static.xml` / `DATA:global.xml` / `DATA:audio.xml` 这三个占位文件,实际对应 SDK `builtmods` 里已编译的 `static.manifest` / `global.manifest` / `audio.manifest` |
|
||||
| `instance` | 源码级可见 | 不展开进当前文件,但编译时“看得到”目标源码;典型用于 `inheritFrom` 继承 |
|
||||
| `all` | 内容合并 | 等价于把目标文件内容直接复制进来;`Mod.xml` 通过它递归聚合整个 Mod |
|
||||
|
||||
路径解析规则(来自现有工具 `check_duplicate_ids.py` 与编译脚本 `defaultscript.cs`):
|
||||
|
||||
- 带前缀 `DATA:` / `ART:` / `AUDIO:` 的路径按固定搜索顺序查找;
|
||||
- 无前缀的路径相对于当前文件所在目录;
|
||||
- `ART:` 路径支持“文件名前两个小写字母作为子目录”的匹配(如 `JUAntiShip` → `ju/JUAntiShip`)。
|
||||
|
||||
继承机制:`inheritFrom` 让一个元素默认获得目标元素的所有内容;具体合并行为由 `xai:joinAction`(`uri:ea.com:eala:asset:instance` 命名空间)控制,实际项目中出现的取值为 `Replace`、`Remove`。
|
||||
|
||||
全部 XML 语法由 XSD 定义:SDK 自带 `Schemas/xsd/CnC3Types.xsd`(及其 800+ 个子 XSD)。大型 Mod 项目(如 Corona)还会携带自己修改过的 XSD 副本。
|
||||
|
||||
## 二、需求清单
|
||||
|
||||
### P0:近期核心功能
|
||||
|
||||
1. **语法高亮**:为 RA3 Mod XML 提供可读的高亮;重点补充普通 XML 高亮之外的领域标记(如 `$DEFINE` 常量引用、`inheritFrom` 等)。
|
||||
2. **自动补全**:
|
||||
- 元素名(按当前父元素的 XSD 定义补全,含顶层资产元素);
|
||||
- 属性名(按当前元素的 XSD 定义补全,`id` 必填者优先);
|
||||
- 属性值:
|
||||
- 引用型属性(XSD 中带 `xas:refType`)补全已定义的资产 ID;
|
||||
- `inheritFrom` 补全可继承的资产 ID;
|
||||
- 枚举值(XSD `xs:enumeration`);
|
||||
- `$DEFINE` 常量(如 `$CIV_HEALTH_SMALL`);
|
||||
- `<Include source>` 补全可解析的文件路径(`DATA:` / `ART:` / `AUDIO:`)。
|
||||
3. **引用提示(Hover)**:元素/属性悬停显示 XSD 文档、类型、默认值;资产 ID 悬停显示定义位置;`$DEFINE` 悬停显示值与定义位置。
|
||||
4. **引用导航**:
|
||||
- 从引用型属性值跳转到对应资产定义(Go to Definition);
|
||||
- 查找某资产 ID 的所有引用(Find All References);
|
||||
- `<Include source>` / `xi:include href` 直接打开目标文件;
|
||||
- `inheritFrom` 跳转到被继承元素;
|
||||
- 文档大纲:显示文件内的顶层资产元素。
|
||||
5. **错误检查(实时诊断)**:
|
||||
- XML 格式错误(well-formedness);
|
||||
- 未知元素 / 未知属性(相对 XSD 模型);
|
||||
- 缺失必填 `id`(顶层资产);
|
||||
- 重复 ID(同类型 + 同 id,mod 文件之间;覆盖原版 SageXml 不算冲突);
|
||||
- 引用未解析(引用了不存在的资产 ID,可配置是否忽略原版 manifest 中的 ID);
|
||||
- `<Include>` 目标文件找不到、Include 循环;
|
||||
- `$DEFINE` 未定义。
|
||||
|
||||
**补充(manifest 解析,支持 include reference 后的补全/导航/诊断)**:
|
||||
|
||||
- 当 `Mod.xml`(或其他文件)用 `<Include type="reference" source="DATA:static.xml" />` 引用占位文件时,实际内容来自 SDK `builtmods` 下对应的已编译二进制 manifest(`static.manifest` / `global.manifest` / `audio.manifest`)。
|
||||
- 通过解析这些 manifest,可以得到其包含的全部资产(名称、类型、来源文件),从而:
|
||||
- **代码补全**:例如 reference 了 `audio.xml` 后,所有音频资产 ID 都能出现在引用型属性(如 `AudioEventRef`)的补全里;
|
||||
- **引用导航/悬停**:能定位资产来自哪个 manifest、哪个源文件;
|
||||
- **诊断**:能把“引用了 manifest 中的 ID”识别为已解析,而不是误报未解析引用。
|
||||
- manifest 为二进制格式,解析逻辑参考 OpenSAGE `ManifestFile.cs`(用户已在本工作区 `OpenSAGE/` 克隆并切到指定 commit)。关键格式要点:
|
||||
- 头部含版本(5/6/7)、端序标志、各缓冲区大小、资产数量;
|
||||
- 每个资产条目含 `TypeId`(哈希)、`NameOffset`、`SourceFileNameOffset` 等;
|
||||
- `TypeId` 哈希 → 类型名的映射来自 OpenSAGE 的 `AssetType` 枚举(本工作区可提取);
|
||||
- 资产名与源文件名各自存放在独立的空字符结尾字符串缓冲区中。
|
||||
|
||||
### P1:非近期目标(本期不做,但预留扩展点)
|
||||
|
||||
6. **高效搜索**:Mod 项目巨大(Corona 约 7500 个 XML、38MB)时直接全文搜索很慢,需要一个高效的 XML 内容索引机制。
|
||||
7. **索引机制的复用性**:希望索引不仅能服务 VS Code 插件,也能被其他工具(如搜索、静态分析)复用,因此索引/解析核心应设计成与编辑器无关的纯模块。
|
||||
|
||||
## 三、环境与参考资源
|
||||
|
||||
- SDK:`C:\Apps\RA3-MODSDK-X`(含 `defaultscript.cs` 编译脚本、`Schemas/xsd`、`SageXml` 原版源码、`builtmods` 编译产物)。
|
||||
- 中小项目:`C:\Apps\RA3-MODSDK-X\Mods\AttachTest`、`D:\Mods\CoronaMod\mods\mods\GenEvoTest`。
|
||||
- 大型项目:`D:\Mods\CoronaMod\mods\mods\corona`(自带 `xsd/`)。
|
||||
- 现有工具:工作区 `check_duplicate_ids.py`(include 解析与重复 ID 检测的参考实现)。
|
||||
- manifest 格式参考:OpenSAGE `src/OpenSage.Game/Data/StreamFS/ManifestFile.cs`(commit `d45d361`,最新分支已移除该文件)。本机网络受限未能拉取,且 manifest 为压缩/哈希的二进制,本期不实现其解析。
|
||||
|
||||
## 四、验收标准
|
||||
|
||||
- 在 AttachTest / GenEvoTest 上开箱即用(高亮、补全、跳转、诊断)。
|
||||
- 在 Corona 规模的目录上不卡 UI:索引在后台执行、保存文件后增量更新。
|
||||
- 纯解析/索引核心不依赖 VS Code API,可被其他工具复用。
|
||||
- 可用 `vsce package` 打出可安装的 `.vsix`。
|
||||
Reference in New Issue
Block a user