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

201 lines
8.6 KiB
Markdown

# 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:
```text
/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
```text
/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:
```sh
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:
```text
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:
```text
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.