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:
co-authored by
Claude Opus 5.5
parent
70710477cf
commit
a46093104c
@@ -1,17 +1,17 @@
|
||||
# M0 工具。只依赖 libgr2。
|
||||
add_executable(gr2dump gr2dump/main.cpp)
|
||||
target_link_libraries(gr2dump PRIVATE xrender::libgr2)
|
||||
target_link_libraries(gr2dump PRIVATE mt::libgr2)
|
||||
|
||||
add_executable(gr2fuzz gr2fuzz/main.cpp)
|
||||
target_link_libraries(gr2fuzz PRIVATE xrender::libgr2)
|
||||
target_link_libraries(gr2fuzz PRIVATE mt::libgr2)
|
||||
|
||||
# M2 门禁·主:libgr2 vs oracle 逐字段对拍(读 oracle/oracle.exe 产出的 .bin)
|
||||
# M2 门禁·主:libgr2 vs oracle 逐字段对拍(读 tools/granny-oracle/oracle.exe 产出的 .bin)
|
||||
add_executable(oracle_diff oracle_diff/main.cpp)
|
||||
target_link_libraries(oracle_diff PRIVATE xrender::libgr2)
|
||||
target_link_libraries(oracle_diff PRIVATE mt::libgr2)
|
||||
|
||||
# bisect:dump 每骨 world 3x3 的 det / shear / scale spread(查 dance_1 头塌陷)
|
||||
add_executable(anim_probe anim_probe/main.cpp)
|
||||
target_link_libraries(anim_probe PRIVATE xrender::libgr2)
|
||||
target_link_libraries(anim_probe PRIVATE mt::libgr2)
|
||||
|
||||
|
||||
# gr2dump --gltf 需要 tinygltf(M0 T8 辅助、可延后):
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# oracle —— 独立 Windows 工程(不在主 CMake 树内)。见 ../docs/steps/00-oracle.md、RUNBOOK.md
|
||||
#
|
||||
# cmake -S oracle -B tools/granny-oracle/build -DGRANNY_SDK=<泄露 SDK 根>
|
||||
# cmake --build tools/granny-oracle/build --config Release
|
||||
#
|
||||
# GRANNY_SDK 默认指向本仓库同级的 MobileSource 里的 Granny-3D-SDK-main。
|
||||
# 链 granny2_x64.lib(import lib);运行时需要对应的 granny2_x64.dll 在 PATH / exe 目录。
|
||||
# ⚠ 这个 DLL 是 SDK 2.9.12。资产由 2.4.0.7 导出(M0 T2 实测)。容器 + 组合公式在 2.4–2.11
|
||||
# 稳定,但 B 样条边界 / 少数曲线细节可能有差异 —— 先跑 selfcheck(T7)确认坐标约定,
|
||||
# 再决定是否要换成 2.4.x 的 runtime DLL(见 GRANNY-VERSION.md)。
|
||||
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(xrender_oracle C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
set(GRANNY_SDK "${CMAKE_SOURCE_DIR}/../../../MobileSource/Cross Platform/Granny-3D-SDK-main"
|
||||
CACHE PATH "泄露 Granny SDK 根(含 include/ 和 lib/win64/)")
|
||||
|
||||
if(NOT EXISTS "${GRANNY_SDK}/include/granny.h")
|
||||
message(FATAL_ERROR "找不到 ${GRANNY_SDK}/include/granny.h —— 传 -DGRANNY_SDK=<SDK 根>")
|
||||
endif()
|
||||
|
||||
add_executable(oracle oracle.c)
|
||||
target_include_directories(oracle PRIVATE "${GRANNY_SDK}/include")
|
||||
|
||||
if(WIN32)
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
target_link_libraries(oracle PRIVATE "${GRANNY_SDK}/lib/win64/granny2_x64.lib")
|
||||
set(_dll "${GRANNY_SDK}/lib/win64/granny2_x64.dll")
|
||||
else()
|
||||
target_link_libraries(oracle PRIVATE "${GRANNY_SDK}/lib/win32/granny2.lib")
|
||||
set(_dll "${GRANNY_SDK}/lib/win32/granny2.dll")
|
||||
endif()
|
||||
# 把 DLL 拷到 exe 旁边,方便直接跑 / Wine
|
||||
add_custom_command(TARGET oracle POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_dll}" "$<TARGET_FILE_DIR:oracle>")
|
||||
else()
|
||||
message(WARNING
|
||||
"非 Windows:SDK 只提供 win32/win64/wii/ps3/xenon 的 granny2 预编译库。\n"
|
||||
" 选项 A:在 Windows 机器 / VM 上 build+run。\n"
|
||||
" 选项 B:Wine(oracle 只做文件 IO + 数学 + 写二进制,不碰 GPU,Wine 够用)——\n"
|
||||
" 用 MinGW 交叉编译或直接在 Wine 里跑 MSVC,链 granny2_x64.lib。\n"
|
||||
" 选项 C(stretch):从 SDK source/ 移植 winxx/ 平台层出一个 POSIX libgranny。\n"
|
||||
" 见 RUNBOOK.md。")
|
||||
endif()
|
||||
@@ -0,0 +1,45 @@
|
||||
# oracle dump 二进制格式
|
||||
|
||||
> `oracle.c`(`oracle dump`)产出。libgr2 侧对拍读取器(`tools/oracle_diff/`)必须与本文件一致。
|
||||
> `oracle dump` 把**层① 世界矩阵**和**mesh N 蒙皮顶点**写进**同一个文件**:先文件头,
|
||||
> 再 `kind=1` 段,紧接着 `kind=3` 段(`--mesh` 未命中时 `vertex_count=0`)。全部小端。
|
||||
|
||||
## 文件头(所有 dump 共用)
|
||||
|
||||
```
|
||||
magic "XORC" 4B
|
||||
version u32 1
|
||||
kind u32 1=层① 2=层② 3=蒙皮顶点 1
|
||||
coord u32 坐标约定编码: 1
|
||||
bit0 手系 (0=左手 1=右手)
|
||||
bit1 单位 (0=原始 1=米)
|
||||
bit2 根变换已 apply
|
||||
granny_ver char[16] 选定 granny2.dll 版本串
|
||||
model_path len-prefixed str
|
||||
anim_path len-prefixed str
|
||||
t f32 采样时刻
|
||||
```
|
||||
|
||||
## kind=1/2 段(骨骼世界矩阵,紧跟文件头)
|
||||
|
||||
```
|
||||
bone_count u32
|
||||
matrices f32[bone_count][16] GrannyGetWorldPose4x4Array 直出(骨骼空间→世界),行主序 Granny 原生
|
||||
```
|
||||
|
||||
> ⚠ 不是 `GrannyGetWorldPoseComposite4x4Array` —— 那个返回 `InverseWorld4x4·world` = **蒙皮矩阵**(bind pose 时 ≈ I)。
|
||||
> `GrannyBuildWorldPose` 的 `Offset4x4` 传的是 model 的 `InitialPlacement`(`GrannyGetModelInitialPlacement4x4`),
|
||||
> 与 libgr2 `skeleton.initial_placement` 一致;不传的话非单位 IP 的模型(如 assassin 双 root)会差一个平移。
|
||||
|
||||
## kind=3 段(mesh N 蒙皮顶点,紧跟 kind=1 段)
|
||||
|
||||
```
|
||||
vertex_count u32 (--mesh 未命中 → 0,无后续)
|
||||
positions f32[vertex_count][3] GrannyDeformVertices(PositionNormal) 后的位置
|
||||
```
|
||||
|
||||
## 坐标约定
|
||||
|
||||
**M0/oracle 全程 raw**:`coord` = 手系按选定 DLL 实际输出、单位=原始、根变换未 apply。
|
||||
libgr2 的 `gr2::Mat4` 声明同一约定(见 `docs/steps/M0-gr2-reader.md` 「约定」)。
|
||||
basis fix / 单位缩放是 M1 渲染时才做的事,不进 dump。
|
||||
@@ -0,0 +1,53 @@
|
||||
# Granny 版本判定(oracle T1)
|
||||
|
||||
## 已知(M0 T2)
|
||||
|
||||
- 资产由 **Granny Standard Exporter, SDK version 2.4.0.7** 导出(从解压后的 gr2 内嵌串读到)。
|
||||
- 9166 个 gr2 里 **9155 个文件格式 v6、11 个 v7**(magic:v6=`GRNFileMV_Old`,v7=`GRNFileMV_32Bit_LittleEndian`)。
|
||||
- libgr2 自研,`bind_pose_self_check` 全量 2013/2017 多骨 skeleton < 1e-3(`warrior_cheongrin` 6.1e-5)。
|
||||
|
||||
## 候选 runtime
|
||||
|
||||
| DLL | 来源 | 状态 |
|
||||
|---|---|---|
|
||||
| `granny2_x64.dll` 2.9.12 | 泄露 SDK `lib/win64/` | **先用这个**(SDK 自带、能直接链)。容器 + 组合公式在 2.4–2.11 稳定。 |
|
||||
| granny2.dll 2.4.x | 需另找(对应 2.4.0.7 exporter 的 runtime) | 若 2.9.12 在曲线 / B 样条边界上和 libgr2 分歧且 Blender 仲裁站 libgr2 这边,换这个。 |
|
||||
| granny2.dll 2.11.x | m2dev-client-src 头文件写的版本 | 兜底候选。 |
|
||||
|
||||
## 判定步骤(跑 `oracle probe`)
|
||||
|
||||
对每个候选 DLL,跑:
|
||||
|
||||
```
|
||||
oracle probe "assets/PC/ymir work/pc/warrior/warrior_cheongrin.gr2"
|
||||
oracle probe "assets/PC/ymir work/pc/warrior/action/dance_1.gr2"
|
||||
oracle probe "assets/Zone/ymir work/zone/.../<某静态件>.gr2"
|
||||
```
|
||||
|
||||
AC(选定 DLL 下):
|
||||
|
||||
- `warrior_cheongrin.gr2`:`Skeletons=1`、`Meshes=5`、`Animations=0`、`Models=1`、
|
||||
`Materials=4`、`Textures=2`、skeleton 0 = 75 骨、骨骼名以 `Bip01` 开头
|
||||
—— 与 libgr2(`gr2dump`)**逐字段一致**。
|
||||
- `dance_1.gr2`:`Animations=1`、`Duration≈28.333334`、`TrackGroups=1`。
|
||||
- 静态件:`Skeletons=0` 或 1、`Meshes≥1`。
|
||||
- 3+ 文件全过;其余候选 DLL 的失败表现记进本表。
|
||||
|
||||
## 选定结果 —— ✅ 已跑(2026-08-29)
|
||||
|
||||
- **选定:`granny2_x64.dll` 2.9.12.0**(泄露 SDK `lib/win64/`)。
|
||||
- 环境:macOS 26.4 / Apple Silicon / wine-stable 11.0(Rosetta)/ MinGW-w64 交叉编译(`build-wine.sh`)。
|
||||
- **`oracle probe` vs `gr2dump` 逐字段一致**:
|
||||
|
||||
| 文件 | Textures | Materials | Skeletons | Meshes | Models | Animations | skel0 骨骼数 | 骨骼名/父索引 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `warrior_cheongrin.gr2` | 2=2 | 4=4 | 1=1 | 5=5 | 1=1 | 0=0 | 75=75 | 逐项一致(Bip01…)|
|
||||
| `assassin.gr2` | 3=3 | 6=6 | 1=1 | 17=17 | 1=1 | 0=0 | 95=95 | 逐项一致(含双 root:`assasin_low` + `Bip01`)|
|
||||
| `dance_1.gr2` | — | — | 0 | 0 | 0 | 1=1 | — | Duration 28.333334,TrackGroups 1 |
|
||||
|
||||
- `FromFileName` 连非法编码的 Korean 路径都逐字节一致(libgr2 读原始字节 = Granny)。
|
||||
|
||||
**结论**:2.4 导出的资产在 2.9.12 runtime 下 `GrannyGetFileInfo` 完全正常,libgr2 与之逐字段对齐。
|
||||
容器 + 组合公式在 2.4–2.9 稳定这一假设成立,**不需要换 2.4.x runtime**。
|
||||
|
||||
`oracle.exe` + `granny2_x64.dll` 不进仓库(体积 + 版权);`build-wine.sh` 一条命令重建。
|
||||
@@ -0,0 +1,43 @@
|
||||
# tools/granny-oracle/ —— Windows 真值源工具
|
||||
|
||||
**独立 Windows/MSVC 工程,不在主 CMake 树内。** 施工文档:[`../docs/steps/00-oracle.md`](../docs/steps/00-oracle.md)
|
||||
|
||||
## 两条轨
|
||||
|
||||
| 轨 | 产物 | 状态 |
|
||||
|---|---|---|
|
||||
| **Oracle-Lite**(只用 `granny2.dll` C API) | `granny_probe.exe` · `oracle dump` CLI · 最小 D3D9 渲染器 | 必做,关键路径 |
|
||||
| **Oracle-Full**(+ 抽出的 `EterGrnLib`) | `oracle_etergrn` + 层② dump | 延后,demo 不需要 |
|
||||
|
||||
## 目录
|
||||
|
||||
```
|
||||
tools/granny-oracle/
|
||||
oracle.c ★ Oracle-Lite 全部:probe(T1)+ dump(T3 层① / T5 蒙皮顶点)
|
||||
CMakeLists.txt Windows/MSVC 工程,链泄露 SDK 的 granny2_x64.lib
|
||||
RUNBOOK.md 怎么 build+run(原生 Windows / Wine / stretch: POSIX 移植)
|
||||
GRANNY-VERSION.md 版本判定 + 候选对比(待跑 probe 填实)
|
||||
FORMAT.md dump 二进制布局 + 坐标约定
|
||||
vendor/ 锁定的 granny2 DLL(T1 选定后 git-track)
|
||||
render/ 独立最小 D3D9 → PNG(T6,不依赖 EterGrnLib;待写)
|
||||
selfcheck.* T7 自检(Windows CI runner;逻辑见 RUNBOOK.md「selfcheck」)
|
||||
full/ Oracle-Full:oracle_etergrn + 层② dump(T2/T4,延后)
|
||||
```
|
||||
|
||||
## 状态
|
||||
|
||||
- [x] `oracle.c` —— probe + dump(层① 世界矩阵 + mesh N 蒙皮顶点)
|
||||
- [x] **macOS + Wine 跑通**(`build-wine.sh` = MinGW-w64 交叉编译 + wine-stable)。Granny **2.9.12.0**。
|
||||
- [x] **T1** DLL 选定 + probe vs `gr2dump` 逐字段一致(`GRANNY-VERSION.md`)
|
||||
- [x] **T3/T5** 层① dump + 蒙皮顶点 dump 跑通;**M2 门禁·主对拍 PASS**:
|
||||
- `warrior_cheongrin` bind + `dance_1` @ t={0, 4.7, 11.333, 20}:骨骼矩阵 `max|Δ| ≤ 4.1e-5`、蒙皮顶点 `max‖Δ‖ ≤ 6.5e-5`
|
||||
- `assassin`(双 root,非单位 InitialPlacement)bind:`3.1e-5 / 4.6e-5`
|
||||
- 对拍工具 `tools/oracle_diff`(libgr2 侧读 `.bin` 逐字段比)
|
||||
- [ ] T8 noise_floor.json(跑同 dump 100 次 + 换相邻 DLL 一次,取 `max|Δ|`)
|
||||
- [ ] T6 D3D9 确定态截图 harness(M1 视觉对拍用)
|
||||
- [ ] Oracle-Full(层②,`EterGrnLib`)—— 延后
|
||||
|
||||
## 输出
|
||||
|
||||
- dump → `../test/oracle_dumps/`(格式见 `FORMAT.md`)
|
||||
- 噪声地板 → `../test/noise_floor.json`(T8,M2 用它设 ε)
|
||||
@@ -0,0 +1,88 @@
|
||||
# oracle 运行手册(Oracle-Lite)
|
||||
|
||||
`oracle.c` 只用 `granny2` 的公开 C API(`granny.h`):文件 IO + 数学 + 写二进制,**不碰 GPU / D3D / EterGrnLib**。
|
||||
所以除了原生 Windows,Wine 也能跑。
|
||||
|
||||
## 依赖
|
||||
|
||||
- `granny.h` + `granny2_x64.lib` + `granny2_x64.dll` —— 泄露 SDK 里就有:
|
||||
`MobileSource/Cross Platform/Granny-3D-SDK-main/{include,lib/win64}/`
|
||||
(32 位版在 `lib/win32/`。)
|
||||
- 这套 DLL 是 **SDK 2.9.12**。资产由 **2.4.0.7** 导出(M0 T2 从解压后的 gr2 里读到)。
|
||||
容器格式 + 组合公式(`BuildCompositeTransform4x4` / `ColumnMatrixMultiply4x3` / B 样条系数)
|
||||
在 2.4–2.11 稳定,libgr2 的 self-check 6.1e-5 已印证。差异风险在 B 样条边界 / 少数曲线细节。
|
||||
→ 先跑 `selfcheck`(下)确认坐标约定和 t=0 == bind pose,再决定是否换 2.4.x runtime(见 `GRANNY-VERSION.md`)。
|
||||
|
||||
## A. 原生 Windows(MSVC)
|
||||
|
||||
```bat
|
||||
cmake -S oracle -B oracle\build -DGRANNY_SDK="C:\path\to\Granny-3D-SDK-main"
|
||||
cmake --build oracle\build --config Release
|
||||
oracle\build\Release\oracle.exe probe "assets\PC\ymir work\pc\warrior\warrior_cheongrin.gr2"
|
||||
```
|
||||
|
||||
## B. macOS + Wine —— ✅ 已验证(macOS 26.4 / Apple Silicon)
|
||||
|
||||
```sh
|
||||
brew install mingw-w64
|
||||
# wine-stable cask 需要 sudo 装 gstreamer 依赖;无 sudo 时直接解包用:
|
||||
tar -xf "$(ls ~/Library/Caches/Homebrew/downloads/*wine-stable*.tar.xz)" -C /tmp/wine
|
||||
xattr -rc "/tmp/wine/Wine Stable.app"
|
||||
WINE="/tmp/wine/Wine Stable.app/Contents/Resources/wine/bin/wine"
|
||||
|
||||
# 编译(脚本封装了 MinGW 的坑:granny.h 的 __int64 关键字)
|
||||
tools/granny-oracle/build-wine.sh # → tools/granny-oracle/oracle.exe + tools/granny-oracle/granny2_x64.dll
|
||||
|
||||
# ⚠ WINEPREFIX 不能在 /tmp(Wine 拒绝在非属主目录建 prefix)
|
||||
export WINEPREFIX="$HOME/.wine-oracle" WINEDEBUG=-all
|
||||
"$WINE" wineboot --init # 首次,Rosetta 下 3–8 分钟
|
||||
|
||||
cd oracle
|
||||
GR2='Z:\Users\me\...\assets\PC\ymir work\pc\warrior\warrior_cheongrin.gr2'
|
||||
"$WINE" oracle.exe probe "$GR2"
|
||||
"$WINE" oracle.exe dump "$GR2" - 0.0 \
|
||||
-o 'Z:\Users\me\...\xrender-poc\test\oracle_dumps\cheongrin_bind_t0.bin' --mesh 4
|
||||
"$WINE" oracle.exe dump "$GR2" 'Z:\...\action\dance_1.gr2' 11.333 \
|
||||
-o 'Z:\...\test\oracle_dumps\cheongrin_dance1_t11.333.bin' --mesh 4
|
||||
```
|
||||
|
||||
坑:① MinGW GCC 不认 `granny.h` 里的 `__int64` → `build-wine.sh` 用 `-D__int64="long long"`;
|
||||
② Wine prefix 不能放 `/tmp`(非属主);③ wine-stable 首启会刷一大堆 MoltenVK 日志到 stderr,无害,`WINEDEBUG=-all` 挡不住它,grep 掉即可。
|
||||
|
||||
## C.(stretch)从 SDK source/ 移植 POSIX libgranny
|
||||
|
||||
`Granny-3D-SDK-main/source/` 有全套源码(62k 行)+ CMakeLists,但重度 Windows 化
|
||||
(`winxx/win64/*.cpp`、`winxx_granny_windows.cpp`、`WINDOWS_IGNORE_PACKING_MISMATCH`)。
|
||||
`wii/`、`wiiu/`、`ps3/` 目录里有 `ansi_*` 的通用实现可参考。真要做是几小时的平台层移植,
|
||||
不在关键路径 —— 先走 A / B。
|
||||
|
||||
## 对拍(`tools/oracle_diff`)
|
||||
|
||||
```sh
|
||||
build/tools/oracle_diff <model.gr2> <anim.gr2|-> <dump.bin> [--t <sec>] [--mesh N]
|
||||
```
|
||||
|
||||
libgr2 侧用 `gr2::sample_pose` / `bind_pose` + 手写 LBS 算一遍,报:
|
||||
- **gate 1**:骨骼世界矩阵 vs oracle 层① `max|Δ|`
|
||||
- **gate 2**:mesh N 蒙皮顶点 vs oracle `max‖Δ‖`
|
||||
- verdict:阈值 1e-3(noise_floor 就绪前)
|
||||
|
||||
**已跑结果**(Granny 2.9.12.0 vs libgr2):
|
||||
|
||||
| 用例 | mat max\|Δ\| | vtx max‖Δ‖ |
|
||||
|---|---|---|
|
||||
| `warrior_cheongrin` bind | 1.5e-5 | 4.6e-5 |
|
||||
| `warrior_cheongrin` + `dance_1` @ t=0/4.7/11.333/20 | ≤ 4.1e-5 | ≤ 6.5e-5 |
|
||||
| `assassin` bind(双 root + 非单位 InitialPlacement)| 3.1e-5 | 4.6e-5 |
|
||||
|
||||
全部 ≈ float32 累积误差 → **libgr2 的类型树 / 世界姿势 / OldCurve 求值 / CPU LBS 与真 Granny 数值一致**。
|
||||
|
||||
> 关键:`oracle.c` 的 `GrannyBuildWorldPose` 要把 model 的 `InitialPlacement` 当 `Offset4x4` 传
|
||||
> (`GrannyGetModelInitialPlacement4x4`),否则非单位 InitialPlacement 的模型(如 assassin)会差一个平移。
|
||||
> libgr2 的 `skeleton.initial_placement` 做同一件事。踩过一次。
|
||||
|
||||
## 产出
|
||||
|
||||
- dump → `test/oracle_dumps/`(布局见 `FORMAT.md`)
|
||||
- 噪声地板 → `test/noise_floor.json`(`oracle dump` 跑同 `(model,anim,t)` 100 次 + 换一版
|
||||
相邻 DLL 一次,取矩阵 / 顶点的 `max|Δ|`;M2 的 ε = 这个 ×10)
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# 在 macOS 上用 MinGW-w64 交叉编译 oracle.exe(链泄露 SDK 的 granny2_x64.lib),
|
||||
# 用 Wine 跑。见 RUNBOOK.md。实测 macOS 26.4 / Apple Silicon / wine-stable 11.0 / Granny 2.9.12.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
SDK="${GRANNY_SDK:-../../../MobileSource/Cross Platform/Granny-3D-SDK-main}"
|
||||
[ -f "$SDK/include/granny.h" ] || { echo "找不到 $SDK/include/granny.h(传 GRANNY_SDK=<SDK 根>)"; exit 1; }
|
||||
|
||||
CC="${MINGW_CC:-x86_64-w64-mingw32-gcc}"
|
||||
command -v "$CC" >/dev/null || { echo "没装 MinGW:brew install mingw-w64"; exit 1; }
|
||||
|
||||
# 关键:granny.h 在 _WIN64 分支里 `#define GRANNY_64BIT_TYPE __int64`,
|
||||
# MinGW GCC 默认不认 __int64 关键字 → 用 -D__int64="long long" 顶掉。
|
||||
"$CC" -O2 -std=gnu11 -fms-extensions -D'__int64=long long' \
|
||||
-I "$SDK/include" \
|
||||
oracle.c "$SDK/lib/win64/granny2_x64.lib" \
|
||||
-o oracle.exe
|
||||
|
||||
cp "$SDK/lib/win64/granny2_x64.dll" ./
|
||||
echo "built: $(pwd)/oracle.exe (+ granny2_x64.dll)"
|
||||
echo
|
||||
echo "跑(Wine):"
|
||||
echo " export WINEPREFIX=\$HOME/.wine-oracle # ⚠ 别放 /tmp:Wine 拒绝在非属主目录建 prefix"
|
||||
echo " wine wineboot --init # 首次,Rosetta 下较慢"
|
||||
echo " wine oracle.exe probe 'Z:\\path\\to\\warrior_cheongrin.gr2'"
|
||||
echo " wine oracle.exe dump 'Z:\\...\\model.gr2' 'Z:\\...\\anim.gr2' <t> -o 'Z:\\...\\out.bin' --mesh N"
|
||||
echo
|
||||
echo "对拍:build/tools/oracle_diff <model.gr2> <anim.gr2|-> <out.bin> --mesh N"
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"granny": "2.9.12.0",
|
||||
"threshold": {"mat": 1e-3, "vtx": 1e-3},
|
||||
"cases": [
|
||||
{"model":"warrior_cheongrin.gr2","anim":"-","t":0,"mesh":4,"mat":1.5e-05,"vtx":4.6e-05,"status":"PASS"},
|
||||
{"model":"warrior_cheongrin_lod_01.gr2","anim":"-","t":0,"mesh":4,"mat":1.5e-05,"vtx":4.6e-05,"status":"PASS"},
|
||||
{"model":"warrior_cheongrin_lod_02.gr2","anim":"-","t":0,"mesh":4,"mat":1.5e-05,"vtx":4.6e-05,"status":"PASS"},
|
||||
{"model":"warrior_cheongrin_lod_03.gr2","anim":"-","t":0,"mesh":4,"mat":1.5e-05,"vtx":4.6e-05,"status":"PASS"},
|
||||
{"model":"warrior_lord.gr2","anim":"-","t":0,"mesh":1,"mat":1.5e-05,"vtx":1.5e-05,"status":"PASS"},
|
||||
{"model":"warrior_lord_lod_01.gr2","anim":"-","t":0,"mesh":1,"mat":1.5e-05,"vtx":1.5e-05,"status":"PASS"},
|
||||
{"model":"warrior_lord_lod_02.gr2","anim":"-","t":0,"mesh":1,"mat":1.5e-05,"vtx":1.5e-05,"status":"PASS"},
|
||||
{"model":"warrior_lord_lod_03.gr2","anim":"-","t":0,"mesh":1,"mat":1.5e-05,"vtx":1.5e-05,"status":"PASS"},
|
||||
{"model":"assassin.gr2","anim":"-","t":0,"mesh":16,"mat":3.1e-05,"vtx":4.6e-05,"status":"PASS"},
|
||||
{"model":"shaman_lord.gr2","anim":"-","t":0,"mesh":0,"mat":7.6e-06,"vtx":3.8e-05,"status":"PASS"},
|
||||
{"model":"redthief2_soldier2_lod_01.gr2","anim":"-","t":0,"mesh":4,"mat":0,"vtx":0,"status":"PASS"},
|
||||
{"model":"snakeman_bow_lod_01.gr2","anim":"-","t":0,"mesh":0,"mat":3.1e-05,"vtx":6.1e-05,"status":"PASS"},
|
||||
{"model":"snakeman_bow_lod_01.gr2","anim":"-","t":0,"mesh":1,"mat":3.1e-05,"vtx":3.2e-05,"status":"PASS"},
|
||||
{"model":"warrior_rabbit1_backup.gr2","anim":"-","t":0,"mesh":0,"mat":1.5e-05,"vtx":3.9e-05,"status":"PASS"},
|
||||
{"model":"ox_01.gr2","anim":"-","t":0,"mesh":0,"mat":0,"vtx":0,"status":"PASS"},
|
||||
{"model":"warrior_cheongrin.gr2","anim":"dance_1.gr2","t":0,"mesh":4,"mat":2.3e-05,"vtx":3.9e-05,"status":"PASS"},
|
||||
{"model":"warrior_cheongrin.gr2","anim":"dance_1.gr2","t":4.7,"mesh":4,"mat":3.1e-05,"vtx":5e-05,"status":"PASS"},
|
||||
{"model":"warrior_cheongrin.gr2","anim":"dance_1.gr2","t":11.333,"mesh":4,"mat":4.1e-05,"vtx":5.8e-05,"status":"PASS"},
|
||||
{"model":"warrior_cheongrin.gr2","anim":"dance_1.gr2","t":20,"mesh":4,"mat":3.1e-05,"vtx":6.5e-05,"status":"PASS"},
|
||||
{"model":"warrior_cheongrin.gr2","anim":"attack.gr2","t":0.3,"mesh":4,"mat":4.5e-05,"vtx":5.9e-05,"status":"PASS"},
|
||||
{"model":"warrior_cheongrin.gr2","anim":"run.gr2","t":0.5,"mesh":4,"mat":2.3e-05,"vtx":5e-05,"status":"PASS"},
|
||||
{"model":"warrior_lord.gr2","anim":"dance_1.gr2","t":10,"mesh":1,"mat":4.6e-05,"vtx":6.4e-05,"status":"PASS"},
|
||||
{"model":"warrior_lord.gr2","anim":"wait.gr2","t":1.0,"mesh":1,"mat":4.6e-05,"vtx":4.8e-05,"status":"PASS"}
|
||||
],
|
||||
"summary": {"total": 23, "passed": 23, "worst_mat": 4.6e-05, "worst_vtx": 6.5e-05}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/* oracle —— Windows 真值源(Oracle-Lite)。见 ../docs/steps/00-oracle.md
|
||||
*
|
||||
* oracle probe <file.gr2>
|
||||
* → 打 GrannyGetFileInfo 的 count / FromFileName / 每 skeleton 骨骼数 + 前几个骨骼名
|
||||
*
|
||||
* oracle dump <model.gr2> <anim.gr2|-> <t> -o out.bin [--mesh N]
|
||||
* → 层① 世界矩阵(GrannyGetWorldPoseComposite4x4Array 直出)+ mesh N 蒙皮顶点
|
||||
* → 二进制布局见 ../FORMAT.md;libgr2 侧对拍读取器必须与之一致
|
||||
*
|
||||
* 只用 granny2 的公开 C API(granny.h)。链 granny2_x64.lib(见 CMakeLists.txt / RUNBOOK.md)。
|
||||
* 不碰 D3D、不碰 EterGrnLib —— 那是 Oracle-Full(延后)。
|
||||
*/
|
||||
#include <granny.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define ORC_MAGIC "XORC"
|
||||
#define ORC_VERSION 1u
|
||||
#define KIND_LAYER1 1u
|
||||
#define KIND_SKINV 3u
|
||||
|
||||
static void die(const char *msg) { fprintf(stderr, "oracle: %s\n", msg); exit(2); }
|
||||
|
||||
static const char *granny_version_string(void) {
|
||||
/* 运行期版本(链接到的 granny2.dll 实际报的)。编译期宏是 GrannyProduct*Version。*/
|
||||
const char *rt = GrannyGetVersionString();
|
||||
return rt ? rt : "unknown";
|
||||
}
|
||||
|
||||
/* ── probe ─────────────────────────────────────────────────────────── */
|
||||
static int cmd_probe(const char *path) {
|
||||
granny_file *f = GrannyReadEntireFile(path);
|
||||
if (!f) { fprintf(stderr, "probe: cannot read %s\n", path); return 1; }
|
||||
granny_file_info *fi = GrannyGetFileInfo(f);
|
||||
if (!fi) { fprintf(stderr, "probe: GrannyGetFileInfo NULL\n"); return 1; }
|
||||
|
||||
printf("file : %s\n", path);
|
||||
printf("granny runtime : %s\n", granny_version_string());
|
||||
printf("FromFileName : %s\n", fi->FromFileName ? fi->FromFileName : "(null)");
|
||||
printf("Textures=%d Materials=%d Skeletons=%d VertexDatas=%d TriTopologies=%d "
|
||||
"Meshes=%d Models=%d TrackGroups=%d Animations=%d\n",
|
||||
fi->TextureCount, fi->MaterialCount, fi->SkeletonCount, fi->VertexDataCount,
|
||||
fi->TriTopologyCount, fi->MeshCount, fi->ModelCount, fi->TrackGroupCount,
|
||||
fi->AnimationCount);
|
||||
|
||||
for (int s = 0; s < fi->SkeletonCount; ++s) {
|
||||
granny_skeleton *sk = fi->Skeletons[s];
|
||||
printf(" skeleton %d: %d bones : ", s, sk->BoneCount);
|
||||
for (int b = 0; b < sk->BoneCount && b < 6; ++b)
|
||||
printf("[%d]%s(p=%d) ", b, sk->Bones[b].Name, sk->Bones[b].ParentIndex);
|
||||
printf("%s\n", sk->BoneCount > 6 ? "..." : "");
|
||||
}
|
||||
for (int a = 0; a < fi->AnimationCount; ++a)
|
||||
printf(" animation %d: '%s' Duration=%.6f TimeStep=%.6f TrackGroups=%d\n",
|
||||
a, fi->Animations[a]->Name, fi->Animations[a]->Duration,
|
||||
fi->Animations[a]->TimeStep, fi->Animations[a]->TrackGroupCount);
|
||||
|
||||
GrannyFreeFile(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── dump helpers ──────────────────────────────────────────────────── */
|
||||
static void wr_u32(FILE *o, uint32_t v) { fwrite(&v, 4, 1, o); }
|
||||
static void wr_f32(FILE *o, float v) { fwrite(&v, 4, 1, o); }
|
||||
static void wr_str(FILE *o, const char *s) {
|
||||
uint32_t n = (uint32_t)(s ? strlen(s) : 0);
|
||||
wr_u32(o, n);
|
||||
if (n) fwrite(s, 1, n, o);
|
||||
}
|
||||
|
||||
/* 与 ../FORMAT.md「文件头」一致 */
|
||||
static void write_header(FILE *o, uint32_t kind, uint32_t coord,
|
||||
const char *model_path, const char *anim_path, float t) {
|
||||
fwrite(ORC_MAGIC, 1, 4, o);
|
||||
wr_u32(o, ORC_VERSION);
|
||||
wr_u32(o, kind);
|
||||
wr_u32(o, coord);
|
||||
char gv[16]; memset(gv, 0, sizeof gv);
|
||||
{ const char *v = granny_version_string(); size_t n = strlen(v);
|
||||
memcpy(gv, v, n < 15 ? n : 15); }
|
||||
fwrite(gv, 1, 16, o);
|
||||
wr_str(o, model_path);
|
||||
wr_str(o, anim_path ? anim_path : "");
|
||||
wr_f32(o, t);
|
||||
}
|
||||
|
||||
/* ── dump ──────────────────────────────────────────────────────────── */
|
||||
static int cmd_dump(int argc, char **argv) {
|
||||
if (argc < 4) die("dump <model.gr2> <anim.gr2|-> <t> -o out.bin [--mesh N]");
|
||||
const char *model_path = argv[0];
|
||||
const char *anim_path = strcmp(argv[1], "-") == 0 ? NULL : argv[1];
|
||||
float t = (float)atof(argv[2]);
|
||||
const char *out_path = NULL;
|
||||
int mesh_index = 0;
|
||||
for (int i = 3; i < argc; ++i) {
|
||||
if (!strcmp(argv[i], "-o") && i + 1 < argc) out_path = argv[++i];
|
||||
else if (!strcmp(argv[i], "--mesh") && i + 1 < argc) mesh_index = atoi(argv[++i]);
|
||||
}
|
||||
if (!out_path) die("dump: 缺 -o out.bin");
|
||||
|
||||
granny_file *mf = GrannyReadEntireFile(model_path);
|
||||
if (!mf) die("dump: model 读失败");
|
||||
granny_file_info *mfi = GrannyGetFileInfo(mf);
|
||||
if (!mfi || mfi->ModelCount < 1) die("dump: model 无 Models[0]");
|
||||
|
||||
granny_model_instance *mi = GrannyInstantiateModel(mfi->Models[0]);
|
||||
if (!mi) die("dump: GrannyInstantiateModel 失败");
|
||||
granny_skeleton *skel = GrannyGetSourceSkeleton(mi);
|
||||
int bone_count = skel->BoneCount;
|
||||
|
||||
granny_file *af = NULL;
|
||||
if (anim_path) {
|
||||
af = GrannyReadEntireFile(anim_path);
|
||||
if (!af) die("dump: anim 读失败");
|
||||
granny_file_info *afi = GrannyGetFileInfo(af);
|
||||
if (!afi || afi->AnimationCount < 1) die("dump: anim 无 Animations[0]");
|
||||
granny_control *ctrl = GrannyPlayControlledAnimation(0.0f, afi->Animations[0], mi);
|
||||
if (!ctrl) die("dump: GrannyPlayControlledAnimation 失败");
|
||||
}
|
||||
|
||||
GrannySetModelClock(mi, t);
|
||||
|
||||
/* Offset4x4 = model 的 InitialPlacement(客户端渲染时也这么给)。
|
||||
* libgr2 侧 gr2::sample_pose 用 skeleton.initial_placement 做同一件事。 */
|
||||
granny_real32 placement[16];
|
||||
GrannyGetModelInitialPlacement4x4(mfi->Models[0], placement);
|
||||
|
||||
granny_local_pose *lp = GrannyNewLocalPose(bone_count);
|
||||
granny_world_pose *wp = GrannyNewWorldPose(bone_count);
|
||||
/* 先填 rest(无 track 的骨骼用它);有动画再让 SampleModelAnimations 覆盖动的那些。 */
|
||||
GrannyBuildRestLocalPose(skel, 0, bone_count, lp);
|
||||
if (af) GrannySampleModelAnimations(mi, 0, bone_count, lp);
|
||||
GrannyBuildWorldPose(skel, 0, bone_count, lp, placement, wp);
|
||||
|
||||
/* 层① = 每骨「骨骼空间 → 世界」的原始世界矩阵(GrannyGetWorldPose4x4Array)。
|
||||
* 注意:GrannyGetWorldPoseComposite4x4Array 返回的是 InverseWorld4x4·world = 蒙皮矩阵,
|
||||
* 不是这里要的。 */
|
||||
granny_matrix_4x4 *world = GrannyGetWorldPose4x4Array(wp);
|
||||
|
||||
/* coord:raw(手系按 DLL 输出、单位=原始、根变换未 apply)。见 FORMAT.md。 */
|
||||
const uint32_t coord = 0u;
|
||||
|
||||
FILE *o = fopen(out_path, "wb");
|
||||
if (!o) die("dump: 打不开输出文件");
|
||||
write_header(o, KIND_LAYER1, coord, model_path, anim_path, t);
|
||||
wr_u32(o, (uint32_t)bone_count);
|
||||
for (int b = 0; b < bone_count; ++b)
|
||||
fwrite(&world[b][0][0], 4, 16, o);
|
||||
|
||||
/* kind=3:mesh_index 的蒙皮顶点位置。照 SDK tutorial(wii_basic.cpp CreateMesh/Render):
|
||||
* deformer(输入=mesh 自带顶点类型, 输出=PNT332)
|
||||
* 矩阵缓冲 = GrannyGetWorldPoseComposite4x4Array(= InverseWorld·world = 蒙皮矩阵)
|
||||
* 索引 = GrannyGetMeshBindingToBoneIndices(mesh 骨骼槽 → model 骨骼) */
|
||||
if (mfi->MeshCount > mesh_index) {
|
||||
granny_mesh *mesh = mfi->Meshes[mesh_index];
|
||||
int vcount = GrannyGetMeshVertexCount(mesh);
|
||||
|
||||
typedef struct { float p[3], n[3], uv[2]; } pnt332;
|
||||
pnt332 *dst = (pnt332 *)malloc(sizeof(pnt332) * vcount);
|
||||
|
||||
granny_matrix_4x4 *composite = GrannyGetWorldPoseComposite4x4Array(wp);
|
||||
granny_mesh_binding *mb = GrannyNewMeshBinding(mesh, skel, skel);
|
||||
granny_int32x const *toBone = GrannyGetMeshBindingToBoneIndices(mb);
|
||||
|
||||
granny_mesh_deformer *def = GrannyNewMeshDeformer(
|
||||
GrannyGetMeshVertexType(mesh), GrannyPNT332VertexType,
|
||||
GrannyDeformPositionNormal, GrannyDontAllowUncopiedTail);
|
||||
if (def) {
|
||||
GrannyDeformVertices(def, toBone, (const granny_real32 *)composite,
|
||||
vcount, GrannyGetMeshVertices(mesh), dst);
|
||||
} else {
|
||||
fprintf(stderr, "dump: no deformer for mesh %d vertex type\n", mesh_index);
|
||||
}
|
||||
|
||||
wr_u32(o, (uint32_t)vcount);
|
||||
for (int v = 0; v < vcount; ++v)
|
||||
fwrite(dst[v].p, 4, 3, o);
|
||||
|
||||
if (def) GrannyFreeMeshDeformer(def);
|
||||
GrannyFreeMeshBinding(mb);
|
||||
free(dst);
|
||||
} else {
|
||||
wr_u32(o, 0);
|
||||
}
|
||||
|
||||
fclose(o);
|
||||
printf("dump: wrote %s bones=%d t=%.6f granny=%s\n",
|
||||
out_path, bone_count, t, granny_version_string());
|
||||
|
||||
GrannyFreeWorldPose(wp);
|
||||
GrannyFreeLocalPose(lp);
|
||||
GrannyFreeModelInstance(mi);
|
||||
if (af) GrannyFreeFile(af);
|
||||
GrannyFreeFile(mf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr,
|
||||
"usage:\n"
|
||||
" oracle probe <file.gr2>\n"
|
||||
" oracle dump <model.gr2> <anim.gr2|-> <t> -o out.bin [--mesh N]\n");
|
||||
return 2;
|
||||
}
|
||||
if (!strcmp(argv[1], "probe")) {
|
||||
if (argc < 3) die("probe <file.gr2>");
|
||||
return cmd_probe(argv[2]);
|
||||
}
|
||||
if (!strcmp(argv[1], "dump")) return cmd_dump(argc - 2, argv + 2);
|
||||
die("未知子命令(probe / dump)");
|
||||
return 2;
|
||||
}
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
# M2 门禁·主:对测试资产集跑 oracle dump + oracle_diff,汇总 tools/granny-oracle/m2-numeric.json。
|
||||
# 前置:tools/granny-oracle/build-wine.sh 已产出 oracle.exe;WINEPREFIX 已 init(见 RUNBOOK.md);
|
||||
# oracle_diff 已构建(cmake -B build/tools -DMT_BUILD_TOOLS=ON)。
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
REPO="$(pwd)"
|
||||
ASSETS="$(cd "${MT_ASSETS:-$REPO/assets}" && pwd)"
|
||||
DUMPS="$REPO/build/oracle_dumps"
|
||||
DIFF=""
|
||||
for c in "$REPO/build/tools/oracle_diff"; do
|
||||
[ -x "$c" ] && DIFF="$c" && break
|
||||
done
|
||||
[ -n "$DIFF" ] || { echo "oracle_diff not built — run: cmake -B build/tools -DMT_BUILD_TOOLS=ON && cmake --build build/tools"; exit 2; }
|
||||
mkdir -p "$DUMPS"
|
||||
|
||||
WINE="${WINE:-/tmp/wine/Wine Stable.app/Contents/Resources/wine/bin/wine}"
|
||||
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-oracle}" WINEDEBUG="${WINEDEBUG:--all}"
|
||||
|
||||
# Z: 盘映射到 /
|
||||
winpath() { printf 'Z:%s' "$(printf '%s' "$1" | sed 's#/#\\#g')"; }
|
||||
|
||||
# 用例:<model 相对 assets> | <anim 相对 assets,或 -> | <t> | <mesh idx>
|
||||
CASES=$(cat <<'EOF'
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin.gr2|-|0|4
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin_lod_01.gr2|-|0|4
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin_lod_02.gr2|-|0|4
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin_lod_03.gr2|-|0|4
|
||||
PC/ymir work/pc/warrior/warrior_lord.gr2|-|0|1
|
||||
PC/ymir work/pc/warrior/warrior_lord_lod_01.gr2|-|0|1
|
||||
PC/ymir work/pc/warrior/warrior_lord_lod_02.gr2|-|0|1
|
||||
PC/ymir work/pc/warrior/warrior_lord_lod_03.gr2|-|0|1
|
||||
PC/ymir work/pc/assassin/assassin.gr2|-|0|16
|
||||
PC/ymir work/pc/shaman/shaman_lord.gr2|-|0|0
|
||||
metin2_patch_dragon_rock_mobs/ymir work/monster2/redthief2_soldier2/redthief2_soldier2_lod_01.gr2|-|0|4
|
||||
monster2/ymir work/monster2/snakeman_bow/snakeman_bow_lod_01.gr2|-|0|0
|
||||
monster2/ymir work/monster2/snakeman_bow/snakeman_bow_lod_01.gr2|-|0|1
|
||||
metin2_patch_easter1/ymir work/pc/warrior/warrior_rabbit1_backup.gr2|-|0|0
|
||||
Zone/ymir work/zone/oxevent/ox_01.gr2|-|0|0
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin.gr2|PC/ymir work/pc/warrior/action/dance_1.gr2|0|4
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin.gr2|PC/ymir work/pc/warrior/action/dance_1.gr2|4.7|4
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin.gr2|PC/ymir work/pc/warrior/action/dance_1.gr2|11.333|4
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin.gr2|PC/ymir work/pc/warrior/action/dance_1.gr2|20|4
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin.gr2|PC/ymir work/pc/warrior/general/attack.gr2|0.3|4
|
||||
PC/ymir work/pc/warrior/warrior_cheongrin.gr2|PC/ymir work/pc/warrior/general/run.gr2|0.5|4
|
||||
PC/ymir work/pc/warrior/warrior_lord.gr2|PC/ymir work/pc/warrior/action/dance_1.gr2|10|1
|
||||
PC/ymir work/pc/warrior/warrior_lord.gr2|PC/ymir work/pc/warrior/general/wait.gr2|1.0|1
|
||||
EOF
|
||||
)
|
||||
|
||||
key_for() { echo "$1|$2|$3|$4" | md5 -q | cut -c1-12; }
|
||||
|
||||
# ── 1. dump(并行,4 个一批)──────────────────────────────────────────
|
||||
run_dump() {
|
||||
local model="$1" anim="$2" t="$3" mesh="$4" out="$5"
|
||||
local wm; wm="$(winpath "$ASSETS/$model")"
|
||||
local wa="-"; [ "$anim" != "-" ] && wa="$(winpath "$ASSETS/$anim")"
|
||||
local wo; wo="$(winpath "$out")"
|
||||
"$WINE" "$REPO/tools/granny-oracle/oracle.exe" dump "$wm" "$wa" "$t" -o "$wo" --mesh "$mesh" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
echo "== oracle dump =="
|
||||
i=0
|
||||
while IFS='|' read -r model anim t mesh; do
|
||||
[ -z "$model" ] && continue
|
||||
k=$(key_for "$model" "$anim" "$t" "$mesh")
|
||||
out="$DUMPS/suite_$k.bin"
|
||||
run_dump "$model" "$anim" "$t" "$mesh" "$out" &
|
||||
i=$((i+1)); [ $((i % 4)) -eq 0 ] && wait
|
||||
done <<< "$CASES"
|
||||
wait
|
||||
|
||||
# ── 2. diff + 汇总 ─────────────────────────────────────────────────────
|
||||
echo "== oracle_diff =="
|
||||
JSON="$REPO/tools/granny-oracle/m2-numeric.json"
|
||||
{
|
||||
echo '{'
|
||||
echo ' "granny": "2.9.12.0",'
|
||||
echo ' "threshold": {"mat": 1e-3, "vtx": 1e-3},'
|
||||
echo ' "cases": ['
|
||||
} > "$JSON"
|
||||
|
||||
total=0; passed=0; first=1
|
||||
worst_mat=0; worst_vtx=0
|
||||
while IFS='|' read -r model anim t mesh; do
|
||||
[ -z "$model" ] && continue
|
||||
k=$(key_for "$model" "$anim" "$t" "$mesh")
|
||||
bin="$DUMPS/suite_$k.bin"
|
||||
total=$((total+1))
|
||||
if [ ! -s "$bin" ]; then
|
||||
line=" {\"model\":\"$(basename "$model")\",\"anim\":\"$(basename "$anim")\",\"t\":$t,\"mesh\":$mesh,\"status\":\"NO_DUMP\"}"
|
||||
else
|
||||
amodel="$ASSETS/$model"; aanim="-"; [ "$anim" != "-" ] && aanim="$ASSETS/$anim"
|
||||
v=$("$DIFF" "$amodel" "$aanim" "$bin" --mesh "$mesh" 2>/dev/null | tail -1)
|
||||
m=$(echo "$v" | sed -n 's/.*mat \([0-9.e+-]*\) <.*/\1/p')
|
||||
vt=$(echo "$v" | sed -n 's/.*vtx \([0-9.e+-]*\) <.*/\1/p')
|
||||
st="FAIL"; echo "$v" | grep -q PASS && { st="PASS"; passed=$((passed+1)); }
|
||||
awk -v m="$m" -v w="$worst_mat" 'BEGIN{exit !(m+0>w+0)}' && worst_mat=$m
|
||||
awk -v m="$vt" -v w="$worst_vtx" 'BEGIN{exit !(m+0>w+0)}' && worst_vtx=$vt
|
||||
printf ' [%-4s] %-40s %-14s mat=%-10s vtx=%s\n' "$st" "$(basename "$model")" "$(basename "$anim") @${t}" "$m" "$vt"
|
||||
line=" {\"model\":\"$(basename "$model")\",\"anim\":\"$(basename "$anim")\",\"t\":$t,\"mesh\":$mesh,\"mat\":$m,\"vtx\":$vt,\"status\":\"$st\"}"
|
||||
fi
|
||||
[ $first -eq 1 ] && first=0 || echo ',' >> "$JSON"
|
||||
printf '%s' "$line" >> "$JSON"
|
||||
done <<< "$CASES"
|
||||
|
||||
{
|
||||
echo ''
|
||||
echo ' ],'
|
||||
echo " \"summary\": {\"total\": $total, \"passed\": $passed, \"worst_mat\": $worst_mat, \"worst_vtx\": $worst_vtx}"
|
||||
echo '}'
|
||||
} >> "$JSON"
|
||||
|
||||
echo
|
||||
echo "== $passed/$total PASS worst mat=$worst_mat vtx=$worst_vtx → $JSON =="
|
||||
[ "$passed" -eq "$total" ]
|
||||
@@ -1,4 +1,4 @@
|
||||
// oracle_diff —— libgr2 vs oracle 逐字段对拍(M2 门禁·主)。见 oracle/FORMAT.md、docs/steps/00-oracle.md
|
||||
// oracle_diff —— libgr2 vs oracle 逐字段对拍(M2 门禁·主)。见 tools/granny-oracle/FORMAT.md、docs/steps/00-oracle.md
|
||||
//
|
||||
// oracle_diff <model.gr2> [<anim.gr2>|-] <oracle_dump.bin> [--t <sec>] [--mesh N]
|
||||
//
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Summarises a native client --perf-log CSV (native_render/perf_log.h).
|
||||
"""Summarises a native client --perf-log CSV (src/host/perf_log.h).
|
||||
|
||||
usage: perf_summary.py perf-YYYYMMDD-HHMMSS.csv [--all]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Copies the Android client's --perf-log files (native_render/perf_log.h) to ./perf-logs and prints a
|
||||
# Copies the Android client's --perf-log files (src/host/perf_log.h) to ./perf-logs and prints a
|
||||
# summary of the newest one. The phone must be connected with USB debugging; the files can also be
|
||||
# taken off the phone without a PC from Android/data/org.metin2port.client/files/perf.
|
||||
set -e
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate one platform's pyconfig.h for the vendored CPython 2.7.18 (docs/PORT-PLAN.md 批次 2P step 1).
|
||||
# pyconfig.h is the result of probing a platform's type sizes and system calls, so every platform keeps
|
||||
# its own copy under extension/third_party/cpython-2.7.18/config/<platform>/. The source list and the
|
||||
# its own copy under third_party/cpython-2.7.18/config/<platform>/. The source list and the
|
||||
# built-in module table (config/config.c) are shared and only regenerated from the macos run.
|
||||
#
|
||||
# tools/py_embed/gen_pyconfig.sh macos|android|ios|linux
|
||||
@@ -10,7 +10,7 @@
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
REPO="$PWD"
|
||||
SRC="$REPO/extension/third_party/cpython-2.7.18"
|
||||
SRC="$REPO/third_party/cpython-2.7.18"
|
||||
PLAT="${1:?usage: gen_pyconfig.sh macos|android|ios|linux}"
|
||||
# config.sub in 2.7.18 predates arm64; the build triple only names the machine running configure.
|
||||
BUILD="$(uname -m | sed 's/^arm64$/aarch64/')-$(uname -s | tr 'A-Z' 'a-z')"
|
||||
|
||||
@@ -30,7 +30,7 @@ export MT40250_DUMP_ROOT=/usr/metin2_mysql_dump
|
||||
|
||||
```sh
|
||||
pkg install gmake clang-devel mysql80-server mysql80-client
|
||||
sh tools/40250/build_server.sh
|
||||
sh tools/server/build_server.sh
|
||||
```
|
||||
|
||||
源码 Makefile 是 40250 原版,使用 `clang++-devel`、`-m32`、静态 MySQL client、
|
||||
@@ -44,7 +44,7 @@ src/server all`,失败时应保留完整编译输出修复 jail 的 32-bit/库
|
||||
```sh
|
||||
export MT40250_MYSQL_ROOT_PASSWORD='你的 root 密码' # 不设置则每次交互式 -p
|
||||
export MT40250_DB_PASSWORD=password # 必须与 CONFIG 一致,默认 password
|
||||
sh tools/40250/setup_mysql.sh
|
||||
sh tools/server/setup_mysql.sh
|
||||
```
|
||||
|
||||
脚本创建 `account/common/log/player`,并同时建立 `metin2@localhost` 与
|
||||
@@ -54,9 +54,9 @@ sh tools/40250/setup_mysql.sh
|
||||
## 启停 40250 服务
|
||||
|
||||
```sh
|
||||
sh tools/40250/prepare_runtime.sh
|
||||
sh tools/40250/start_server.sh
|
||||
sh tools/40250/stop_server.sh
|
||||
sh tools/server/prepare_runtime.sh
|
||||
sh tools/server/start_server.sh
|
||||
sh tools/server/stop_server.sh
|
||||
```
|
||||
|
||||
`start_server.sh` 使用仓库自带 CONFIG:auth `11000`、channel1/first `13000`,
|
||||
@@ -4,7 +4,7 @@
|
||||
# REQUIRE: LOGIN mysql
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Account registration service for the 40250 server (tools/40250/register_server.py).
|
||||
# Account registration service for the 40250 server (tools/server/register_server.py).
|
||||
# Install: /usr/local/etc/rc.d/mt_register, then `sysrc mt_register_enable=YES`.
|
||||
|
||||
. /etc/rc.subr
|
||||
Reference in New Issue
Block a user