0.1.24 fix inheritFrom="type:id"
This commit is contained in:
@@ -2128,3 +2128,90 @@ hover / 跳转 / 诊断 / FAR)在第三十一轮补齐,见下。
|
||||
- `docs/plan.md`:simple-content 文本引用说明补充第三十一轮扩展;
|
||||
- `docs/features-reference-counts.md`:引用语义说明补充“含 simpleContent 复杂
|
||||
类型”。
|
||||
|
||||
---
|
||||
|
||||
## 三十二、问题分析(2026-08-11):限定引用值 `类型:ID` 未被归一化导致误报未解析
|
||||
|
||||
### 现象
|
||||
|
||||
Corona `Data\Allied\Units\AlliedFutureTankX-1\AudioEvent.xml`:
|
||||
|
||||
```xml
|
||||
<Includes>
|
||||
<Include type="instance" source="DATA:SageXml/Sounds/BaseSoundEffect.xml" />
|
||||
</Includes>
|
||||
|
||||
<AudioEvent
|
||||
id="ALL_FutureTank_ArmPrimaryWeapon"
|
||||
inheritFrom="AudioEvent:BaseSoundEffect"
|
||||
... />
|
||||
```
|
||||
|
||||
报 `Unresolved reference "AudioEvent:BaseSoundEffect"`,提示当前索引中未找到;
|
||||
但 `SageXml\Sounds\BaseSoundEffect.xml` 里确实存在 `<AudioEvent id="BaseSoundEffect" />`,
|
||||
且 `instance` include 会被索引器与文档局部 overlay 正常 walk。
|
||||
|
||||
### 根因
|
||||
|
||||
插件只在 **manifest 一侧**做了“资产名 `类型:ID` → 裸 ID(取最后冒号段)”
|
||||
的归一化(`manifestParser.deriveAssetId`);**XML 引用值一侧**直接用原始值查
|
||||
`assetsById`。于是 `AudioEvent:BaseSoundEffect` 被当成完整 ID 精确匹配,
|
||||
索引里只有 `BaseSoundEffect`,必然查不到。
|
||||
|
||||
实测最小复现:索引中包含 `AudioEvent@BaseSoundEffect`(origin=sdk),
|
||||
`assetsById.get("audioevent:basesoundeffect")` 返回 NOT FOUND,
|
||||
`resolveReferenceTargetsForType` 返回 0 目标。
|
||||
|
||||
### 影响面(真实数据统计)
|
||||
|
||||
这是原版数据的**普遍写法**,不是用户笔误:
|
||||
|
||||
| 属性 | SageXml | Corona Data | 典型值 |
|
||||
|---|---|---:|---|
|
||||
| `inheritFrom` | 5,483 | 3,219 | `AudioEvent:BaseSoundEffect` |
|
||||
| `Sound`(AudioEntry) | 39 | 98 | `AudioEvent:JAP_Refinery_Select` |
|
||||
| `Side` | 67 | 195 | `PlayerTemplate:Allies` |
|
||||
| `ParticleTexture` | 2 | 2 | `Texture:FXLenzFlare01` |
|
||||
|
||||
前缀全部是**定义资产的具体类型**(manifest 全名格式),而 XSD refType 可能是
|
||||
基类(如 `Sound` 的 refType 是 `BaseAudioEventInfo`,前缀是 `AudioEvent`)。
|
||||
两侧数据的 `id="类型:ID"` 出现次数均为 0,说明定义侧永远是裸 ID,取最后冒号段
|
||||
没有歧义。少数 `Sound="AudioEvent:MammothTankTurretMoveLoop"` 等引用在 SDK
|
||||
源码与三个 manifest 中都找不到定义,是原版数据自身的死引用,归一化后仍会
|
||||
(且应该)继续报未解析。
|
||||
|
||||
### 修复
|
||||
|
||||
1. `refs.ts` 新增 `normalizeReferenceId(value)`:取最后冒号段(与
|
||||
`deriveAssetId` 同一规则;冒号后为空时保留原值,避免半输入误匹配),
|
||||
应用到 `resolveReferenceTargetsForType` 与 `resolveContentReferenceTargets`。
|
||||
2. `referenceIndex.ts` 的 `buildReferenceIndex` 与 `features/references.ts`
|
||||
的 `definitionsForReference` 同样归一化,FAR / CodeLens / 引用 peek 与
|
||||
诊断、hover、跳转保持一致。
|
||||
3. `records.ts` 不修改:记录仍保存原始值与原始偏移,导航/悬停范围不受影响,
|
||||
缓存格式与版本不变。
|
||||
4. `completion.ts` 的 `assetIdItems`:当前输入段含 `:` 时按冒号后片段过滤,
|
||||
补全项 label/insertText 为“已输入前缀 + 裸 ID”(如 `AudioEvent:Base…`
|
||||
→ `AudioEvent:BaseSoundEffect`);未输入前缀时保持裸 ID 补全,不特判任何
|
||||
类型、也不改变默认补全形态。
|
||||
|
||||
### 测试(219 → 226 全绿)
|
||||
|
||||
- `refs.test.mjs`:`normalizeReferenceId` 边界;qualified `inheritFrom`
|
||||
解析、裸 ID 不变、错误类型前缀仍被 selfType 过滤;qualified 属性
|
||||
(`Sound` / `Side`)与 simple-content(`AudioFile:...`)引用解析;
|
||||
- `referenceIndex.test.mjs`:qualified 记录计入反向索引(FAR / CodeLens 桶);
|
||||
- `indexer.test.mjs`:临时项目集成——`instance` include 进 SageXml +
|
||||
`inheritFrom="AudioEvent:BaseSoundEffect"`,断言定义入库、解析命中、
|
||||
反向索引落点(即用户报告的完整场景);
|
||||
- `completion.test.mjs`:`AudioEvent:Base…` 补全为
|
||||
`AudioEvent:BaseSoundEffect` 且替换范围只覆盖当前段;无前缀仍补裸 ID;
|
||||
- `contentFeatures.test.mjs`:qualified `inheritFrom` 不产生未解析诊断,
|
||||
Ctrl+点击精确定位到裸 ID 定义。
|
||||
|
||||
### 文档同步
|
||||
|
||||
- `docs/requirements.md`:情况描述补充 `类型:ID` 引用写法与归一化规则;
|
||||
- `docs/plan.md`:设计决策 5 补充限定引用值归一化,实施记录追加第 29 轮;
|
||||
- `CHANGELOG.md`:0.1.24。
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
- `inheritFrom`(按元素自身类型过滤);
|
||||
- 无 `refType` 的 `isRef` 属性(按同名 ID 匹配任意声明类型)。
|
||||
|
||||
引用值本身支持原版/Mod 常用的 manifest 风格全名 `类型:ID`
|
||||
(`inheritFrom="AudioEvent:BaseSoundEffect"`、`Sound="AudioEvent:..."`、
|
||||
`Side="PlayerTemplate:Allies"`):解析与反向索引先按 `normalizeReferenceId`
|
||||
取最后冒号段,再执行上述类型过滤;记录里的原始值与偏移保持不变。
|
||||
|
||||
`inheritFrom` 对 `BaseAssetType` 系资产是通用合法属性(XSD 只在
|
||||
`BaseInheritableAsset` 声明,但原版数据在 `FXList` 等类型上也使用)。这里的
|
||||
“合法属性”判定与“设计上应显示引用计数”的 `referenceTargetTypes()` 是分开的:
|
||||
|
||||
+9
-1
@@ -142,7 +142,7 @@ test/
|
||||
跨重建复用(详见设计决策 14)。
|
||||
3. **manifest 资产建模**:类型优先用哈希表,未知时从名称前缀推导;可引用 ID 取最后冒号段;类型名统一走大小写规范化(`W3dContainer` ↔ `W3DContainer`),类型匹配严格遵循 XSD 继承链。`assetsById` 按 id 汇总**全部类型**的定义,去重身份为 `(type, file, line)`,同一 manifest 中同名但不同类型的美术资产(如 `W3DHierarchy:AUMCV_HOVER` 与 `W3DContainer:AUMCV_HOVER`)必须全部保留,避免 `Model@Name` 这类 `BaseRenderAssetType` 引用因先到的非渲染类型而被误判为未解析。
|
||||
4. **上下文感知元素类型**:同名元素按父元素类型解析(`resolveElementType` 沿解析树逐层 `childTypeOf`,失败回退全局映射),保证 `<Weapon>` 等元素的属性/引用判定正确。
|
||||
5. **引用判定与解析**:`refType` 或 `isRef` 均视为引用;带 `refType` 时严格按类型过滤(同名 ID 不串类型);`inheritFrom` 按可继承类型过滤。**局部作用域例外**(`isLocalReferenceAttribute`):`id` 是元素自身的定义点——无 refType 或 refType 与自身类型兼容时不检查、不解析(`RoadObject@id→Road` 这类跨类型 id 引用保留检查);Poid 类型属性是管线局部引用,全局索引无法判定,不检查、不解析。
|
||||
5. **引用判定与解析**:`refType` 或 `isRef` 均视为引用;带 `refType` 时严格按类型过滤(同名 ID 不串类型);`inheritFrom` 按可继承类型过滤。**局部作用域例外**(`isLocalReferenceAttribute`):`id` 是元素自身的定义点——无 refType 或 refType 与自身类型兼容时不检查、不解析(`RoadObject@id→Road` 这类跨类型 id 引用保留检查);Poid 类型属性是管线局部引用,全局索引无法判定,不检查、不解析。**限定引用值**:原版/Mod 数据常用 manifest 风格全名 `类型:ID`(如 `AudioEvent:BaseSoundEffect`、`PlayerTemplate:Allies`);XML 定义侧 id 从不含冒号,所以查询统一走 `normalizeReferenceId`(取最后冒号段,与 manifest 的 `deriveAssetId` 同一规则)后再按 refType/selfType 过滤,records 仍保留原始值与偏移供导航/悬停使用。
|
||||
6. **重复 ID 诊断**:与 `check_duplicate_ids.py` 一致——SageXml 不参与冲突判定,mod 覆盖原版视为正常。
|
||||
7. **未解析引用诊断**:按设置严重级别报告(默认 warning);类型不匹配时给出明确文案("有同名 ID 但类型不匹配")。`definitionMode` 设置控制跳转候选:`all`(mod + 原版全部列出,mod 优先)或 `project-only`。
|
||||
8. **跳转精度**:XML 定义跳转到 `id` 属性值的精确 Range;manifest 定义映射到源码文件(如 SageXml)时也在文件内精确定位;找不到再回退到记录行。
|
||||
@@ -424,6 +424,14 @@ test/
|
||||
修正;SageXml 源缺失时保持 manifest-only,文件存在但 id 被删时降级
|
||||
到文件顶部;测试 178 → 184;分析见 `docs/analysis-issues.md`
|
||||
二十八。
|
||||
29. [x] 限定引用值 `类型:ID` 归一化(2026-08-11,v0.1.24):新增
|
||||
`refs.normalizeReferenceId`(取最后冒号段,与 manifest `deriveAssetId`
|
||||
同一规则),应用到属性引用、simple-content 引用、语义反向索引与
|
||||
FAR/CodeLens 的 `definitionsForReference`;records 仍保存原始值与
|
||||
偏移;补全在已输入 `类型:` 前缀时按冒号后片段过滤并保留前缀;
|
||||
实测 SageXml 5483 / Corona 3219 处 `inheritFrom="AudioEvent:..."`
|
||||
等受限引用全部修复;测试 219 → 226;分析见
|
||||
`docs/analysis-issues.md` 三十二。
|
||||
|
||||
## 四、验证结果(实测)
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ XML 之间的组织靠 `<Include>` 标签,共有三种语义:
|
||||
|
||||
继承机制:`inheritFrom` 让一个元素默认获得目标元素的所有内容;具体合并行为由 `xai:joinAction`(`uri:ea.com:eala:asset:instance` 命名空间)控制,实际项目中出现的取值为 `Replace`、`Remove`。XSD 只在 `BaseInheritableAsset` 上显式声明 `inheritFrom`,但原版与 Corona 数据也在 `FXList`、`AIMicroManagerData` 等 `BaseAssetType` 系资产上使用它,插件按“所有资产类型的通用属性”处理。
|
||||
|
||||
引用值写法:原版与 Corona 数据中的引用既可以是裸 ID,也可以是 manifest 风格的全名 `类型:ID`(如 `inheritFrom="AudioEvent:BaseSoundEffect"`、`Sound="AudioEvent:JAP_Refinery_Select"`、`Side="PlayerTemplate:Allies"`、`ParticleTexture="Texture:FXLenzFlare01"`)。XML 定义侧的 `id` 从不含冒号,因此插件统一按“最后冒号段”归一化后再匹配定义,类型过滤仍按 refType / 元素自身类型执行。
|
||||
|
||||
全部 XML 语法由 XSD 定义:SDK 自带 `Schemas/xsd/CnC3Types.xsd`(及其 800+ 个子 XSD)。大型 Mod 项目(如 Corona)还会携带自己修改过的 XSD 副本。
|
||||
|
||||
## 二、需求清单
|
||||
@@ -38,6 +40,7 @@ XML 之间的组织靠 `<Include>` 标签,共有三种语义:
|
||||
- 属性值:
|
||||
- 引用型属性(XSD 中带 `xas:refType`)补全已定义的资产 ID;
|
||||
- `inheritFrom` 补全可继承的资产 ID;
|
||||
- 引用值支持 `类型:ID` 前缀写法:已输入 `AudioEvent:` 时按冒号后的 ID 过滤,插入时保留已输入的前缀;未输入前缀时保持裸 ID 补全;
|
||||
- 枚举值(XSD `xs:enumeration`);
|
||||
- `$DEFINE` 常量(如 `$CIV_HEALTH_SMALL`);
|
||||
- `<Include source>` 补全可解析的文件路径(`DATA:` / `ART:` / `AUDIO:`)。
|
||||
@@ -61,6 +64,7 @@ XML 之间的组织靠 `<Include>` 标签,共有三种语义:
|
||||
- 缺失必填 `id`(顶层资产);
|
||||
- 重复 ID(同类型 + 同 id,mod 文件之间;覆盖原版 SageXml 不算冲突);
|
||||
- 引用未解析(引用了不存在的资产 ID,可配置是否忽略原版 manifest 中的 ID);
|
||||
- 引用值带 `类型:` 前缀时先归一化为裸 ID 再判定(如 `AudioEvent:BaseSoundEffect` → `BaseSoundEffect`),前缀不影响类型过滤;
|
||||
- simple-content 引用元素的文本未解析(同属性引用规则,仅带 refType 的类型)。
|
||||
- `<Include>` 目标文件找不到、Include 循环;
|
||||
- `$DEFINE` 未定义。
|
||||
|
||||
Reference in New Issue
Block a user