The official source repository is MegaMek/mm-launcher.
The Launcher native installers workflow builds on pushes to main, pull requests,
and manual dispatch. PR branch pushes run once through the pull-request event rather
than also starting a duplicate push matrix. CI produces only five unsigned native installer/checksum
pairs: Windows x64 .msi, Linux x64 .deb and .rpm, and macOS .pkg on both
Intel and Apple Silicon. It does not build, verify, or upload portable archives,
install packages on CI runners, create a release, sign, or notarize. The separate
manual Publish launcher release workflow reuses these same builds and tests before publication. Each
installer contains a host-native Java 21 image with all JDK modules and bin/java
for games launched by the application; no external Java is required.
Run ./gradlew test buildDebInstaller buildRpmInstaller on Linux,
./gradlew test buildPkgInstaller on macOS, or
.\gradlew.bat test buildWindowsInstaller on Windows. Windows packaging requires
WiX 3.14 in .tools/wix314; CI verifies the downloaded binaries.
The former archive tasks remain available for development but are not CI outputs.
After a release updates main, the native-installer workflow still appears, but its
small read-only tooling job can skip the four duplicate installer builds. It verifies
the exact published tag, version-only candidate and candidate branch, plus successful
build/test/publication jobs from the original release run. Current release and asset IDs,
sizes and SHA-256 digests must also match the small immutable provenance artifact from
that run's successful publication attempt. The gate downloads only that bounded record,
not installers. Missing or expired evidence means normal builds; replaced assets fail
explicitly. Recording/uploading this optional evidence does not block publication.
A release-run trailer is only
a lookup hint, not permission to skip CI. PRs, ordinary code changes, manual builds and
the release workflow's pinned candidate builds still build all platforms. The decision
and original run link appear in the workflow summary. The advisory workflow uses the same
read-only proof to skip its four desktop jobs only for these verified version-only pushes.
PRs, ordinary main changes, manual desktop runs and weekly runs still execute desktop tests.
Missing evidence keeps normal validation; invalid evidence fails visibly. Neither workflow
publishes anything.
Actions run titles explicitly say Launcher build checks or Desktop UI checks, with the event and branch, rather than reusing the release candidate's commit message. After publication, two short workflow entries can still appear: installer verification and desktop-check selection. Verified version-only pushes skip both expensive matrices; the Actions entries themselves are not hidden. Ordinary PRs add one lightweight desktop selection job before their four desktop jobs.
test is the required headless gate: backend/file-safety tests, deterministic UI
components and state, the production automatic-check worker, and safe Windows helper
execution. Tests that open native windows are tagged native-gui and run separately:
.\gradlew.bat nativeGuiTest on Windows, ./gradlew nativeGuiTest on macOS,
or xvfb-run -a ./gradlew nativeGuiTest on Linux. The independent
Launcher desktop smoke tests (advisory) workflow reports real failures on all four
platforms, but does not block release publication. Do not make its jobs required branch
checks. It must execute tests, not report an all-skipped run as success.
The Windows MSI is an additional per-user installation (bundled Java, fixed
install folder, persistent upgrade UUID). Installed Apps, Start Menu, the
desktop shortcut, and executable are named MegaMek Launcher; the icon on
the installed application and installer is derived from MegaMek.png. The macOS
app bundle uses an ICNS derivative of that same source. The
interactive WiX wizard uses composed crops of the launcher first-launch artwork,
shows the costed per-user binary path on Welcome, advances through a modeless
installation progress dialog with a steady status label and live progress bar,
and offers a checked Launch MegaMek Launcher option on its completion screen. This
launch is an interactive finish-button action only: msiexec /qn self-upgrades
never launch the app. It does not change the .msi file association or prompt
for an install directory. mm-launcher remains the compatible internal CLI
and Linux script name.
Version 0.14.5 is a newer MSI for upgrading existing 0.14.4 (or earlier) installations;
rebuilding the same version does not allow a same-version MSI reinstall. The upgrade
keeps the fixed upgrade UUID and install location and leaves user data intact.
The Linux installers use the stable megamek-launcher package identity under
/opt, while macOS uses bundle identifier org.megamek.launcher under
/Applications. macOS's internal package version offsets the numeric major
by one (for example, launcher 0.14.5 uses package version 1.14.5) because
Apple does not accept a zero major; the downloadable filename retains the
launcher version. They contain only program files. Neither installer owns the
per-user registry, logs, settings, or any registered game installation;
upgrades must leave those separate locations untouched. macOS and Linux
installers do not self-update.
Only the Windows MSI installation checks the official MegaMek/mm-launcher
GitHub latest stable release for a newer numeric MSI version. One themed
Update now confirmation authorizes download, verification, closing the launcher,
and installation. Its text states the available version and asks users to close running games.
A cancellable progress dialog shows download and verification;
cancellation stops being available when installer handoff begins. It requires an exact release
MSI asset with a published SHA-256, checks the staged MSI's upgrade code,
product name and product version using Windows Installer, then exits before
the per-user upgrade begins with visible Windows Installer progress (/passive,
no extra wizard and no forced computer restart). After successful completion,
the helper records its result before reopening the launcher; the next launch confirms
the installed version and quietly saves clean success in local diagnostics, without a
success popup. Failures, restart-required notices and staged MSI cleanup warnings remain
visible. Settings offers a manual check.
Active updates are shown as still finishing, not as failed release lookups. Completed
failures are acknowledged so a later manual check can retry. Incomplete reports are
retained as diagnostic evidence: identified targets are confirmed as updated only
with a matching installed version and an exited helper. If the old version is still
confirmed installed, the failed attempt is acknowledged so a later check can retry.
Safely reconciled incomplete reports do not show a popup or get appended to an update
confirmation or release-lookup error. Their original evidence is archived and recovery
details are saved in local operation logs. Old reports without a target remain an unknown
previous result, not a claimed successful update, after checking that the old
helper is absent and Windows confirms the current installation. A successful installation
with staged MSI cleanup failure is reported as a cleanup warning, not as an
installation failure. Pending or unrecognized results remain for review; a
completed result is acknowledged before the release lookup, even when offline.
Helper output and Windows Installer diagnostics are retained beside the registry
as msi-update-helper.log and msi-update-installer.log; update operation logs include
both locations. Windows PowerShell's atomic completion write uses [NullString]::Value,
not $null, which is converted to an invalid empty backup path by its .NET method binding.
If no compatible
official release exists, no upgrade is attempted. Portable archives, macOS
and Linux do not self-update. MSI upgrades replace installer-owned binaries,
not the %LOCALAPPDATA% launcher registry, logs or installed games.
The manually dispatched Publish launcher release workflow is independent of the game-suite
coordinator. Select main and click Run workflow, with no version field. It increments the
committed patch version (for example 0.14.5 to 0.14.6), prepares a version-only commit on
release-candidates/<version>/<run-id> using a dedicated release bot, and captures that
exact candidate. Main's version does not change during preparation or builds. It refuses forks, other
branches, stale dispatches, existing candidate tags/releases (including drafts), and a version
not newer than every published stable launcher release. It never runs automatically on pushes
or pull requests. Major/minor version changes remain explicit source changes.
The workflow calls the ordinary native-installer CI at the captured candidate commit and
waits for all four platforms' builds, package inspections, required headless tests, and release tooling
tests to pass. Only then does one write-enabled
job download this run's five installers and five checksum files, check the exact filenames and
hashes, create v<version> at the tested commit, and upload the complete asset set. GitHub must
report the expected uploaded asset IDs, sizes, URLs, and SHA-256 digests, including the digest used
by Windows self-update.
Publication goes straight to a stable release in that same run, with no draft-review approval
stage. The upload operation briefly uses GitHub's draft flag so an incomplete asset set cannot
become the update target; it is cleared automatically only after verification. The workflow then
verifies the official latest-stable endpoint. Installers remain unsigned and macOS packages are
not notarized. A separate finalization job rechecks the published release, latest endpoint,
tested tag, and version-only candidate, then fast-forwards main without force. Main must
still match the captured base commit; concurrent work is never overwritten.
During upload, GitHub may give draft assets a temporary untagged-... download address.
The publisher accepts it only when it matches that draft's official release-page address and
the exact asset name. Once published, every download address must use the final v<version>
tag, including on the latest-stable endpoint. Asset IDs, sizes, states and SHA-256 checks remain
mandatory throughout.
The release bot needs one-time installation, repository-scoped Contents write permission, an
explicit protected-main ruleset bypass, the LAUNCHER_RELEASE_APP_ID Actions variable, and
the LAUNCHER_RELEASE_APP_PRIVATE_KEY Actions secret. Preparation and finalization each use
a fresh, repository-scoped App token, revoked at the end of their job; neither token is
passed to builds or publication. See
release bot setup.
Writes are not automatically retried, existing assets/tags are never overwritten, and failure
does not delete a candidate branch, tag, draft, or published release.
A failed build or unpublished draft leaves main unchanged. Inspect any partial state before
another attempt: publication and the main update cannot be one atomic transaction.
If publication succeeded but finalization failed, resume only finalization after inspection,
using the same candidate and artifacts; do not publish or increment again. An already-synchronized
candidate is confirmed without another write. If main has advanced independently, an owner
must review a manual version synchronization that preserves that newer work.
Full Windows self-update still requires an older installed MSI and a newer published
version. Publishing the same version as the installed launcher does not trigger an upgrade.
See the distribution contract for validation commands.
The desktop default registry is %LOCALAPPDATA%\MegaMek Launcher\launcher-registry.json
on Windows, ~/Library/Application Support/MegaMek Launcher/launcher-registry.json
on macOS, and $XDG_STATE_HOME/MegaMek Launcher/launcher-registry.json on Linux
(fallback ~/.megamek-launcher/launcher-registry.json). On first default GUI
launch, a valid schema-3 registry from the previous MegaMek location is
copied along with its launcher settings, receipt/metadata directory, and logs
to a new directory via atomic rename. The old state remains intact as a
backup; registered game files are never moved or removed. An unsupported old
schema (including schema 1) is left untouched, so it cannot block a new
install. Unknown future schemas, corrupt schema-3 data, symlinks, pending writes or conflicting new
state stop migration visibly without resetting either location. Custom
--registry paths do not migrate. Future versions should continue reading
schema 3 where compatible; any eventual schema change needs an explicit,
version-aware and backed-up migration, never a speculative unknown-schema
conversion.
These unsigned, non-notarized CI artifacts are not a release; signing and notarization remain outstanding. MegaMek Launcher source code is GPL version 3 or later (see LICENSE.code); distributions carry the same license text alongside dependency notices. Archive builds and their read-only verification remain developer-only.
The developer-only archive tasks can build a versioned all-platform portable archive.
Install an external 64-bit Java 21 or newer runtime, download
MegaMek-Launcher-0.1.0-SNAPSHOT.tar.gz, extract it, and keep the fixed MegaMek Launcher/ tree intact.
Choose the entry point for the current OS:
- Windows:
MegaMek Launcher.exe - macOS:
MegaMek Launcher.app - Linux:
./mm-launcher
The one shared application/dependency payload is inside
MegaMek Launcher.app/Contents/app/lib/. This keeps the macOS app self-contained; the sibling Windows
and Linux entry points deliberately use that same payload. Do not move the Windows executable or
Linux script away from the sibling app folder.
The macOS and Windows prototypes are unsigned, and the macOS app is not notarized. Do not bypass Gatekeeper or other OS security controls. Signing, notarization, and a public source-license decision remain release prerequisites. The generic archive is Java-free; the native installers bundle Java, but the launcher does not download a JRE.
The support goal is all three OSes. The archive's Windows x64 entry point and native startup smoke were validated locally on Windows. CI now builds and inspects native installers instead of running portable archive verification across runners.
Build the one archive and its checksum, then verify its full structure and current native entry point with:
& 'C:\repos\megamek\mm-launcher\gradlew.bat' `
-p 'C:\repos\megamek\mm-launcher' buildArchive verifyArchiveThe generic archive outputs are MegaMek-Launcher-<version>.tar.gz and its exact-name .sha256 file under
build/distributions/. verifyArchive checks all three entry points, the single shared payload,
and checksum, then runs the current host's native entry point. verifyProvidedArchive is the
separate read-only CI mode and requires explicit -PprovidedArchive=<path> and
-PprovidedChecksum=<path> inputs; it never depends on packaging. It is not
used by installer CI. --version and
--startup-check are side-effect-free packaged diagnostics. The desktop launchers also accept
--registry <absolute-path> as an isolated test/development override.
See the archive distribution contract for layouts, program-files/user-data separation, native runner coverage, and release limitations.
After onboarding, Home is application-oriented. MegaMek, MekHQ, and MegaMekLab each have an independent preferred installation. Every application found across the union of registered static package records gets an equal gold split control whose primary label uses the shared Program Channel (Version) format, such as Launch MekHQ Milestone (0.51.0). The arrow lists the other records containing that application in deterministic registry order. An alternate row launches only that exact captured record and never changes a preference.
Primary and alternate launch actions are direct: there is no command/runtime confirmation dump. The existing backend still re-reads the captured registry record, statically reinspects the root and product layout, validates external Java, and acquires the root/process coordinator. Only after the BusyGate accepts the action does the launcher iconify into the normal taskbar/Dock while it keeps running and holding coordination for the child's full lifetime. Exit zero leaves the launcher minimized without a completion dialog or foreground request. A start exception or nonzero exit restores the window and shows the explicit sanitized error. A busy action does not minimize and retains the Please wait response. This is normal window minimization, not tray integration, disposal, detachment, or launcher exit.
Registry schema 3 stores those targets by application key and stable installation UUID. Older shapes, including records containing per-copy Java, remain rejected; only the same schema 3 is relocated between default launcher data directories. Missing or stale targets use the first matching record as a side-effect-free runtime fallback. Registration fills only still-unset application preferences; removal safely retargets affected preferences to the first remaining compatible copy. Strict parsing, canonical roots, locked mutation, and atomic replacement remain required.
Home has no visible global “Main” block, per-copy update buttons, or a second navigation control for update status. Its single Installations button gains a known count such as Installations (2 updates) when checked installations have updates; unknown and launch-only copies never create a false count. Update, retry, recovery, channel, location, removal, and explicit Use as preferred for … actions live on dark-teal installation cards. A managed card shows its read-only Channel: Milestone/Development/Weekly identity and directly shows Check for updates when the launcher opens. This per-card checkbox is the sole automatic-check setting and saves immediately without changing the fixed channel. An imported card says Imported copy · Launch only · Updates unavailable and has no channel, check, preview, update, or recovery action; only its separate opt-in Enable managed updates… action is available. There is no bulk update. Managed Home has one top-left information area for useful aggregate update state: Checking for updates…, All installations are up to date, N installations have updates, or Some installations could not be checked. It does not repeat an obvious post-install “ready” message or place status text above navigation.
The title bar shows the packaged MegaMek Launcher version (or "development build" outside a packaged release). Settings also includes Launcher update on Windows MSI, Community, and Latest news. Latest news reads the official MegaMek Atom feed once per launcher window, independently of installation checks, and shows up to three dated headlines linking to official posts. All news opens the blog archive even if the feed cannot be loaded; news never blocks the launcher. At wide window sizes Settings places Game Java, Diagnostics, and Launcher update in one column beside Community and Latest news in an equally wide column; narrower windows stack the groups in reading order above the fixed bottom navigation. Change default Java validates and atomically stores the sole external Java 21+ executable for every installation. Launch and explicit launch preview resolve and revalidate this setting; installation records contain no Java path or feature. With no saved default, the exact Java runtime executing MegaMek Launcher is the automatic effective runtime and is not persisted. An invalid or corrupt explicit setting fails visibly instead of silently falling back. The pre-release settings schema contains no global update fields and deliberately does not migrate older settings schemas. Settings presents unboxed, left-aligned sections; the Java version and regular-font path appear above the left-aligned change action, and View logs opens a styled local viewer. Settings displays the effective runtime whether it is automatic or explicitly selected. The selector uses the same dark-teal/gold controls. No JRE is downloaded or installed by the application; installed/native launcher distributions already bundle a runtime.
Determinate download bars show only a localized percentage; the detail line uses human-readable sizes (for example, Downloaded 282 MB of 690 MB). Extraction remains indeterminate and reports processed files below the bar.
With no registered copy, the artwork-led Home has an accessible split Install latest MekHQ Milestone control. Its large primary segment remains a one-click route to the normal confirmation for the current official Milestone MekHQ suite, which contains MegaMek, MekHQ, and MegaMekLab. Its validated version is added to the button label, for example Install latest MekHQ Milestone (0.51.0); loading and unavailable states never invent a version. The separate arrow opens a styled dark-teal/gold popup containing exactly:
- Install latest MegaMek Milestone
- Install latest MegaMekLab Milestone
- Install latest MekHQ Development
- Install latest MegaMek Development
- Install latest MegaMekLab Development
- Install latest MekHQ Weekly
- Install latest MegaMek Weekly
- Install latest MegaMekLab Weekly
Each available row includes its independently validated version in parentheses. One background snapshot per launcher-window session discovers complete suite records for Milestone, Development, and Weekly, validates all three exact product references before offering any product, and caches reused release IDs. The record's mandatory SHA-256 remains authoritative when GitHub omits its digest. There is no website YAML or independent-latest-release fallback. While it loads, both install segments are visibly disabled and removed from keyboard focus, while Use existing installation remains available. Failure keeps installation unavailable and exposes one explicit Retry version check command; concurrent retries are deduplicated. The first successful immutable snapshot is retained for the frame lifetime, so Home reloads, confirmation cancellation, location changes, and repeated quote opens preserve the same labels without another current-channel request. Missing channels are disabled with explicit reasons; Weekly-only records remain usable without changing the Milestone default. Explicit Retry can refresh partial availability. A visible secondary Use existing installation action, captioned MegaMek, MekHQ, or MegaMekLab, imports a portable copy without moving it. After the folder chooser, static inspection and atomic registration run in one styled progress window; there is no separate name, confirmation, or completion dialog. The first page has no Java prerequisite/download control and no healthy-first-launch advanced picker. The exact product/release/channel picker remains on Installations after a copy exists or when repair/recovery navigation is relevant.
The first successful import or normal install initializes only still-unset preferences for the applications it actually contains; later or concurrent operations never replace explicit preferences. Imported copies are launch-only and create no receipt/channel provenance. Home uses the union of products statically detected across registered copies. There is no simple/advanced mode toggle. The layout adapts to narrow windows and Java's per-monitor HiDPI scaling. See the first-launch presentation notes. New windows request a 1180-by-820 logical-pixel size, capped to the current monitor's usable work area; users can resize them, but the size is not yet saved across launcher restarts.
Managed Home keeps that composition: full-width cover artwork on top and a compact, scrollable dark control deck below, with no duplicate header/footer or left artwork rail. It renders the per-application split controls and aggregate installation-level update summary described above. A missing root or pending recovery is routed to the exact installation card. An absent saved Java default is healthy and never blocks Home; Java is resolved by the launch worker. Imported/no-receipt copies remain launch-only and are never offered impossible update actions. Their explicit Enable managed updates… flow presents one compact program/version and future channel choice. Continue searches bounded official release-list metadata automatically; it never guesses a tag URL. Exactly one eligible normalized version match proceeds to the existing one-package verification. Otherwise the copy remains launch-only and the user may explicitly choose a different official version from the bounded chooser. Existing application and personal files are not changed by matching or verification.
The existing application/installDist entry point remains the CLI. Requires Java 21. From the
launcher checkout, build and run the graphical subcommand in PowerShell:
Set-Location C:\repos\megamek\mm-launcher
.\gradlew.bat installDist
if ($LASTEXITCODE -ne 0) { throw "Build failed with exit code $LASTEXITCODE." }
.\build\install\mm-launcher\bin\mm-launcher.bat guiGUI tests use the package-local SwingTestSupport harness. Probe Swing state on the EDT,
wait for the actual rendered result rather than service-entry counters or fixed sleeps,
and reacquire controls after a render. Assertions and component counts also belong on
the EDT: finding a component does not make a later off-EDT tree read safe. The shared
counting helpers traverse the entire tree in one EDT turn.
click looks up a showing, enabled control and
clicks it in the same EDT turn. Use startClick or startClickText for actions that can
open a synchronous modal dialog, then await the owned dialog and dismiss it; a synchronous
click cannot finish while its modal dialog is open. Asynchronous listener failures are
reported to the test, and waits have bounded, named timeout failures. Installation menus
must belong to the exact clicked invoker; dialog lookup and recursive teardown must stay
within the tested window's ownership tree.
SwingTestSupportTest exercises hidden/disabled and replaced controls, modal interaction,
exception propagation, timeout diagnostics, and unrelated-window isolation. The existing
GUI classes use the same harness. They require a display (Xvfb on Linux); a headless skip
is not GUI qualification. Two package-local testability hooks expose metadata-worker
completion and let progress tests explicitly flush pending rendering, without changing
the production worker or timer behavior.
Review Home, Installations, Settings, the split Install latest MekHQ Milestone control, Use existing installation, Install another version, Update, recovery, Game Java selection, and direct launch behavior. The primary normal first install uses the greatest complete Milestone suite record's MekHQ target. Each popup item binds its displayed official repository and record membership; no selection can substitute a different product, channel, title, or bundle. Before package transfer it shows a dedicated dark-teal/gold confirmation headed with the selected product and channel. Its concise summary gives the validated version, actual included programs, binary download size, and full selectable destination. Change location chooses an existing parent and safe new subfolder, then produces a fresh immutable quote for that same product/channel pair. Java is not part of installation planning or confirmation. There is no update-behavior row or technical-details toggle in this simple confirmation. The backend still binds and revalidates the exact repository, source, tag, asset identity/digest, destination, registry, and Main snapshot; operation logs retain failures.
Ready first-launch actions plan from the exact cached option, not from another current-record lookup. Local planning still freshly captures destination and registry/default state off the EDT. Before package bytes or parent creation, install revalidates the captured record and its exact references, requiring the same release/asset IDs, name, size, SHA-256, and URL. A newer record cannot silently retarget the quoted install; exact metadata drift requires fresh consent.
Static folder inspection recognizes independent program versions, preferring each root jar's
Implementation-Version and retaining the legacy shared-MegaMek metadata fallback when absent.
The bundle display uses MekHQ's version, otherwise Lab's, otherwise MegaMek's. This does not prove
compatibility or ownership: imported copies remain launch-only until exact official archive
verification succeeds. Conflicting versions of duplicated MegaMek jars remain unsupported.
Routine source and documentation iteration does not rebuild the portable archive. Archive creation and verification remain explicit release-validation work.
With the ordinary GUI registry, launcher-managed application payload/support data defaults to
%LOCALAPPDATA%\MegaMek\<product channel> on Windows,
~/Library/Application Support/MegaMek/<product channel> on macOS, and
${XDG_DATA_HOME:-~/.local/share}/MegaMek/<product channel> on Linux. A relative
XDG_DATA_HOME is ignored. The product/channel child, such as MekHQ Milestone, is required
because registry and receipt metadata may use files in the same support root; the payload is never
installed directly as the MegaMek root. An explicit
gui --registry override, and custom registries supplied to LauncherServices, intentionally
retain <registry parent>/installations/<product channel> for disposable isolation. The macOS
path is managed payload/support data, not content inside MegaMek Launcher.app.
Confirmation is required before any package byte or destination/state write. Metadata drift, an
appeared destination, or registry/Main drift rejects the attempt for fresh consent; no choice
falls back to another repository/channel, a different tag, or “latest.” MekHQ requires the exact
three-program suite; standalone MegaMek and MegaMekLab packages must inspect as only their actual
product, so managed Home never gains launch buttons from a mixed or title-inferred bundle.
The Installations page's dark Install another version dialog defaults to MekHQ and Milestone.
Its Product/Channel/Fetch releases row discovers bounded complete records and shows one page
of product targets with the selected record membership. Page one contains the current product
exactly once; reused tags retain their newest referencing record as the captured source.
All three product references must validate for each displayed target. Titles, publication dates,
and prerelease flags never supply classification, and unclassified repository history is not
a fallback. The selected Channel becomes the new installation's immutable update track.
No picker metadata action fetches package bytes.
Previous/next use the exact loaded product/channel snapshot and a bounded record-history page. Changing either combo clears the rows, selection, and page controls and invalidates late results. Eligible rows use Program Channel (Version) — human size; unavailable rows contain only — Unavailable after the identity. A separate Page N indicator remains in the footer, while the reserved status line is blank after success. The controls use the same styled combo/button treatment and high-contrast fallback as the rest of the launcher.
Before consent, the picker sends the exact choice and captured record source through the
normal-install planner. Current and historical choices revalidate that record and its exact
references, never a newer current record. Before transfer the planner repeats registry/destination
checks and requires the same source, release/asset IDs, tag, asset URL, name, size, and mandatory
record SHA-256. The package must match that digest even when GitHub publishes none.
Every successfully published graphical managed installation
starts with its per-install check-on-open value enabled. See
ci_update.md for the complete-suite record and producer release contract.
Preview asks to fetch official releases and shows the exact package and full size before download.
Preview itself remains read-only and has no Apply control. Update… is a separate workflow:
it previews first, requires a second confirmation that names the destructive intent and exact
size/digest and the captured installation name/path, then acquires the update/launch gate,
re-reads provenance, refreshes small release metadata, re-verifies the retained archive, safely
re-extracts it, and replans immediately before changing managed files. One GUI Update attempt
downloads the full package exactly once; the second confirmation states that it is already
downloaded and no second package transfer occurs. It is available only to copies freshly
downloaded by this launcher or successfully adopted from one exact official ancestor, with a
valid immutable ownership receipt. Unadopted, pre-receipt, and corrupt/incomplete copies remain
launch-only; local hashes alone are never converted into provenance.
For a managed receipt-backed copy, the installation card displays its fixed channel and a direct Check for updates when the launcher opens checkbox. Toggling it immediately writes only that copy's boolean using the exact displayed record/preference binding. Use Install another version to create a separate root on another channel. Existing valid channel sidecars are read without migration writes; missing, corrupt, stale, or unavailable channel state remains launch-only and cannot be assigned through channel settings. Imported folders receive no channel sidecar or ownership receipt unless the separate exact-ancestor adoption succeeds. A check reads canonical channel and exact release metadata only; it downloads no package and changes no installed files. If an update is recommended, Update binds both consent steps to that exact installation, fixed channel, repository, tag, asset name, size, URL, and optional published digest and uses one full-package transfer for the attempt. Older official assets without a published digest remain eligible: the exact bounded body is hashed during that transfer and the computed value is retained in receipt/current state. Update continues to use the same read-only plan internally before its separate Apply consent. There is no standalone GUI preview picker; the CLI diagnostic preview remains available.
Prepared package data is process-local and attempt-scoped, not a persistent/offline cache or
filesystem token. Cancel, success, and ordinary failure remove only that attempt's random temporary
workspace; no later GUI or CLI operation resumes or adopts it. A process crash can leave that exact
temporary directory for manual diagnosis, and startup does not broadly delete such directories.
CLI preview-update remains a separate read-only diagnostic that disposes its workspace.
Standalone CLI apply-update keeps its independent one-download contract and
does not share packages with an unrelated preview or arbitrary path.
Every installation offers Open location and Remove from launcher…. Open location
revalidates the exact root and asks the platform file manager to open it off the UI thread.
Removal deletes only the exact registration and launcher-owned sidecars; application files stay.
The installation More… → Reset preferences… action asks you to close all suite apps
(including those started outside the launcher) and confirms the exact files and backup location.
For a MekHQ package it moves the existing mmconf/clientsettings.xml, mm.preferences,
mhq.preferences, mml.preferences, megameklab.properties and
megameklab.properties.bak; MegaMek-only and MegaMekLab-only packages move only their
respective files. The .bak is included because MML can restore settings from it.
The files are moved, not deleted, into a unique backup under
<installation>/.mm-launcher-preferences-backups/. Unrecognized or ambiguous imported
packages are refused; campaigns, saves, custom files, and all other mmconf contents remain.
OS-global Java Preferences cannot be reset per installation and are not touched.
Managed/adopted copies with complete current provenance and a fixed channel additionally offer
Uninstall…. Uninstall moves only byte-exact current official files into an external,
same-filesystem recovery area, preserves modified/custom/protected files, removes only empty known
package directories, and removes registration/metadata last. A durable strict journal makes every
pre-commit failure recoverable; post-commit cleanup failures are successful uninstalls with a
cleanup warning. The complete move intent is recorded before changes, with phase checkpoints
instead of rewriting the whole journal for every file. Pending work blocks launch/update and
exposes Recover uninstall while the folder exists.
If a user manually deletes an installation folder, More… offers a styled Installation not found confirmation to remove its launcher registration, including a validated interrupted-uninstall record. This does not recreate the folder or touch other installations. Inaccessible locations and unavailable drives are not mistaken for deleted folders. Reinstalling the launcher intentionally preserves the per-user registry and settings; use this registration-removal flow rather than reinstalling to clear a missing game copy.
Fresh download, standalone Preview, prepared Update, and recovery use one functional operation dialog with a compact dark-teal/gold presentation and typed phases: metadata, download, verification, extraction, planning, consent, installation preparation, Apply/uninstall/recovery, and cleanup. Byte totals are shown for package transfer and retained-package verification. File totals are shown only when known; extraction remains indeterminate when the archive has no trustworthy total while its typed event supplies a localized N files processed detail under Extracting application files. Safe user-facing typed details are preferred; paths, URLs, and digests fall back to phase text. Updates are coalesced before reaching Swing. Phase, bar, and detail sit directly on the dialog background without an inner framed backplate. Backend text remains in a hidden bounded legacy capture and structured local logs; it does not create a visible log pane or blank space. While work is cancellable, the only bottom action is the same vector-painted dark secondary Cancel used by launcher controls, including its whole-control custom focus outline. Contextual repair actions remain absent until a failure needs them. After the atomic cutoff, Cancel disappears and the concise status asks the user to keep the launcher open.
Cancellation is safe only before publication or transaction/recovery finalization. A fresh install
stops accepting cancellation immediately before its extracted destination is published; registry,
receipt, and channel finalization then run without interruption. Update stops accepting it
immediately before RealUpdateService creates .mm-launcher-update and its pending transaction.
Recovery is non-cancellable before any replay or cleanup. A request that loses one of those atomic
cutoff races is denied with the reason and never interrupts the worker. Accepted cancellation
closes only the operation-owned pending response, checkpoints hashing/extraction/planning, removes
only owned temporary data, closes the progress window, and returns to Home or the current page
with a cancelled status, not success or a rollback claim. It never kills a game process.
A successful normal install closes progress immediately and returns directly to managed Home. The registered copy initializes only still-unset included-application preferences; existing explicit preferences are preserved. There is no install-complete modal and nothing is launched automatically. Failure stays in the same compact progress surface with a concise sanitized summary, Close, and failure-only View details; the generic error dialog is not also opened. A retained published copy adds Open Installations only after that repair condition is known.
Structured local operation/error logs are stored beside the registry in
<registry-name>.launcher-logs/, never inside an installation, fresh destination, or profile data.
The default retention is 20 closed logs of at most 1 MiB each. Matching files are deleted only
after their schema, registry binding, and operation UUID prove launcher ownership; unknown files
and links are retained. View logs is available in Settings and reads logs
asynchronously into bounded plain text. Copy sanitized details/log is explicit; there is no
automatic clipboard use, upload, telemetry, browser launch, or file-browser path input.
Persisted and copied details remove URL user information/query/fragment data, bearer values,
password/secret/token fields, and configuration-source snippets from Jackson parse chains before
storage or display. Home-directory prefixes are shortened to ~; review local diagnostics before
sharing. The logs can still contain other absolute local paths needed to diagnose placement.
Unsafe/corrupt registry placement, links/reparse points, permissions, or log I/O fail explicitly.
Logging failure never changes the operation result, masks an original failure, or removes update
journals/backups. The current checkpoint adds these functional surfaces only; broader empty,
imported, managed, and recovery-state visual polish remains a separate UI/UX checkpoint.
The primary normal GUI install creates a fixed Milestone copy. Current-channel, exact-history, adopted, and CLI managed installations always initialize check-on-open to true. That value is not part of the install quote and there is no configurable new-install default. Existing explicit false choices remain false, and legacy/unknown/corrupt channel state is never inferred, assigned, or rewritten. The CLI requires a fixed channel for installation and initializes check-on-open to true. Only explicitly enabled copies are checked, serially and in the background; slow or unavailable metadata does not disable healthy launch or page navigation. No check downloads or applies a package. A failed check is shown as “Could not check,” never as “Up to date.”
Before Apply or recovery, close all MegaMek, MekHQ, and MegaMekLab processes, including ones started manually. Launcher-started games are coordinated across launcher GUI/CLI processes for their full lifetime. The launcher cannot universally detect old or externally started processes and does not claim that Windows file locks prove closure. It never kills a process.
There are no automatic Applies, launcher self-updates, Nightly channel, bundled Java, elevation,
or administrator requirement. A channel cannot be changed: another channel always means another
managed copy/root, so there is no cross-channel in-place downgrade or retarget path. Advanced
downloads need an existing writable parent and always create a new subfolder. A styled
dark-teal/gold folder-name dialog validates the preselected friendly
Program Channel (Version) suggestion inline; Cancel or Escape changes no state. The same dialog
is used by Change location, and the registered name is derived automatically from that
friendly label. The normal
destination can create at most four individually quoted real parent directories after consent;
the product/channel target itself must be wholly absent.
Import remains immediate, dialog-free after folder choice, and launch-only. A genuine imported record with no receipt, current provenance, fixed channel, or pending transaction is labeled Imported copy · Launch only · Updates unavailable and has a separate Enable managed updates… action. Incomplete or corrupt managed metadata never gets that action.
The opt-in flow shows the statically detected applications/version and a fixed future channel (Milestone by default, or Development or Weekly). It treats that information only as a candidate. Before offering Enable, the launcher resolves one exact release in the matching fixed official repository, requires its bounded asset name/size and validated official URL, downloads that package once, verifies a valid published SHA-256 when present (or records the SHA-256 computed from that one exact body when absent), safely extracts it outside the application root, and compares its static product/build identity and ownership inventory with the imported copy. If automatic version lookup is unavailable, the user may explicitly choose an exact version from the bounded official release browser. Release titles and GitHub prerelease flags do not establish identity or channel history.
All official application executables, application JARs, dependency JARs, and launch metadata must match byte-for-byte. Missing managed files, case/Unicode aliases, links, structural conflicts, a product/build identity mismatch with the selected archive, or a linked/special runtime path fail closed. Ordinary Update replaces damaged or missing official runtime files and removes obsolete official runtime, retaining a verified rollback backup until commit. Extra unowned JARs under lib are preserved (they may affect the game). Modified non-runtime official files are recorded as pre-existing overrides; unknown custom files and protected saves, campaigns, userdata, configuration, and logs remain outside launcher ownership. The current architecture cannot durably represent intentional missing managed files, so adoption rejects them rather than allowing a later update to restore them silently.
Preparation never writes beneath the application root. Confirmation re-enters the shared root gate, rechecks the exact registry binding and absence of all provenance, compares a complete immutable local snapshot, revalidates the retained package and exact release metadata, and then publishes receipt, current state/override history, adoption binding, and fixed channel outside the root. The channel is the final visibility barrier; failed publication rolls back only attempt-owned metadata and leaves the copy launch-only. Success reports Managed updates enabled, does not launch or update anything, and makes the normal Check/Update path available for later explicit use. There is no second package download and no Nightly adoption.
The official source hierarchy is singular: the canonical release index is the long-term release and channel authority; an embedded package identity is a signed/build-produced projection that assists exact candidate selection; the launcher receipt and fixed track are local policy. Adoption never trusts an embedded marker, local version string, or registry field by itself.
See the real-update contract, GUI contract, the channel/check contract, and read-only CLI preview contract, plus the safe uninstall contract. The archived command-line fresh-install walkthrough remains in docs/cli-fresh-install.md.