# 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 关键字) oracle/build-wine.sh # → oracle/oracle.exe + 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 [--t ] [--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)