Cloudward
Sync your Outward saves across devices via a mounted/shared folder: boot-time reconcile, offline-first play, fork detection/resolution, crash-safe session lock. Verified across real devices; some offline/crash-recovery edge cases still being staged.Cloudward changelog
0.2.0 — 2026-08-28
-
You can now see the launch sync working. When the boot pull takes more than a moment (a first sync, or a big share over a slow connection), a small progress bar and status line appear at the bottom of the main menu — "checking share…", "syncing saves… 12/44", then a brief "sync complete — pulled 40" — and disappear on their own. A fast sync shows nothing; there is no setting. Previously a multi-minute first sync was indistinguishable from a hang without reading the log.
-
Boot sync can no longer fail in total silence (V-SILENTBOOT, found live on a device that never pulled the share's characters). The boot pass now logs a synchronous
boot reconcile queued:line before any mount I/O, aboot reconcile done: pulled=… pushed=…summary on completion (including the previously-silent save-in-progress skip), and apatch ledger: applied=2/2line proving the Harmony hooks bound. A stall watchdog warns once when a background sync op runs past 60 seconds — the signature of a hung network mount, which blocks forever without throwing and used to silently starve every heartbeat for the rest of the session. -
A misconfigured share that resolves to a local folder now names itself (V-SHADOWSHARE). A one-shot boot probe reads the game process's own mount table and warns if
[Sync] MountPathresolves to a local filesystem instead of a real network share, instead of silently syncing against an empty private replica forever. -
The save-set pin no longer engages at every main menu (V-MENUPIN). It was reading a local-player UI slot count that is true from the moment the main menu itself is shown, not from an actual character being loaded — so the boot pull deferred on every single launch, on every device, since the pin shipped. Removed in favour of the three real signals (save in progress, a load sequence running, any character actually in use), which already cover the wedge this pin exists to prevent.
-
The Thunderstore package now actually ships the preloader patcher.
Cloudward.Preloadhas to live inBepInEx/patchers/: it is what applies an adopted payload, and it can only run before the plugins load. It was never included in the Thunderstore package at all, so for anyone who installed Cloudward that way the payload tier could sync but never apply. Manual installs and the hand-off bundle already shipped it. -
Fixed: the
overlays/folder was about to be flattened by r2modman, which would have left the config tier with no overlays to read. Same packaging cause as the Beastwhispering fix; 0.1.1 predates the folder, so no released version was affected. -
Also since 0.1.1: the kit versioning contract (
KitContracthandshake,COMPAT_SINCE,[BepInDependency]version floors), a wave of payload-integrity and reliability fixes (locked+gated boot repair, quit-time publish budget, stale-receipt generation checks, share-tree crash windows, late-mount recovery, join-race protection), clearer failure logging throughout (say when a decision was downgraded; several silent failures made loud), a single share-temp-name/path-denormaliser instead of three separate ones, and the Forge shell / dev-tooling work this mod's live-testing leaned on all session.
Payload sync (mod folders + mod config)
- New
[Payload]tier: sync your mod folders and mod configuration across devices, alongside saves. Off by default ([Payload] Enable = false) — it rewritesBepInEx/plugins, so it has to be a decision rather than a side effect of turning save sync on. - Change detection is free in the steady state. A directory walk produces a size+mtime signature;
if it is unchanged, the tier's content fingerprint is reused and zero bytes are hashed. On a
miss, only the files whose stats moved are re-hashed. mtime is a cache key only — content SHA256
is always the authority, because
cp -a,rsync -aand installers all preserve or reset mtimes in ways that lie. - Adopting a payload never touches the running session. BepInEx loads plugin assemblies at
process start and reads each plugin's
.cfgin its constructor, so an incoming payload is fetched intoBepInEx/cloudward-staged/and applied by a new preloader patcher (BepInEx/patchers/Cloudward.Preload.dll) at the next launch, before the chainloader runs. The patcher verifies every staged file's SHA256 before touching anything: a truncated network fetch discards the staging tree and leaves the install untouched, rather than producing a half-updated plugins folder that loads no mods at all. - The config tier moves values, not files, and only the keys named in
plugins/Cloudward/overlays/*.cfg.overlay— the same allowlistscripts/sync-config.pyuses. Everything else in your.cfgfiles is host-local and its bytes are never rewritten, soMountPath,DeviceName, keybinds, resolution and joystick GUIDs cannot travel. A no-op merge is byte-identical, enforced in code. - The first boot against a new share reports what it would do and changes nothing.
- Configs, saves, logs, pet files, registries and dev command channels can never ride the plugins
tier — that exclusion is compiled in, not a config setting, and
ExtraExcludescan only add to it. - Divergence (both sides changed) pauses by default rather than guessing: unlike a save fork there is no newest-wins heuristic worth automating when a wrong pick installs the wrong code.
- Backups before every replace, on the share and locally;
payloadrollbackrestores. - Kill switch that works without launching the game: create
.cloudward-disablein the game root. - Refuses to run on an IL2CPP install, and refuses to adopt a payload from a device whose BepInEx loader differs from this one's.
- New verbs:
payloadstatus,payloadscan,payloadpush,payloadstage,payloadresolve,payloadrollback,payloadapply. - Save-tier fixes carried along: the device-local state file is now written temp-then-swap (a second background worker now exists in the process), and a share set up payload-first is recognised as real data by the mount-marker recovery check.
0.1.0 — initial release
- Cross-device save sync via a mounted/shared directory: always-local writes, boot-time reconcile (pull), play/quit push, offline-first.
- Fork detection via a share-side generation ledger; a genuine divergence pauses instead of
auto-merging or auto-discarding (
syncresolve local|share|both). - Marker-file mount guard (an unmounted mountpoint looks like an empty directory otherwise).
- Crash-safe heartbeat session lock — a stale lock is reclaimable by any device, and a device's own crashed lock reclaims instantly on relaunch.
- Verbs:
syncstatus,syncnow,syncresolve,synclock,syncmarker,selftest. - Alpha — design complete, compute layer unit-tested (
tests/Cloudward.Tests), not yet live-verified on a real multi-device session. See the README's known-limitations note.
