85 lines
3.2 KiB
Bash
Executable File
85 lines
3.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Checks the simulation is deterministic across builds and platforms, which desktop/web crossplay depends on.
|
|
#
|
|
# Fails if any source calls the maths library's transcendentals (src/dmath.h replaces them), then builds the game
|
|
# several ways and plays the same seeded bot matches in each (--balance with RPG_SEED), comparing the determinism hash
|
|
# they print:
|
|
# gcc GCC, optimised (the usual build)
|
|
# gcc-fma GCC for this CPU (with FMA if it has it, standing in for arm64's fused multiply-adds)
|
|
# clang-O0 Clang, unoptimised
|
|
# wasm Emscripten, run under node (needs EMSDK, or emsdk_env.sh sourced)
|
|
#
|
|
# Usage: tools/determinism_check.sh [matches per seed, default 20] [seeds, default "1 2 3"]
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
OUT="$ROOT/build-check"
|
|
MATCHES="${1:-20}"
|
|
SEEDS="${2:-1 2 3}"
|
|
|
|
banned=$(grep -nE "std::(sin|cos|tan|atan2|atan|asin|acos|exp|exp2|expm1|log|log2|log10|log1p|pow|hypot|cbrt|sinh|cosh|tanh|lerp)\(|\b(sinf|cosf|tanf|atan2f|powf|expf|logf|log2f)\(|SDL_(sin|cos|tan|atan2|atan|pow|exp|log)f?\(" \
|
|
"$ROOT"/src/*.cpp "$ROOT"/src/*.h | grep -v "src/dmath.h" || true)
|
|
if [ -n "$banned" ]; then
|
|
echo "Maths library transcendentals in the source (use math_sin and friends from src/dmath.h):"
|
|
echo "$banned"
|
|
exit 1
|
|
fi
|
|
|
|
configure() # name, cmake arguments...
|
|
{
|
|
local name="$1"
|
|
shift
|
|
if [ ! -f "$OUT/$name/build.ninja" ]; then
|
|
cmake -S "$ROOT" -B "$OUT/$name" -G Ninja "$@" > "$OUT/$name.log" 2>&1
|
|
fi
|
|
cmake --build "$OUT/$name" >> "$OUT/$name.log" 2>&1 || {
|
|
echo "$name failed to build, see $OUT/$name.log"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
mkdir -p "$OUT"
|
|
names=(gcc gcc-fma clang-O0)
|
|
configure gcc -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
|
|
configure gcc-fma -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ \
|
|
-DCMAKE_CXX_FLAGS=-march=native -DCMAKE_C_FLAGS=-march=native
|
|
configure clang-O0 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
|
if [ -n "${EMSDK:-}" ] || command -v emcmake > /dev/null; then
|
|
[ -n "${EMSDK:-}" ] && source "$EMSDK/emsdk_env.sh" > /dev/null 2>&1
|
|
if [ ! -f "$OUT/wasm/build.ninja" ]; then
|
|
emcmake cmake -S "$ROOT" -B "$OUT/wasm" -G Ninja -DCMAKE_BUILD_TYPE=Release -DRPG_NODE=ON > "$OUT/wasm.log" 2>&1
|
|
fi
|
|
cmake --build "$OUT/wasm" >> "$OUT/wasm.log" 2>&1 || {
|
|
echo "wasm failed to build, see $OUT/wasm.log"
|
|
exit 1
|
|
}
|
|
names+=(wasm)
|
|
else
|
|
echo "Emscripten not found (set EMSDK): skipping the wasm build."
|
|
fi
|
|
|
|
hash_get() # name, seed
|
|
{
|
|
local run=("$OUT/$1/smack-a-brother")
|
|
[ "$1" = wasm ] && run=(node "$OUT/wasm/smack-a-brother.js")
|
|
(cd "$OUT/$1" && RPG_SEED="$2" "${run[@]}" --balance "$MATCHES" 2>&1 | grep -oE "hash: [0-9a-f]+" | cut -d' ' -f2)
|
|
}
|
|
|
|
failed=0
|
|
for seed in $SEEDS; do
|
|
line="seed $seed:"
|
|
first=""
|
|
for name in "${names[@]}"; do
|
|
hash=$(hash_get "$name" "$seed")
|
|
line+=" $name $hash"
|
|
[ -z "$first" ] && first="$hash"
|
|
[ "$hash" != "$first" ] && failed=1
|
|
done
|
|
echo "$line"
|
|
done
|
|
if [ "$failed" = 1 ]; then
|
|
echo "DESYNC: the builds disagree."
|
|
exit 1
|
|
fi
|
|
echo "Deterministic: every build agrees."
|