Files
healer-man/CONTENT_UPDATES.md
T
2026-09-12 11:07:25 -04:00

8.6 KiB

Local-first content updates for browser and Android

Healer Man separates the installed game client from large game content. Both the production browser build and Android APK contain a compact required set: the React/Three.js runtime, menus, local profile and save support, UI assets, starter equipment, Wailing Caverns starter content, and the basic Human character model with its default male and female appearances. Android also contains its native bridge. Other GLBs, textures, audio, character/skin packs, and dungeons are published by TrueNAS as optional content. Internal Manastorm environment packs remain in the catalog only because regular dungeons reuse them as automatic dependencies.

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.

Production builds deliberately stage only required content. npm run build prepares .web-public and emits the compact browser dist; Android prepares .android-public and emits dist-android. The development server continues to use the source public tree so asset-pipeline work remains convenient. The complete source catalog and published immutable objects stay on TrueNAS, not in every player's initial browser or Android installation.

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 or, in production browsers, to the app-managed content cache through a same-origin service worker. 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.

The client fetches the small manifest and compares it with its bundled and installed file identities. Only missing or hash-mismatched objects are downloaded. Browsers download into a temporary Cache Storage staging area, verify every object's byte count and SHA-256, and promote the pack to the active cache only after the complete pack succeeds. 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. An interrupted or corrupt update leaves the previous pack playable on either platform.

The Downloads screen uses the same lifecycle in browser and Android. A missing pack offers Download, an installed pack with changed files offers Update, and an installed optional pack offers Remove. Required packs are marked as included and cannot be removed. Browser packs live in the app-managed cache; Android packs live in app-private storage and survive APK updates. Save data and roster identities are stored separately and are never removed with content.

Character packs are presentation-only. When a selected race's pack is absent, removed, disabled, or fails to load, the shared character renderer uses the matching-gender basic Human model and default appearance. The saved race, class, equipment, progression, and appearance choices remain intact and become visible again after the pack is installed.

Original spell effects and authentic sounds are enabled by default. The core:combat-media pack contains the original models and textures for every mapped WoW class visual, plus authentic sounds. It downloads automatically when enabled and stays outside required bootstrap content. Missing visual phases use generated effects; missing sounds stay silent. Removing the pack returns visual playback to the generated fallback. Run npm run effects:wow before the normal content publishing workflow to regenerate the original assets.

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.
  • Missing optional character packs fall back to the basic Human model.
  • Generated spell effects remain available without original spell media.
  • 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 compact production browser client at / and the complete downloadable-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 compact initial install and small later patches. Required pack IDs include:

core:runtime
core:ui
core:equipment
core:character-basic
dungeon:wailing-caverns

core:character-basic owns the two basic Human rigs, the character manifest, and only the default texture selections needed by the fallback. The remaining human customizations and every non-Human character model/skin are optional.

Downloadable pack IDs include:

dungeon:forsaken-abbey
characters:wow335a-v1
characters:ascension-v1
characters:runewaker-v1
core:combat-media
shared:creatures

manastorm:* and core:arenas remain valid internal pack IDs for compatibility but are hidden from the Downloads screen. Installing a regular dungeon still installs its exact environment dependency closure automatically.

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.