# Releasing Fusion without Steam (v0)

BUILD.md steps 6.4-6.6. Code: `tools/fusion-cli/src/release.rs` (making a release),
`tools/fusion-launcher` (`Fusion.exe`: installer, launcher, updater), `crates/fusion-core/src/release.rs`
(the release feed, installs, crash reports).

## Publishing a new version (one command)

From the repository, with the site's folder (`SteonModWeb`) next to it:

```
.\tools\publish.ps1 -Notes "What's new, in a sentence."
```

It picks the next version (one patch above the newest in the feed; `-Version 0.2.0` to choose),
builds the release with its smoke test, copies the docs into the site, stages the release and the
hub (`stage-fusion`), **asks**, uploads the site and only the files that are new
(`push.ps1`, `push-fusion.ps1`), then checks that steonmod.com's feed lists the new version.
`-NoUpload` stops before uploading; `-Yes` doesn't ask. The **SteonMod Tools** menu on the
Desktop runs this (option 1) and every other publishing and server job.

Players get it the next time Fusion starts, or within the hour if Fusion is open (see Updates).

## Making a release (by hand)

From the repository, with Godot 4.7.2 at its usual place:

```
target\debug\fusion-cli.exe release build --version 0.2.0 --out target\release-steonmod --key %USERPROFILE%\SteonMod-keys\official-release.key --notes "What's new, in a sentence."
```

It builds Fusion's binaries in release mode, puts a self-contained copy together, imports its
resources and **plays a scenario with it** (the test game in Fusion's playground, with the
release's own engine) before packing anything. Out of it, in `target/release-out/`:

| File | What it is |
|---|---|
| `Fusion-Setup-0.2.0.exe` | the installer new players download |
| `Fusion-0.2.0.zip` | the same Fusion, for updates |
| `releases.toml` + `.sig` | the signed release feed the launcher reads |
| `hub-packages/*.fusion` | game routers (VEIN, Minecraft) as signed packages for the content hub |
| `stage/0.2.0/` | the unpacked copy (and `smoke.log`) |

**What a release holds:** `engine/godot.exe` (Godot 4.7.2, MIT), `godot/` (the project without
its tests; `fusion.gdextension` points at `bin/`), `bin/` (Fusion's programs and the Unreal kit's
DLL), `routers/` (only Fusion's own: `fusion`, `fake`, `sdk-example`), the scenarios those can
play, `kits/` (the Unreal kit's Lua and agent pack, the Fabric kit's jar), creator docs,
`THIRD-PARTY-NOTICES.md`, `THIRD-PARTY-LICENSES.txt` (every Rust crate's license, from
`cargo metadata`) and `version.txt`. **Game routers are never in the base app** (VISION.md §11
rule 1): they're packages on the content hub, so a takedown removes a router, not Fusion.

## Publishing on steonmod.com

Fusion is hosted on **steonmod.com** (the user's site, `SteonModWeb`; its `DEPLOY.md` has the
details). The site serves everything as plain signed files from its `data/fusion/` folder:

| Address | What | Fusion's default |
|---|---|---|
| `/fusion/releases/releases.toml` (+ `.sig`, zips, installers) | the release feed | `settings::DEFAULT_UPDATES` |
| `/fusion/hub/index.toml` (+ `.sig`, `feed.toml`, `packages/`) | the content hub | `settings::DEFAULT_HUB` |
| `/api/fusion/compat` | compatibility reports (POST), their totals (GET) | `settings::DEFAULT_REPORTS` |
| `/download`, `/routers`, `/docs` | the pages people see | |

From the site's folder, after a release build:

```
npm run sync-docs
npm run stage-fusion -- --release ..\SteonMod\target\release-steonmod --key %USERPROFILE%\SteonMod-keys\official-release.key
.\deploy\push.ps1 -Server root@SERVER
.\deploy\push-fusion.ps1 -Server root@SERVER
```

`stage-fusion` copies the release and its feed, packages each game router again with
`package router` (the publish check) and adds it to the hub with `hub add` (which needs a passing
conformance report), skips versions already on the hub, and signs the hub's feed. A router that
can't be published is listed with the reason. To take a router down for everyone, add
`--no-routers --block <router>:"reason"` and push the files again. Moving to another host is copying
`data/fusion/` and changing the three defaults.

## Installing, as a player sees it

`Fusion-Setup-<v>.exe` asks once, then installs **for the current user** (no administrator rights)
into `%LOCALAPPDATA%\Programs\Fusion`:

```
Fusion.exe          the launcher
current.txt         which version runs
versions\<v>\       a whole Fusion; the version before is kept
```

It adds Start menu and desktop shortcuts, an entry in Windows' installed apps (Uninstall works from
there, or `Fusion.exe --uninstall`), and opens `.fusion` package files with Fusion (double-click →
the content browser). Uninstalling asks before removing the player's own data
(`%LOCALAPPDATA%\Fusion`: settings, signing key, installed content), and keeps it by default.

## Updates

Like Steam, Fusion updates itself without asking, unless the player turned off **Settings →
Updates → Update Fusion automatically** (then it asks first).

- **Each start**, `Fusion.exe` reads the release feed (if Settings has an update address),
  checks its official signature, and installs a newer version with a progress window (Cancel
  keeps the current one). Offline, a host that's down, or a feed that isn't Fusion's (a hotel
  Wi-Fi's login page) just starts the version it has.
- **While Fusion runs**, it checks the same feed at start and every hour (`FusionUpdates`),
  downloads a newer version **next to** the current one, and the header shows a green **Restart
  to update** (players who are asked first see **Update available**). The next start, even
  offline, switches to it at once.
- **The launcher updates itself:** each release carries `bin/Fusion.exe`; when it differs from the
  installed `Fusion.exe`, the running one moves aside to `Fusion.exe.old` (removed next start).
  A running Fusion does the same, so 0.1.0's launcher (which can't) is replaced by 0.1.1.

Every download must match the feed's sha256 and size; it unpacks to a temporary folder and is
renamed into place, so a failed update never leaves half a version. Kept: the current version, the
one before (to go back), and a newer one waiting for the next start.

**Never add a field to the feed while 0.1.0 launchers may be around:** 0.1.0 refuses a feed with a
field it doesn't know, and then won't start Fusion at all (a test in `fusion-core::release` checks
what the feed writes). From 0.1.1 on, unknown fields are ignored. That's also why there's no
"required update" flag yet: with updates automatic by default, nearly everyone gets every release.

## When Fusion crashes

The launcher notices (Windows' exit code), saves a report with the end of Fusion's log in
`%LOCALAPPDATA%\Fusion\crashes\`, and says so. If the crash came within 30 s of an update, it
offers to go back to the version before. Next time, the main menu mentions the report once.

Before starting a game, the session looks for a crash reporter that game left open
(`CrashReportClient.exe`, `UnityCrashHandler64.exe`, BugSplat): Steam won't start a game while its
reporter is open, so Fusion says so and offers **Close it**.

## Still the user's decision (it costs money)

- **A code-signing certificate.** Without one, Windows SmartScreen warns about the installer
  ("Windows protected your PC" → More info → Run anyway) and some antivirus tools may flag a
  program that starts and talks to games. With a certificate: sign `Fusion.exe`, the installer
  and `bin\*` with `signtool sign /fd sha256 /tr <timestamp URL> /td sha256 ...` before zipping.
- False positives: submit the installer to Microsoft (https://www.microsoft.com/wdsi/filesubmission)
  and to any vendor that flags it.
