Metin2 game client (P0–P11) + mobile asset pipeline
Networked client on the existing Godot 4.7 + libgr2 renderer:
- net: m2dev wire protocol (libsodium KX + XChaCha20), auth/select/game
phases, EntityStore world model, ~all GC/CG headers. char create/delete,
private shop / mall / cube, SHOP_GC_START_EX, guild, party (+ CG_PARTY_SET_STATE),
quests, dragon soul, refine, safebox, exchange.
- UI: in-game windows migrated 1:1 from the reference uiscript/root .py —
char status (/stat), inventory+equipment, select-item ([SELECT_ITEM] quest
token), system-option + game-option + ESC system menu, private-shop 39-grid,
party info board, shop tabs, atlas, minimap, quickbar, chat, …
- EterGrnLib polish: GR2 material blend/two-sided, LOD crossfade, motion-event
dispatch, contact shadow, ray-AABB picking, weapon grip pre-transform.
Portable asset IO (A1) — all extension/libgr2/formats/mtproto reads routed
through godot::FileAccess (res:// PCK works on iOS/Android); standalone-lib
*_path() kept for the non-Godot CTests. AssetResolver + PropertyRegistry
switched to a baked index (bake_asset_index.gd) instead of std::filesystem.
Mobile builds: build-{android,ios}.sh, export-android.sh, pack-assets.sh,
gen-debug-keystore.sh. Assets ship as a zip mounted at runtime by
project/asset_pack.gd (adb push now; HTTP download is a drop-in later).
ctest 10/10, 34 GDScript suites, macOS/iOS/Android all build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
This commit is contained in:
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/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')"; }
|
||||
|
||||
# 用例:<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/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" ]
|
||||
Reference in New Issue
Block a user