From 1bf7e2383e86d78a6357c299c2d51f96b92d7de7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 17:30:26 -0400 Subject: [PATCH] Fix Webswing font startup; make spike script blocking with clean teardown Two font problems, both now fixed and both verified against a real browser session driving the actual Webswing app JVM. 1. "Fontconfig head is null" killed the app JVM at startup. OpenJDK 9+ stopped shipping lib/fontconfig.bfc and queries libfontconfig at runtime instead, but Webswing's WebFontConfiguration still extends the legacy sun.awt.FontConfiguration, which needs the old properties file. Fixed by generating one (scripts/gen-fontconfig.sh, driven by fc-match so it adapts to the host) and pointing the app JVM at it via -Dsun.awt.fontconfig. After this the JVM starts, WebGraphicsEnvironment11 initialises headless, and MegaMek begins loading. 2. Webswing Lite then skipped all 12 font faces: "Font file outside trusted directory". Fixed with -Dwebswing.trustedFontDirs=/usr/share/fonts. start.sh is now blocking: Webswing runs under setsid in the background, the MegaMek host runs in the foreground, and the script exits when the game reaches VICTORY. A trap on INT/TERM/EXIT kills the Webswing process group (catching the spawned client) and the host. HostForHuman gained VICTORY detection with a result summary, per-round progress logging, and a shutdown hook so Ctrl-C releases the port instead of orphaning a JVM. webswing.config is now generated from a template at startup so absolute paths match the checkout rather than being hardcoded. Co-Authored-By: Claude Opus 5 --- harness/src/spike/HostForHuman.java | 39 ++++++- scripts/gen-fontconfig.sh | 80 ++++++++++++++ spike-webswing/fontconfig.properties | 38 +++++++ spike-webswing/start.sh | 136 +++++++++++++++++------- spike-webswing/webswing.config | 33 ------ spike-webswing/webswing.config.template | 36 +++++++ 6 files changed, 289 insertions(+), 73 deletions(-) create mode 100755 scripts/gen-fontconfig.sh create mode 100644 spike-webswing/fontconfig.properties delete mode 100644 spike-webswing/webswing.config create mode 100644 spike-webswing/webswing.config.template diff --git a/harness/src/spike/HostForHuman.java b/harness/src/spike/HostForHuman.java index 67470b4..3c1b1d8 100644 --- a/harness/src/spike/HostForHuman.java +++ b/harness/src/spike/HostForHuman.java @@ -7,6 +7,7 @@ import java.util.List; import megamek.client.bot.princess.Princess; import megamek.common.Player; import megamek.common.compute.Compute; +import megamek.common.enums.GamePhase; import megamek.common.game.IGame; import megamek.common.loaders.MekSummaryCache; import megamek.common.scenario.Scenario; @@ -89,10 +90,39 @@ public final class HostForHuman { System.out.println("[host] bots connected; waiting for a human to claim " + humanSlot.getName() + " ..."); + // Ctrl-C (or the wrapper script's cleanup) must tear the server down, not + // leave an orphaned JVM holding the port. + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println("\n[host] shutting down ..."); + try { + server.die(); + } catch (Exception ignored) { + // best effort + } + }, "host-shutdown")); + // Hold the bots in the lounge until the human slot is actually occupied. boolean announced = false; + int lastRound = -1; while (true) { - Player current = server.getGame().getPlayer(humanSlot.getId()); + IGame live = server.getGame(); + + if (live.getPhase() == GamePhase.VICTORY) { + System.out.println("\n================================================="); + System.out.println(" GAME OVER (round " + live.getCurrentRound() + ")"); + for (Player p : live.getPlayersList()) { + System.out.printf(" %-16s units remaining: %d%n", + p.getName(), live.getEntitiesOwnedBy(p)); + } + System.out.println("================================================="); + // Give the clients a moment to render the victory report before the + // server goes away underneath them. + Thread.sleep(5000L); + server.die(); + System.exit(0); + } + + Player current = live.getPlayer(humanSlot.getId()); if (current != null && !current.isGhost()) { if (!announced) { System.out.println("[host] human connected -> readying bots. " @@ -107,6 +137,13 @@ public final class HostForHuman { System.out.println("[host] human disconnected; waiting again ..."); announced = false; } + + int round = live.getCurrentRound(); + if (round != lastRound && round > 0) { + System.out.printf("[host] round %d (%s)%n", round, live.getPhase()); + lastRound = round; + } + Thread.sleep(1000L); } } diff --git a/scripts/gen-fontconfig.sh b/scripts/gen-fontconfig.sh new file mode 100755 index 0000000..30770fd --- /dev/null +++ b/scripts/gen-fontconfig.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Generate a legacy-format fontconfig.properties for Webswing's app JVM. +# +# Why this is needed: OpenJDK 9+ stopped shipping lib/fontconfig.bfc and instead +# queries libfontconfig at runtime via FcFontConfiguration. Webswing's +# WebFontConfiguration still extends the legacy sun.awt.FontConfiguration, which +# reads the old properties file. With no such file present the app JVM dies at +# startup with "Fontconfig head is null, check your fonts or fonts configuration". +# +# The JVM honours -Dsun.awt.fontconfig=, so we generate a file rather than +# writing into the JDK. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="${1:-$ROOT/spike-webswing/fontconfig.properties}" +mkdir -p "$(dirname "$OUT")" + +command -v fc-match >/dev/null || { echo "ERROR: fc-match not found (install fontconfig)" >&2; exit 1; } + +# logical Java font -> a family we ask fontconfig to resolve +resolve() { fc-match -f '%{file}' "$1" || true; } + +declare -A FAMILY=( + [serif]="Noto Serif" + [sansserif]="Noto Sans" + [monospaced]="DejaVu Sans Mono" + [dialog]="Noto Sans" + [dialoginput]="DejaVu Sans Mono" +) + +{ + echo "# Generated by scripts/gen-fontconfig.sh - do not edit by hand." + echo "version=1" + echo + echo "sequence.allfonts=latin-1" + echo +} > "$OUT" + +# Component font mappings: .