layout: restructure into src/ tests/ android/ scripts/ tools/

- extension/src/{port,platform,codepage} -> src/; native_render -> src/host
  (+ dxt, shaders/); libgr2 -> src/gr2; extension/third_party -> third_party
- extension/tests -> tests/port, libgr2/tests -> tests/gr2
- android-native -> android (build.sh, push-client.sh moved in)
- script -> scripts; tools/40250 -> tools/server; oracle -> tools/granny-oracle;
  perf tools -> tools/perf
- all build trees under build/ (native, release, android, port-gate)
- xrender:: CMake aliases -> mt::; port-map ledger impl paths rewritten

No code changes. ctest 15/15, port_gate macos+android PASS, port_map check 0 errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-29 19:08:19 +09:00
co-authored by Claude Opus 5.5
parent 70710477cf
commit a46093104c
2817 changed files with 13728 additions and 13744 deletions
+10 -10
View File
@@ -36,14 +36,14 @@ oracle 分成两半,混在一起会误判工期:
| 产物 | 位置 | 轨 |
|---|---|---|
| `granny_probe.exe`(读 gr2 → 打 FileInfo) | `oracle/` | Lite |
| `oracle` CLI(`oracle dump <model.gr2> <anim.gr2> <t> -o out.bin`) | `oracle/` | Lite |
| 最小 D3D9 渲染器(骨架+mesh→PNG,不依赖 EterGrnLib) | `oracle/render/` | Lite |
| 锁定的 `granny2.dll` + 判定记录 | `oracle/vendor/` + `oracle/GRANNY-VERSION.md` | Lite |
| dump 二进制格式规范(含坐标约定) | `oracle/FORMAT.md` | Lite |
| `granny_probe.exe`(读 gr2 → 打 FileInfo) | `tools/granny-oracle/` | Lite |
| `oracle` CLI(`oracle dump <model.gr2> <anim.gr2> <t> -o out.bin`) | `tools/granny-oracle/` | Lite |
| 最小 D3D9 渲染器(骨架+mesh→PNG,不依赖 EterGrnLib) | `tools/granny-oracle/render/` | Lite |
| 锁定的 `granny2.dll` + 判定记录 | `tools/granny-oracle/vendor/` + `tools/granny-oracle/GRANNY-VERSION.md` | Lite |
| dump 二进制格式规范(含坐标约定) | `tools/granny-oracle/FORMAT.md` | Lite |
| 噪声地板测量 | `test/noise_floor.json` | Lite |
| 自检脚本(CI Windows runner) | `oracle/selfcheck.*` | Lite |
| `oracle_etergrn` 静态库 + 层② dump | `oracle/full/` | Full(延后)|
| 自检脚本(CI Windows runner) | `tools/granny-oracle/selfcheck.*` | Lite |
| `oracle_etergrn` 静态库 + 层② dump | `tools/granny-oracle/full/` | Full(延后)|
---
@@ -74,7 +74,7 @@ T1(锁 DLL + granny_probe) ─┬─▶ T3(层① dump) ─┬─▶ T5(蒙皮
- 先写 `granny_probe.exe`(~30 行):`GrannyReadEntireFileFromMemory` → `GrannyGetFileInfo` → 打印 `Skeletons/Meshes/Materials/Textures/Animations` 的 count、`FromFileName`、每 skeleton 的骨骼数。
- 收集候选 `granny2.dll`(**2.4.x 优先**,再 2.6.x / 2.9.x / 2.11.x 兜底),各链一遍 `granny_probe` 跑 `warrior_cheongrin.gr2` + 3 个 zone 静态件 + 1 个 `action/*.gr2`。
- 选返回全部合理的那个,`git`-track 进 `oracle/vendor/granny2.dll`,判定写 `oracle/GRANNY-VERSION.md`(列出每个候选的输出)。
- 选返回全部合理的那个,`git`-track 进 `tools/granny-oracle/vendor/granny2.dll`,判定写 `tools/granny-oracle/GRANNY-VERSION.md`(列出每个候选的输出)。
- **AC**(选定 DLL 下):
- [ ] `warrior_cheongrin.gr2`:`Skeletons==1`、`Meshes>=1`、`Animations==0`、`FromFileName` 是可读路径串、骨骼数 ∈ [20, 120](人形合理范围)—— 与 [M0 T4](./M0-gr2-reader.md) 期望值一致。
- [ ] `action/dance_1.gr2`:`Animations==1`、`Duration` ∈ (0, 60] 秒。
@@ -173,8 +173,8 @@ T1(锁 DLL + granny_probe) ─┬─▶ T3(层① dump) ─┬─▶ T5(蒙皮
## DoD 清单(Oracle-Lite)
- [ ] `granny_probe.exe` + `oracle/vendor/granny2.dll` 锁定 + `GRANNY-VERSION.md`(含候选对比)
- [ ] `oracle dump <model.gr2> <anim.gr2> <t>` 可用;`oracle/FORMAT.md` 写清二进制布局 + 坐标约定
- [ ] `granny_probe.exe` + `tools/granny-oracle/vendor/granny2.dll` 锁定 + `GRANNY-VERSION.md`(含候选对比)
- [ ] `oracle dump <model.gr2> <anim.gr2> <t>` 可用;`tools/granny-oracle/FORMAT.md` 写清二进制布局 + 坐标约定
- [ ] T3 正确性锚:t=0 层① == bind pose(`< 1e-4`)
- [ ] T5 正确性锚:bind pose 蒙皮 == 原始顶点
- [ ] T6 确定态截图两次一致 + 视觉 sanity + 换 t 有变化
+6 -6
View File
@@ -45,7 +45,7 @@
| 产物 | 位置 |
|---|---|
| `libgr2` 静态库(demo 子集) | `libgr2/` |
| `libgr2` 静态库(demo 子集) | `src/gr2/` |
| `gr2dump` CLI(结构化 dump + `--sections` + 可选 glTF) | `tools/gr2dump/` |
| `gr2fuzz`(全量 ~9166 解析 + 谓词 + 直方图) | `tools/gr2fuzz/` |
| 格式变体直方图报告 | `test/fuzz-report.json` |
@@ -79,7 +79,7 @@ T4,T5,T6(+可选 T7b) ──▶ T8(gr2dump)
### T1 · `gr2_file` —— header + section table + fixup **[M]** ✅
> **状态:header + section 表解析已实现**(`libgr2/src/gr2_file.cpp`),`gr2dump --sections` 跑通全部 9166 个文件、0 崩溃。
> **状态:header + section 表解析已实现**(`src/gr2/src/gr2_file.cpp`),`gr2dump --sections` 跑通全部 9166 个文件、0 崩溃。
> 剩下的是 **section 解压(Oodle1,见 T2)**和 **fixup 重定位**(需展开后的数据)。
**实测结论**(写进代码注释,替换旧假设):
@@ -105,7 +105,7 @@ T4,T5,T6(+可选 T7b) ──▶ T8(gr2dump)
### T2 · `gr2_decompress` **[M]** —— 已实现并验证
> **状态:done。** `libgr2/src/oodle1.c` —— 从泄露 SDK 的 `granny_oodle1_compression.cpp` + `radlz.c` + `radarith.c` + `arithbit.c` 端口了**解码路径**(自适应算术编码 + LZ)。约 450 行 C。
> **状态:done。** `src/gr2/src/oodle1.c` —— 从泄露 SDK 的 `granny_oodle1_compression.cpp` + `radlz.c` + `radarith.c` + `arithbit.c` 端口了**解码路径**(自适应算术编码 + LZ)。约 450 行 C。
- `format==0` → memcpy;`format==2`(Oodle1)→ `gr2_oodle1_decompress()`;`format==1`(Oodle0)/ `format==4`(BitKnit,全样本 0 个)→ 留桩报错。
- 3-block 结构:`stop0/1/2` = section 的 `First16Bit` / `First8Bit` / `ExpandedDataSize`(32 位 / 16 位 / 8 位 marshalling 区各一块,共享算术流)。
@@ -213,10 +213,10 @@ T4,T5,T6(+可选 T7b) ──▶ T8(gr2dump)
## 门禁(go / no-go)
- **主**:`libgr2` dump vs 真 Granny —— ✅ **达成**。`oracle probe` 的 `GrannyGetFileInfo` 与 `gr2dump` 逐字段一致;`tools/oracle_diff` 对拍 Granny **2.9.12** 的 `GrannyGetWorldPose4x4Array` + `GrannyDeformVertices`:23 用例(13 模型 bind + 8 动画帧,含 v7 / 双 root / 刚体)骨骼矩阵 ≤ 4.6e-5、蒙皮顶点 ≤ 6.5e-5(`test/m2-numeric.json`)。oracle 在 macOS + Wine + MinGW 跑(`oracle/RUNBOOK.md`)。
- **主**:`libgr2` dump vs 真 Granny —— ✅ **达成**。`oracle probe` 的 `GrannyGetFileInfo` 与 `gr2dump` 逐字段一致;`tools/oracle_diff` 对拍 Granny **2.9.12** 的 `GrannyGetWorldPose4x4Array` + `GrannyDeformVertices`:23 用例(13 模型 bind + 8 动画帧,含 v7 / 双 root / 刚体)骨骼矩阵 ≤ 4.6e-5、蒙皮顶点 ≤ 6.5e-5(`test/m2-numeric.json`)。oracle 在 macOS + Wine + MinGW 跑(`tools/granny-oracle/RUNBOOK.md`)。
- **自洽**:T5 bind pose 自洽 —— ✅ `warrior_cheongrin` `6.1e-5` PASS。全量 2013/2017 < 1e-3;4 个 `>=1e-1` **经 oracle 证实 libgr2 正确**(assassin 对 Granny 3.1e-5),是自洽不变量本身对这几个双-root 资产不成立,非 libgr2 bug。
- **fuzz**:T9b 全量 9166 —— ✅ **零崩溃 + 零谓词失败**。
- **变体可实现**:✅ 无 BitKnit / 无 Oodle0;曲线全 `OldCurveType`,degree ≤ 2,T7b 全覆盖。不做的变体见 [`libgr2/README.md`](../../../libgr2/README.md)「明确不做」(均 0 出现,坏输入干净报错不崩)。
- **变体可实现**:✅ 无 BitKnit / 无 Oodle0;曲线全 `OldCurveType`,degree ≤ 2,T7b 全覆盖。不做的变体见 [`src/gr2/README.md`](../../../src/gr2/README.md)「明确不做」(均 0 出现,坏输入干净报错不崩)。
- **产出**:✅ `test/{fuzz-report,m2-numeric,noise_floor}.json` + `test/assets.list` + `test/oracle_dumps/`。
- **noise_floor**:✅ `test/noise_floor.json`。同-DLL 确定性 = 0(byte-identical);跨实现地板 = mat 4.6e-5 / vtx 6.5e-5;跨版本一档待另一个 granny2.dll。
@@ -279,7 +279,7 @@ T4,T5,T6(+可选 T7b) ──▶ T8(gr2dump)
**bind pose 自洽 4 个异常**(`>=1e-1`):`assassin.gr2`(PC + season1,同)、`warrior_rabbit1_backup.gr2`、`hair_11_1.gr2`。
这 4 个都是**多 root skeleton**(如 assassin:`assasin_low` + `Bip01` 两个 root),且 model 的 `InitialPlacement` 非单位。
> **后续更正(oracle 对拍后)**:这不是 libgr2 的 bug。`assassin` bind pose 的 **骨骼世界矩阵 + 蒙皮顶点与真 Granny 2.9.12 逐字段一致(3.1e-5 / 4.6e-5)**(见 [00-oracle](./00-oracle.md) / `oracle/RUNBOOK.md`)。
> **后续更正(oracle 对拍后)**:这不是 libgr2 的 bug。`assassin` bind pose 的 **骨骼世界矩阵 + 蒙皮顶点与真 Granny 2.9.12 逐字段一致(3.1e-5 / 4.6e-5)**(见 [00-oracle](./00-oracle.md) / `tools/granny-oracle/RUNBOOK.md`)。
> `world[i]·InverseWorld4x4[i] ≈ I` 这个**自洽不变量本身**对这几个资产不成立 —— 它们的 `InverseWorld4x4` 是相对「不含 InitialPlacement 的参考」烘的,而 `world = local链 · InitialPlacement`。是资产属性,不是读取错误。
> 自洽检查因此是个**比 oracle 对拍弱的信号**:能抓转置 / 手系 bug,但对「InverseWorld 参考系不一致」会误报。`warrior_cheongrin` 等单 root 模型不受影响(IP 为单位,两者等价)。
+3 -3
View File
@@ -107,17 +107,17 @@
| rigid + deformable mesh 混合 | `engine/skinning.cpp` 按 `mesh.rigid` 分支(单骨刚体 vs 4 权重混合)|
| 非单位 scaleshear 让 `world·invBind` 近似失效 | 全程完整仿射 4x4(`mul4x3` 保 3x3 + 平移);法线用 skin 3x3(scaleshear 严格应逆转置,M2 先近似,记 M4 收紧)|
| ε 拍脑袋 | 待 oracle T8 的 noise_floor × 10 |
| ~~数值门禁未跑~~ | **已跑**:macOS + Wine + MinGW 交叉编译的 `oracle.exe`(Granny 2.9.12)vs libgr2,骨骼矩阵 + 蒙皮顶点全 ≤ 6.5e-5(float 累积误差量级)。`oracle/RUNBOOK.md` / `tools/oracle_diff` |
| ~~数值门禁未跑~~ | **已跑**:macOS + Wine + MinGW 交叉编译的 `oracle.exe`(Granny 2.9.12)vs libgr2,骨骼矩阵 + 蒙皮顶点全 ≤ 6.5e-5(float 累积误差量级)。`tools/granny-oracle/RUNBOOK.md` / `tools/oracle_diff` |
## DoD 清单
- [x] `.msm`/`.msa` 解析 dump 与源文本核对通过(`formats/textscript` token 树 + `msa`/`msm`)
- [x] **骨骼世界矩阵 vs oracle 层① `max|Δ| ≤ 4.6e-5`** —— `oracle/run-diff-suite.sh` 跑 **23 用例**(warrior_cheongrin/lord ×4 LOD、assassin 双 root、shaman_lord **v7**、redthief2、snakeman、rabbit_backup、ox_01;dance_1/attack/run/wait @ 多个 t)。`test/m2-numeric.json`。
- [x] **骨骼世界矩阵 vs oracle 层① `max|Δ| ≤ 4.6e-5`** —— `tools/granny-oracle/run-diff-suite.sh` 跑 **23 用例**(warrior_cheongrin/lord ×4 LOD、assassin 双 root、shaman_lord **v7**、redthief2、snakeman、rabbit_backup、ox_01;dance_1/attack/run/wait @ 多个 t)。`test/m2-numeric.json`。
- [x] **蒙皮顶点 vs oracle `‖Δ‖ ≤ 6.5e-5`**(同上,vs `GrannyDeformVertices`;法线用同 3x3,Granny 也不做逆转置 —— `engine/skinning.cpp`)
- [x] `test/noise_floor.json`:同-DLL 确定性 0,跨实现地板 mat 4.6e-5 / vtx 6.5e-5,ε=1e-3
- [x] **`app/render_fuzz` 全量渲染烟测**:9166 个 `.gr2` 全过真管线(bgfx + `Scene::build` + DXT 解码 + CPU 蒙皮 + `submit`;skinned 4055 / anim-only 5109 / rigid 2)——**0 崩溃 / 0 空场景 / 0 NaN 姿势**(`test/render-fuzz.json`)。
- **顺带修的 libgr2 bug**:`redthief_general/{back,front}_damage.gr2` 的 finger track 控制点在文件里就是 `NaN`。`read_old_curve` 现在遇到非有限 knot/control 整条曲线弃(回退 bind),不把 NaN 灌进蒙皮链。
- 抽样截图里「模型扭曲」的都是 **render_fuzz 把某物种的动画 retarget 到别的 rig**(如怪物动画播到 warrior 身上,或 warrior dance 播到怪物身上)—— 按名 retarget 到 bind pose 比例不同的骨架,肢体会拉长。**不是 libgr2/engine bug**(同 rig 的 warrior + dance_1/attack/run/wait 已对拍 Granny 1e-5)。render_fuzz 已改成只在 rig 匹配时才应用动画,其余画 bind pose。
- 抽样截图里「模型扭曲」的都是 **render_fuzz 把某物种的动画 retarget 到别的 rig**(如怪物动画播到 warrior 身上,或 warrior dance 播到怪物身上)—— 按名 retarget 到 bind pose 比例不同的骨架,肢体会拉长。**不是 src/gr2/engine bug**(同 rig 的 warrior + dance_1/attack/run/wait 已对拍 Granny 1e-5)。render_fuzz 已改成只在 rig 匹配时才应用动画,其余画 bind pose。
- [~] 挂点武器变换 —— `gr2::sample_pose` 出每骨世界矩阵,武器 = `world[handBone]·mount`;未接进 demo(无 .msm 武器数据),逻辑就绪
- [x] LOD 0–3 共享骨架:`warrior_cheongrin` lod_01/02/03 与 base 均 75 骨、骨骼名 + ParentIndex 逐项一致 → 切 LOD 骨骼索引不变、无跳变
- [x] `xrender-demo` 按 `Space` 播 `dance_1`:CPU LBS 每帧更新 dynamic VB,姿势连贯(`test/golden/m2-*.png`)