diff --git a/README.md b/README.md index 9872fe6..b22acc7 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,13 @@ the stock MegaMek Swing client. See [TODO.md](TODO.md) for what's next and ## Constraint **Minimal, upstreamable patches.** Everything builds against an unmodified -release jar, the harness uses only public API, and the two patches that exist are -latent MegaMek bugs rather than adaptations. A new MegaMek release should be a +release jar, the harness uses only public API, and all four patches that exist +are latent MegaMek bugs rather than adaptations. A new MegaMek release should be a recompile plus at most a patch refresh, never a rebase. -## Running the browser spike +## Quickstart + ./scripts/bootstrap.sh # ~1.1GB of downloads, then builds everything ./spike-webswing/start.sh # blocks; Ctrl-C tears everything down Open . You play TraineeA, Princess plays @@ -55,6 +56,19 @@ out and Player Settings throws an NPE. Webswing Lite session settings do not hel `maxClients: 1` refuses the second browser, and `CONTINUE_FOR_USER` + `allowStealSession` lets it evict the first and locks out both. +### What bootstrap does + +Nothing third-party is committed — the repo is ~40 files. `bootstrap.sh` fetches +the pinned JDK 21, the MegaMek 0.51.0 release *and* source tarball, and Webswing +Lite 26.4.5; then builds the harness, applies the patchset into +`MegaMek-patched.jar`, generates the fontconfig, and primes MegaMek's +`units.cache`. It is idempotent, so re-run it freely. + +Host requirements: `curl`, `tar`, `unzip`, `sha256sum`, and **`fontconfig`** +(`fc-match`). Without fontconfig the client dies at startup with +"Fontconfig head is null" — bootstrap warns rather than failing, so the message +is easy to miss. + ## Patches Unified diffs in `patches/patchset/`, applied `-p1` against a pristine tree — @@ -111,15 +125,15 @@ Applied, and verified to be the only differences from the stock jar: the adapter-pattern prototype) - `spike-webswing/` — Webswing Lite + MegaMek settings templates, `start.sh` - `patches/` — patchset, archive, patched-jar builder -- `scripts/` — `fetch-deps.sh`, `gen-fontconfig.sh` (legacy fontconfig Webswing Lite's - toolkit needs), `cdp-screenshot.py`, `summarize.py` +- `scripts/` — `bootstrap.sh` (one-shot setup), `fetch-deps.sh`, + `gen-fontconfig.sh` (legacy fontconfig Webswing Lite's toolkit needs), + `cdp-screenshot.py`, `summarize.py` - `results/` — bench CSVs and jstacks kept as evidence for Findings ## Benchmarks ```sh -./scripts/fetch-deps.sh -./harness/build.sh +./scripts/bootstrap.sh ./harness/run.sh --games 8 --settle-round 3 --csv results/out.csv python3 scripts/summarize.py results/out.csv ``` diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..7951c13 --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Take a fresh checkout to the point where you can play a match. +# +# ./scripts/bootstrap.sh +# ./spike-webswing/start.sh # then open http://localhost:8080/megamek/ +# +# Steps, all idempotent: +# 1. fetch third-party deps (JDK, MegaMek release + source, Webswing Lite) +# 2. compile the harness against the stock MegaMek jar +# 3. build MegaMek-patched.jar by applying patches/patchset/ +# 4. generate the legacy fontconfig Webswing Lite's toolkit requires +# 5. prime MegaMek's units.cache so the first launch is not 17s slower +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +step() { printf '\n=== %s ===\n' "$1"; } + +step "1/5 third-party dependencies" +./scripts/fetch-deps.sh + +step "2/5 harness" +./harness/build.sh + +step "3/5 patched MegaMek jar" +./patches/build-patched-jar.sh + +step "4/5 fontconfig" +# Needs fontconfig tooling on the host; MegaMek's symbol glyphs depend on it. +if command -v fc-match >/dev/null; then + ./scripts/gen-fontconfig.sh +else + echo "SKIPPED: fc-match not found. Install fontconfig, then run" >&2 + echo " ./scripts/gen-fontconfig.sh" >&2 + echo "Without it the MegaMek client dies at startup with 'Fontconfig head is null'." >&2 +fi + +step "5/5 prime units.cache" +# MegaMek builds this by walking ~11k unit files on first run - 20s instead of 3s. +# Doing it here keeps it out of the first match. +MM="$ROOT/MegaMek-0.51.00" +if [ -f "$MM/data/mekfiles/units.cache" ]; then + echo " already present" +else + echo " building (this takes ~20s) ..." + ( cd "$MM" && "$ROOT/jdk-21.0.12+8/bin/java" \ + -Dlog4j2.configurationFile="$ROOT/harness/log4j2-quiet.xml" \ + --add-opens java.base/java.util=ALL-UNNAMED \ + --add-opens java.base/java.util.concurrent=ALL-UNNAMED \ + -cp "$ROOT/harness/out:$ROOT/MegaMek-patched.jar:$(ls "$MM"/lib/*.jar | tr '\n' ':')" \ + bench.IsolationCheck >/dev/null 2>&1 ) || true + [ -f "$MM/data/mekfiles/units.cache" ] && echo " built" || echo " (not built; first launch will be slower)" +fi + +cat <<'DONE' + +=== ready === + + ./spike-webswing/start.sh then open http://localhost:8080/megamek/ + ./spike-webswing/stop.sh stop it (never pkill - see README) + + You play TraineeA, Princess plays TraineeB. Press "Done" in the lobby. +DONE diff --git a/scripts/fetch-deps.sh b/scripts/fetch-deps.sh index c57a8f4..e191c49 100755 --- a/scripts/fetch-deps.sh +++ b/scripts/fetch-deps.sh @@ -1,20 +1,72 @@ #!/usr/bin/env bash -# Fetch the pinned JDK and MegaMek release. Both are gitignored; this makes the -# repo reproducible without committing ~700MB of third-party artifacts. +# Fetch every third-party artifact the spike needs. All of them are gitignored, +# so the repo stays small and this script is what makes a fresh checkout usable. +# +# Idempotent: already-downloaded archives and already-extracted trees are skipped, +# so it is safe to re-run. +# +# ./scripts/fetch-deps.sh +# +# Downloads (~1.1GB total, mostly MegaMek's data directory): +# Temurin JDK 21 MegaMek needs Java 21 exactly; checksum-verified +# MegaMek release the jar plus data/ (units, boards, images, fonts) +# MegaMek source needed by patches/build-patched-jar.sh to apply the patchset +# Webswing Lite AGPL-3.0 fork by manticore-projects - NOT commercial Webswing set -euo pipefail + ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" mkdir -p dl JDK_URL="https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.12%2B8/OpenJDK21U-jdk_x64_linux_hotspot_21.0.12_8.tar.gz" JDK_SHA="e4446ff06a276155697597cc0f1b15da004ff083f4964a35271ecee567177370" -MM_URL="https://github.com/MegaMek/megamek/releases/download/v0.51.0/MegaMek-0.51.0.tar.gz" +JDK_DIR="jdk-21.0.12+8" + +MM_VERSION="0.51.0" +MM_URL="https://github.com/MegaMek/megamek/releases/download/v${MM_VERSION}/MegaMek-${MM_VERSION}.tar.gz" +MM_SRC_URL="https://github.com/MegaMek/megamek/archive/refs/tags/v${MM_VERSION}.tar.gz" +MM_DIR="MegaMek-0.51.00" # note: the release tree has a trailing 0 +MM_SRC_DIR="megamek-${MM_VERSION}" + +WS_VERSION="26.4.5" +WS_URL="https://github.com/manticore-projects/webswing/releases/download/${WS_VERSION}/webswing-${WS_VERSION}.zip" +WS_DIR="webswing-dist" + +need() { command -v "$1" >/dev/null || { echo "ERROR: '$1' not found" >&2; exit 1; }; } +need curl; need tar; need unzip; need sha256sum + +get() { # url dest + [ -f "$2" ] && { echo " have $(basename "$2")"; return 0; } + echo " downloading $(basename "$2") ..." + curl -fsSL -o "$2" "$1" +} + +echo "JDK 21 (Temurin)" +get "$JDK_URL" dl/jdk21.tar.gz +echo "${JDK_SHA} dl/jdk21.tar.gz" | sha256sum -c - >/dev/null || { + echo "ERROR: JDK checksum mismatch - delete dl/jdk21.tar.gz and retry" >&2; exit 1; } +[ -d "$JDK_DIR" ] || tar xzf dl/jdk21.tar.gz +echo " -> $JDK_DIR" + +echo "MegaMek $MM_VERSION (release)" +get "$MM_URL" "dl/MegaMek-${MM_VERSION}.tar.gz" +[ -d "$MM_DIR" ] || tar xzf "dl/MegaMek-${MM_VERSION}.tar.gz" +echo " -> $MM_DIR" -[ -f dl/jdk21.tar.gz ] || curl -sSL -o dl/jdk21.tar.gz "$JDK_URL" -echo "${JDK_SHA} dl/jdk21.tar.gz" | sha256sum -c - -[ -d jdk-21.0.12+8 ] || tar xzf dl/jdk21.tar.gz +echo "MegaMek $MM_VERSION (source, needed to apply the patchset)" +get "$MM_SRC_URL" "dl/megamek-src-${MM_VERSION}.tar.gz" +[ -d "$MM_SRC_DIR" ] || tar xzf "dl/megamek-src-${MM_VERSION}.tar.gz" +echo " -> $MM_SRC_DIR" -[ -f dl/MegaMek-0.51.0.tar.gz ] || curl -sSL -o dl/MegaMek-0.51.0.tar.gz "$MM_URL" -[ -d MegaMek-0.51.00 ] || tar xzf dl/MegaMek-0.51.0.tar.gz +echo "Webswing Lite $WS_VERSION (AGPL-3.0 fork; not commercial Webswing)" +get "$WS_URL" "dl/webswing-${WS_VERSION}.zip" +[ -d "$WS_DIR" ] || unzip -q "dl/webswing-${WS_VERSION}.zip" -d "$WS_DIR" +# The distribution ships no LICENSE file of its own; add the AGPL text so the +# extracted tree states its terms. See GUIDELINES.md. +[ -f "$WS_DIR/LICENSE.txt" ] || \ + curl -fsSL -o "$WS_DIR/LICENSE.txt" "https://www.gnu.org/licenses/agpl-3.0.txt" || \ + echo " (warning: could not fetch AGPL text for $WS_DIR/LICENSE.txt)" +echo " -> $WS_DIR" -echo "deps ready" +echo +echo "deps ready. Next: ./scripts/bootstrap.sh" diff --git a/spike-webswing/start.sh b/spike-webswing/start.sh index 50e96db..bff094e 100755 --- a/spike-webswing/start.sh +++ b/spike-webswing/start.sh @@ -62,8 +62,16 @@ fi # Materialise the Webswing config with absolute paths for this checkout. sed -e "s|@ROOT@|$ROOT|g" -e "s|@PORT_MM@|$PORT_MM|g" \ "$ROOT/spike-webswing/webswing.config.template" > "$WS_CONFIG" -sed "s|^org.webswing.server.http.port=.*|org.webswing.server.http.port=$PORT_WS|" \ - "$WS/jetty.properties" > "$WS_JETTY" +# Written from scratch rather than derived from the shipped jetty.properties: +# that file defaults to https=true pointing at an ssl/keystore.jks the +# distribution does not contain, so a fresh extract fails to start. Generating it +# means a clean checkout works with no hand-editing of webswing-dist. +cat > "$WS_JETTY" <