-
Notifications
You must be signed in to change notification settings - Fork 0
Build and Test
This page is the technical command reference for contributors and release operators. Product documentation lives in Playing and Library, Translation Lab, and Analogue Pocket SD Setup.
- macOS 14 or later;
- current Apple Command Line Tools or Xcode;
- Swift 6 toolchain support;
- CMake 3.28 or later; and
- Git.
The full Swift/XCTest lane requires the full Xcode developer directory. Command Line Tools alone may not provide XCTest.
From the repository root:
./Scripts/build-engine.sh
export SWAN_ARES_ENGINE_DIR="$PWD/.engine/build"
./Scripts/build-app.sh
open ".build/app/SwanSong.app"For direct SwiftPM execution instead of a Finder-style bundle:
./Scripts/build-engine.sh
export SWAN_ARES_ENGINE_DIR="$PWD/.engine/build"
swift run SwanSongThe local app is ad-hoc signed and is not an official distributable release.
The bundle embeds the WonderSwan-family ares dylib and declares .ws, .wsc,
.pc2, and .pcv2 document types. ZIPs are accepted by the open panel,
drag-and-drop, and folder import without claiming every ZIP in Launch Services.
A plain swift build uses the inspection-only stub backend for UI work. It
must not be presented as gameplay, compatibility, or release evidence.
SWAN_UNIVERSAL=1 ./Scripts/build-app.sh
./Scripts/verify-app-architectures.sh ".build/app/SwanSong.app"The universal engine uses .engine/build-app-universal. Apple-silicon and
Intel Swift slices use separate scratch directories so a host-native cache
cannot leak into the other architecture.
Official signing and notarization are documented in Signing and Notarization.
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
./Scripts/swift-package.sh test --package-path .
./Scripts/check-live-engine.sh
./Scripts/check-compatibility-matrix.sh
./Scripts/check-av-soak.sh
./Scripts/check-app-runtime.sh
./Scripts/check-app-bundle.sh
./Scripts/check-release-metadata.sh
./Scripts/prepare-wiki-sync.sh --check
python3 ./Scripts/check-sparkle-dependency-lock.py \
--repository . \
--upstream-package .build/checkouts/Sparkle/Package.swift
./Scripts/check-sparkle-configuration.sh
./Scripts/check-sparkle-framework.sh
./Scripts/selftest-sparkle-dependency-lock.sh
./Scripts/selftest-sparkle-appcast.sh
./Scripts/check-ui-snapshots.sh
./Scripts/check-mcp-server.sh
./Scripts/check-playtest-mcp-server.sh
./Scripts/check-playtest-cli.sh
./Scripts/check-translation-automation-cli.sh
./Scripts/check-translation-lab.sh
./Scripts/check-pcv2-translation-lab.sh
./Scripts/check-homebrew-production-readiness.shFixture results prove bounded execution invariants. They are not commercial- game compatibility results or original-hardware accuracy evidence.
The trusted project config starts the local STDIO server through:
./Scripts/run-swansong-mcp.shRun the protocol-surface check without enabling live app control:
./Scripts/check-mcp-server.sh
./Scripts/check-playtest-mcp-server.shRun the live-ares playtest, route creation, and paired-evidence checks:
./Scripts/check-playtest-cli.sh
./Scripts/check-translation-automation-cli.shThe latter test proves both write guards, route-v3, empty persistence, fixed RTC, native checkpoint capture, Original/Patched endpoint parity, two Capture Intake runs, and manifest digest revalidation. See Local MCP and Automation for tool schemas and the direct CLI.
For an authorized local game, the headless probe runs the same ares engine and Open IPL path without opening a macOS window:
swift run SwanSongProbe \
--rom "/path/to/game.wsc" \
--frames 600 \
--report probe.json \
--capture probe.ppm \
--require-video-activityThe report records first non-uniform video, distinct native-raster frames, longest flat-color run, final-frame hash, audio activity, state size, and first-batch replay behavior. Its outputs contain rendered pixels and measurements, never ROM, state, persistence, or memory bytes.
Build a matched debug automation app in an isolated output directory, then provide an authorized private input explicitly:
CONFIGURATION=debug \
SWAN_APP_OUTPUT_DIR="$PWD/.build/owned-smoke-app" \
./Scripts/build-app.sh
./Scripts/check-owned-rom-smoke.sh \
--app "$PWD/.build/owned-smoke-app/SwanSong.app" \
--rom "$OWNED_GAME_ZIP"The checked executable and debug runner must share a Mach-O build UUID. The lane uses a unique private home/data directory to test import, Open IPL launch, native frame activity, saves, and states. It removes private artifacts, proves the app bundle is byte-identical before/after, and never prints private paths, names, hashes, frames, or diagnostics.
For a privacy-safe aggregate over an authorized directory:
./Scripts/check-owned-rom-open-ipl.sh \
--rom-dir "/path/to/owned-rom-directory" \
--report .build/compatibility/owned-open-ipl-summary.jsonThis lane accepts direct games and one-game ZIPs, rejects firmware-shaped inputs, binds Open IPL and deterministic RTC, and writes only source-free case counts.
./Scripts/check-compatibility-matrix.shThe matrix builds the Probe in an isolated live-ares scratch directory and
runs every checked-in .ws/.wsc fixture plus a clean-room generated .pc2
fixture. The JSON report records video activity, nonzero audio, state capture,
and first replay behavior. It labels static output and settle-required replay
instead of inflating them into compatibility claims.
The release-default source-free soak runs the checked-in open fixture at strict wall-clock speed for 30 minutes:
./Scripts/check-av-soak.shIts sorted-key JSON tracks sequential/invalid frames, delivery stalls, 48 kHz stereo stability, virtual-queue depth, underruns, dropped batches, transport drift, pacing rate, and bounded host-discontinuity recovery without including source bytes, paths, frames, or timestamps.
Production pacing targets five audio batches (about 66 ms nominal) under a 180 ms hard cap. A transport epoch is recovered only after a primed queue drains beyond the full four-batch horizon: the obsolete schedule is cleared, five batches are re-primed, and the first 5 ms fades in. Ordinary sub-horizon starvation remains an underrun and fails the gate. Reports count recoveries separately and allow at most one per requested minute.
Short local and scheduler-neutral hosted-CI lanes are explicit:
SWAN_AV_SOAK_SECONDS=5 ./Scripts/check-av-soak.sh
SWAN_AV_SOAK_SECONDS=5 SWAN_AV_SOAK_CLOCK_MODE=media-time \
./Scripts/check-av-soak.sh .build/av-soak/ci-integrity.jsonFocused injection proves recovery and its disabled control:
SWAN_AV_SOAK_SECONDS=3 SWAN_AV_SOAK_INJECT_HOST_GAP_MS=120 \
./Scripts/check-av-soak.sh .build/av-soak/recovery.json
SWAN_AV_SOAK_SECONDS=3 SWAN_AV_SOAK_INJECT_HOST_GAP_MS=120 \
SWAN_AV_SOAK_DISABLE_DISCONTINUITY_RECOVERY=1 \
SWAN_AV_SOAK_EXPECT_STATUS=fail \
./Scripts/check-av-soak.sh .build/av-soak/recovery-disabled.jsonThe virtual sink is a queue model, not Core Audio hardware. Physical device latency and owned-game audio remain separate release evidence.
./Scripts/check-app-runtime.sh
./Scripts/check-app-bundle.shThe runtime smoke launches the actual SwiftUI app with isolated data and open fixtures. It exercises Open IPL launch, library import, atomic autosave, versioned visual states, byte-identical preview restore, memory-only rewind, and generated Pocket Challenge V2 flash persistence.
The rewind lane captures frame 90, advances to frame 450, restores the nearest
five-second checkpoint, replays frame 90 exactly, and proves no .state file
was created.
The bundle smoke verifies self-contained dylib resolution, ad-hoc signing, Finder-style document opening, and absence of game/firmware payloads.
./Scripts/check-ui-snapshots.shThe gate renders real AppKit/SwiftUI surfaces offscreen across compact/wide, Light/Dark, horizontal/vertical, player, library, controller, Translation Lab, and Analogue Pocket states. It checks blank regions, framebuffer corners, interaction targets, accessibility labels, scrolling, and reviewed perceptual baselines.
After visually reviewing every generated PNG under .build/ui-regression/,
refresh a deliberately changed baseline with:
./Scripts/check-ui-snapshots.sh --update-baselinesNormal checks are read-only and never approve or rewrite a baseline.
./Scripts/check-translation-lab.sh
./Scripts/check-pcv2-translation-lab.shThe general smoke builds a synthetic private project, records immutable route-v3 tests, captures digest-bound Original/Patched evidence, exercises guarded packing and batch verification, rejects unsafe readiness and legacy routes, and proves the normal library and save store remain untouched.
The Pocket Challenge V2 lane proves project/startup identity, all nine keypad inputs, 16 KiB internal RAM, route replay, and First Visual Change hardware routing.
Signed bundles include a separately gated command-line runner:
/Applications/SwanSong.app/Contents/Helpers/SwanSongRouteRunner \
--enable-debug-tools \
--rom "/path/to/game.wsc" \
--route "/path/to/route.json" \
--output "/path/to/route-report.json" \
--capture "/path/to/final-frame.png"The runner requires the route-bound ROM digest, hardware, Open IPL context, RTC seed, and bundled engine build to agree. It exits nonzero when the final native-raster checkpoint differs.
./Scripts/check-player-input.sh "/path/to/game.wsc"The gate posts a physical keyboard event, requires active gameplay focus and the expected effective WonderSwan input, and proves canonical game-raster fingerprints change. Exit 77 means the host lacks WindowServer or Accessibility permission; it is not a pass. Grant the invoking Terminal or Codex app access under System Settings → Privacy & Security → Accessibility and rerun.
SWAN_ARES_ENGINE_DIR="$PWD/.engine/build" swift run SwanSongDifferential \
--rom testroms/ws-test-suite/80186_quirks/80186_quirks.ws \
--rtl /path/to/swan-song-rtl-frames \
--frames 360 \
--out .build/differential/80186_quirks.jsonThe differential compares the live ares framebuffer with raw 224×144 RTL frames. Mono reports preserve the raw result and add a separately labeled four-luminance structural comparison. Every report states that emulator/RTL agreement is not original-hardware evidence.
Updater acceptance requires the signed production feed, public key, disabled system profiling, off-by-default automation, signed enclosure, immutable URL, and stable/beta behavior in App Updates. A source configuration test cannot replace installation/relaunch from the previous supported app.
Pocket release acceptance includes the adversarial fixture suite plus real cards, readers, filesystems, eject behavior, and hardware; see Analogue Pocket SD Setup. Complete artifact, owned-game, physical-controller, signing, and publication requirements are in Release Gates.
Keep all private ROMs, saves, captures, and Translation Lab evidence outside Git. Never attach them to a public issue or CI artifact.
Start here
Create and translate
Homebrew and hardware
Build and release
- Architecture and Source Ownership
- Build and Test
- Signing and Notarization
- Release Gates
- 0.9 Release Testing
- 0.8 Release Testing
- 0.7 Release Testing
- 0.6 Release Testing
- 0.5 Release Testing
- 0.4 Beta Testing
Help and policies