#!/usr/bin/env bash
#
# Bug #8455 / #9170 - build the single-threaded, no-shared-memory WASM Stockfish
# that wwwroot/lib/stockfish/single/ ships.
#
# WHY THIS EXISTS. The engine the app normally runs, wwwroot/lib/stockfish/sf_19.js,
# is an emscripten pthread build: it constructs a SHARED WebAssembly.Memory, which
# a page can only do when it is cross-origin isolated. ASCENDCHESS sends
# Cross-Origin-Embedder-Policy: credentialless, which Chromium honors and WebKit
# does not implement, and every browser on iOS is WebKit (Chrome and Firefox for
# iOS included). So on iOS that constructor throws and the engine never starts.
# This build removes the worker thread entirely, so no shared memory is involved
# and no isolation is required.
#
# PROVENANCE. Everything below is pinned. Re-running this script on any machine
# with docker must reproduce the same two artifacts.
#
#   source           https://github.com/official-stockfish/Stockfish.git
#   source commit    edb0d9db6731067ec50ce619ff372b463bc4dd5d  (tag sf_19, "Bench: 2497913")
#   patch            0001-single-threaded-no-shared-memory.patch (in this directory)
#   toolchain        docker image emscripten/emsdk:5.0.7, the same emcc the threaded
#                    sf_19.js was built with (@lichess-org/stockfish-web 0.5.0)
#                    emcc 5.0.7 (263db4cffa6f9fc2ec514a70abac81362ea41849)
#   neural net       unchanged: the build loads the SAME nn-1a298aa575a0.nnue the
#                    threaded engine already ships, because it is the same engine
#                    version. Stockfish 19 has ONE net; there is no small net.
#
#   measured output  stockfish.js    63046 bytes
#                    sha256 c72fbdb0d5a9d2363a9dbf964da256c32cd4afed0bb7db6ea4527c7ccc16ad13
#                    stockfish.wasm  630698 bytes
#                    sha256 d96dcfaed7f35731d89cb7aa9390a8c1c11dd9b74fc6041cc225a9da9201d972
#                    (#9170: two builds on 2026-10-30, one with SF_NET_CACHE and one
#                    downloading the net in the container, were byte-identical)
#
# WHAT THE PATCH DOES, in four pieces, all of them inert without the new options.
# It is the #8455 sf_18 patch PORTED to sf_19, not re-applied: upstream gained a
# wasm32 target, a fallible native-thread wrapper and a currentCmd field in the
# meantime, so five of the six sf_18 hunks no longer applied.
#
#   1. Makefile - adds pthreads=no to upstream's own ARCH=wasm32 target: it drops
#      -pthread, defines SF_NO_THREADS and SF_WASM_SINGLE, and gives the main
#      WASM stack 4MB because the search now runs on it.
#   2. thread.h / thread.cpp - under SF_NO_THREADS a Thread has no native thread
#      and run_custom_job runs its job inline on the caller. Upstream behavior is
#      untouched without that define.
#   3. uci.h / uci.cpp - the UCI dispatch moves out of loop() into a new public
#      execute(cmd), and loop() becomes a thin reader over it. This is a pure
#      refactor: loop() behaves exactly as before, currentCmd included.
#   4. main.cpp - under SF_WASM_SINGLE, main() does nothing and the module exposes
#      sf_init() and sf_command(line) instead, because a browser has no usable
#      blocking stdin and this build has no thread to park a reader on.
#
# USAGE:   bash tools/wasm-stockfish/build-single-threaded.sh [output-dir]
# Default output-dir is AscendChess/AscendChess/wwwroot/lib/stockfish/single.
#
# Optional environment:
#   TMPDIR           where the source is cloned and built (mktemp -d). On a box
#                    whose /tmp is RAM, point this at a disk path.
#   SF_BUILD_JOBS    make -j value (default: nproc). Lower it on a box short of RAM.
#   SF_DOCKER_MEMORY a docker --memory cap for the build container (e.g. 4g).
#   SF_NET_CACHE     a directory already holding nn-1a298aa575a0.nnue. The
#                    Makefile's net step validates it by hash and skips the
#                    download. Without it the net step downloads the net inside
#                    the container. The net is NOT compiled in either way
#                    (NNUE_EMBEDDING_OFF), so this changes no output byte.

