#!/usr/bin/env bash # M2 门禁·主:对测试资产集跑 oracle dump + oracle_diff,汇总 test/m2-numeric.json。 # 前置:oracle/build-wine.sh 已产出 oracle.exe;WINEPREFIX 已 init(见 RUNBOOK.md); # oracle_diff 已构建(cmake -B build-tools -DMTGODOT_BUILD_EXTENSION=OFF -DMTGODOT_BUILD_TOOLS=ON)。 set -uo pipefail cd "$(dirname "$0")/.." REPO="$(pwd)" ASSETS="$(cd "${MT_ASSETS:-$REPO/assets}" && pwd)" DUMPS="$REPO/test/oracle_dumps" DIFF="" for c in "$REPO/build-tools/tools/oracle_diff" "$REPO/build/tools/oracle_diff"; do [ -x "$c" ] && DIFF="$c" && break done [ -n "$DIFF" ] || { echo "oracle_diff not built — run: cmake -B build-tools -DMTGODOT_BUILD_EXTENSION=OFF -DMTGODOT_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')"; } # 用例: | | | 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/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/test/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" ]