Files
healer-man/CONTENT_UPDATES.md
2026-08-14 15:56:39 -04:00

6.5 KiB

Local-first content updates and Android releases

Healer Man separates the installed game client from large game content. The Android APK contains the React/Three.js runtime, native Android bridge, menus, local profile and save support, UI assets, and the Wailing Caverns starter pack. Other GLBs, textures, audio, character packs, dungeons, and Manastorm stages are published by TrueNAS as optional content.

Single-player is local-first. Starting the game, loading a character, entering installed content, simulating combat, and saving progress never require the account server. Online authentication, cloud synchronization, rankings, and content discovery are opportunistic services rather than gameplay dependencies.

Update boundaries

Content-only changes do not require a new APK when the installed client already understands the data:

  • GLB additions or replacements
  • textures, icons, audio, and previews
  • data-driven encounter, loot, dialogue, or balance data
  • new files added to an existing compatible content pack

A new Obtainium APK is required for changes to JavaScript/React gameplay code, UI behavior, shaders or loaders, native Android code, permissions, or the content-manifest schema. A manifest can set minimumAppVersion so an older APK will not activate incompatible content.

The browser build remains a full build served by TrueNAS. It receives client code when the app checkout is pulled, rebuilt, and restarted. The Android build is deliberately separate and compact so the APK does not inherit the complete multi-gigabyte browser asset catalog.

Measured on August 14, 2026, the production browser dist is 3.88 GiB across 18,895 files; GLBs account for 3.75 GiB. The compact Android dist-android is 148.0 MiB across 5,561 files, including 47.8 MiB of starter GLBs. The final signed APK will be smaller because Android packages compress eligible files; record its exact size after the first release build.

Content manifest

npm run content:publish scans runtime content, calculates SHA-256 for every file, writes immutable objects, and publishes /content/manifest.json last. Each manifest entry contains:

  • a stable logical path used by the client
  • an immutable object URL derived from the file hash
  • the SHA-256 digest and byte count
  • the logical pack that owns the file

The runtime continues to request stable paths such as:

/assets/game/dungeons/wailing-caverns/wailing-caverns-visual.glb

The content resolver maps that path to a verified file in Android app-private storage. Filenames are therefore not used as version numbers and runtime code does not need to know the hash.

Safe publication and activation

Publication order is intentionally atomic:

  1. Hash the source files.
  2. Copy any new immutable objects into the content volume.
  3. Write the versioned manifest.
  4. Replace manifest.json only after all referenced objects exist.

On a device, the client fetches the small manifest and compares it with its installed manifest. Only missing or hash-mismatched objects are downloaded. Android streams each object to a .part file, supports HTTP range resume, verifies its size and SHA-256, then renames it into the object cache. The active logical-path map changes only after the complete pack verifies. An interrupted or corrupt update leaves the previous pack playable.

The starter pack is bundled into the APK. Downloaded packs live under Android's app-private files directory, outside the packaged web bundle, and survive an APK update. Players may remove optional packs through the content manager; save data is stored separately and is never removed with content.

Offline behavior

  • The app never blocks startup on a network request.
  • Bundled and previously installed packs remain playable offline.
  • The last verified content map is read synchronously from local storage.
  • A failed update check is silent outside the content manager.
  • A failed or interrupted download never replaces active content.
  • Local saves are authoritative during play and cloud synchronization retries when connectivity returns.
  • Content that has never been downloaded is clearly marked as unavailable offline instead of loading a broken scene.

Loading the game client itself from a remote WebView is intentionally avoided. Keeping the runtime in the APK is what makes offline startup reliable.

TrueNAS layout

/mnt/usbssds/apps/healer-man/app       Runnable Git checkout
/mnt/usbssds/apps/healer-man/data      SQLite accounts and cloud saves
/mnt/usbssds/apps/healer-man/content   Published manifests and immutable objects
/mnt/usbssds/apps/healer-man/backups   Database backups

The Node server exposes the content volume at /content. Manifests use no-cache; immutable hashed objects use long-lived cache headers and support byte ranges for resumed downloads.

After pulling source on TrueNAS, publish content before restarting the app:

CONTENT_DIR=/mnt/usbssds/apps/healer-man/content \
npm --prefix /mnt/usbssds/apps/healer-man/app run content:publish

Content objects are uploaded before manifest.json, so a client can never discover a manifest whose files have not been published.

Pack policy

Packs are player-facing groups while files remain individually addressable. This permits a fast initial pack install and small later patches. Current pack IDs are derived predictably:

dungeon:wailing-caverns
dungeon:forsaken-abbey
characters:wow335a-v1
characters:ascension-v1
characters:runewaker-v1
manastorm:<stage-id>
shared:creatures

Changing one GLB changes one object hash. The client downloads that object and atomically updates the pack rather than downloading every dungeon again.

Obtainium release policy

Obtainium monitors the Gitea Releases page. APKs are release attachments, not files committed to main. Every APK release must:

  • increase Android versionCode
  • use a semantic versionName and matching Git tag
  • be signed with the permanent Healer Man release key
  • include a SHA-256 checksum attachment

Content publication is independent from this process. Routine asset updates can be published immediately without creating an APK release. Client or native changes use the signed APK workflow documented in DEPLOYMENT.md.

Compatibility and rollback

Manifests have a schema version and minimumAppVersion. The client refuses an unsupported schema or incompatible minimum version. Immutable old objects are not overwritten, so the previously active manifest remains a valid rollback. Content-volume cleanup must retain all objects referenced by the current and previous production manifests.