set -euo pipefail

SF_COMMIT="edb0d9db6731067ec50ce619ff372b463bc4dd5d"
EMSDK_IMAGE="emscripten/emsdk:5.0.7"
EXPORT_NAME="Sf19Single"
NET_NAME="nn-1a298aa575a0.nnue"
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$HERE/../.." && pwd)"
OUT_DIR="${1:-$REPO_ROOT/AscendChess/AscendChess/wwwroot/lib/stockfish/single}"
JOBS="${SF_BUILD_JOBS:-$(nproc)}"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

echo "==> cloning Stockfish at $SF_COMMIT"
git clone --quiet https://github.com/official-stockfish/Stockfish.git "$WORK/sf"
git -C "$WORK/sf" checkout --quiet "$SF_COMMIT"

echo "==> applying $HERE/0001-single-threaded-no-shared-memory.patch"
git -C "$WORK/sf" apply "$HERE/0001-single-threaded-no-shared-memory.patch"

if [ -n "${SF_NET_CACHE:-}" ] && [ -f "$SF_NET_CACHE/$NET_NAME" ]; then
  echo "==> using $NET_NAME from $SF_NET_CACHE (validated by hash in the net step)"
  cp "$SF_NET_CACHE/$NET_NAME" "$WORK/sf/src/$NET_NAME"
fi

echo "==> building in $EMSDK_IMAGE with -j$JOBS"
# Run as the calling user so nothing in $WORK is left owned by root.
MEMORY_ARGS=()
if [ -n "${SF_DOCKER_MEMORY:-}" ]; then
  MEMORY_ARGS=(--memory "$SF_DOCKER_MEMORY" --memory-swap "$SF_DOCKER_MEMORY")
fi
docker run --rm "${MEMORY_ARGS[@]}" --user "$(id -u):$(id -g)" -e HOME=/tmp -e JOBS="$JOBS" \
  -v "$WORK/sf:/src" "$EMSDK_IMAGE" bash -lc '
  set -euo pipefail
  cd /src/src
  make -j"$JOBS" build ARCH=wasm32 COMP=gcc COMPCXX=em++ pthreads=no \
    EXTRALDFLAGS="-sMODULARIZE=1 -sEXPORT_NAME='"$EXPORT_NAME"' -sINVOKE_RUN=0 -sEXIT_RUNTIME=0 -sFORCE_FILESYSTEM=1 -sEXPORTED_RUNTIME_METHODS=ccall,FS,HEAPU8 -sEXPORTED_FUNCTIONS=_sf_init,_sf_command,_main -sMAXIMUM_MEMORY=2GB -sALLOW_MEMORY_GROWTH=1 -sASSERTIONS=0 -sENVIRONMENT=web,worker,node"
  em++ --version | head -1
'

echo "==> installing into $OUT_DIR"
mkdir -p "$OUT_DIR"
install -m 644 "$WORK/sf/src/stockfish.js" "$OUT_DIR/stockfish.js"
install -m 644 "$WORK/sf/src/stockfish.wasm" "$OUT_DIR/stockfish.wasm"
sha256sum "$OUT_DIR/stockfish.js" "$OUT_DIR/stockfish.wasm"

echo "==> done. A shipped artifact must contain no shared memory and no pthreads at all:"
if grep -q "SharedArrayBuffer" "$OUT_DIR/stockfish.js"; then
  echo "FAIL: the glue mentions SharedArrayBuffer, so this is not a no-shared-memory build." >&2
  exit 1
fi
if grep -qE "PThread|pthread_create|ENVIRONMENT_IS_PTHREAD" "$OUT_DIR/stockfish.js"; then
  echo "FAIL: the glue carries pthread support, so this is not a single-threaded build." >&2
  exit 1
fi
echo "OK: no SharedArrayBuffer and no pthread support in the glue."
