# I Want To Heal — Thor healer roguelike

Playable low-poly third-person combat vertical slice for AYN Thor's dual displays.

Offline-first frontend includes three timestamped save slots, TrueNAS accounts
and cloud saves, Hunter Profile statistics, boss and roguelike leaderboards,
boss collection logs, Settings, and PvE/PvP mode entry points. Offline saves
remain playable without an account and can be uploaded after sign-in.

## Run

```bash
pnpm install
pnpm dev:api
# In a second terminal:
pnpm dev
```

The Vite development server listens on `0.0.0.0:4173` and proxies `/api` to the
local production/API server on `127.0.0.1:4174`. Both processes use the same
client API contract as TrueNAS.

## Android / AYN Thor test APK

The Android host uses Capacitor, locks to landscape, requests immersive mode and
60 Hz, and sends controller input directly to the game WebView. Build an
installable debug APK:

```bash
pnpm android:apk
```

Build explicitly against the TrueNAS API with `pnpm android:apk:truenas`.
Native builds also default to `https://iwanttoheal.phenomrom.com` when no API
base override is supplied.

Output is written under `android/app/build/outputs/apk/debug/`. With Android
platform tools and a connected Thor, build and install it with:

```bash
pnpm android:install
```

The Android host routes the main and tactical surfaces to separate physical
Thor displays while preserving one authoritative game state. If only one
display is available, Select (or Tab with a keyboard) opens the tactical surface
over the main game view.

PC and handheld browsers use the Thor top screen as a responsive, full-viewport
game surface. The compact ability strip keeps combat controls visible; Select
or Tab opens party, map, inventory, and other tactical detail. Use
`?layout=thor-preview` to restore the stacked dual-screen hardware mockup for
browser QA.

## TrueNAS deployment

Complete first-install, local-Gitea clone, YAML, update, and verification steps:
[DEPLOYMENT.md](DEPLOYMENT.md).

The live TrueNAS web server uses the existing deployment address and port:

- Public URL: `https://iwanttoheal.phenomrom.com`
- Host/container port: `4173`
- App directory: `/mnt/usbssds/apps/iwanttoheal-mmo/app`

### Server architecture

TrueNAS is the online production game server. Its Node process serves the browser
application and authenticated `/api` routes on port `4173`; the reverse proxy
exposes both at the public URL above. SQLite data persists at `/app/data/game.db`
through the separate TrueNAS data mount.

The server owns account credentials, 30-day sessions, three cloud-save slots per
account, boss-kill rankings, and roguelike highest-round rankings. Passwords use
scrypt with per-account salts; clients store only opaque session tokens. Local
saves remain available for offline play. Gitea is a separate TrueNAS service used
for source hosting and deployment.

Clone from the TrueNAS-local Gitea bare repository into the app directory, then
deploy `compose.yaml`. The expected source path is:

```text
/mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/i-want-to-heal-mmo.git
```

Current configuration:

- use `corepack pnpm install --frozen-lockfile`, not `npm ci`;
- run `pnpm db:init` before starting the production server;
- mount `/mnt/usbssds/apps/iwanttoheal-mmo/data` at `/app/data`;
- keep `CORS_ORIGINS` configured for the public site and Capacitor host;
- keep the source checkout and persistent data in separate mounts.

The existing reverse proxy forwards the public hostname to port `4173`, including
all `/api` routes.

## Publish updates to Gitea

The repository target is:

```text
https://git.whoagland.com/phenom/i-want-to-heal-mmo.git
```

Create `git-token` in the repository root. Paste only the Gitea token into it:

```text
gitea_token_value_goes_here
```

Do not add quotes or a `GITEA_TOKEN=` prefix. The exact `/git-token` path is
Git-ignored. Restrict local file access with `chmod 600 git-token`. The publisher
reads it automatically. `GITEA_TOKEN` remains available as an optional override.
Publish from `main` normally:

```bash
pnpm publish:gitea -- --message "Describe the update"
```

The script installs locked dependencies, builds the web app, runs web and Android
checks, builds a debug APK, commits all project changes, pushes `main`, creates a
version tag and Gitea prerelease, and uploads the APK plus its SHA-256 file. It
uses `package.json` version unless that version is already tagged, then advances
the patch number. Pass `--version 0.2.0` to choose an explicit version.

If the publisher runs where TrueNAS paths are visible, it prefers a
fast-forward-only pull directly from TrueNAS-local Gitea storage. Otherwise it
prints the exact local clone/pull commands to run in the TrueNAS shell. The pull
refuses to run when the checkout still points at the old game's repository;
archive that checkout and clone the new repository first.

Preview checks without changing Git or TrueNAS:

```bash
pnpm publish:gitea -- --dry-run
```

Git push credentials still come from Git's credential manager. The configured
Gitea token handles release creation and APK uploads. Android signing keys remain
outside the repository.

## Controls

- `WASD` / left stick: move
- `Q` and `E` / D-pad: cycle party target
- `1`–`6`: cast Smite, Renew, Shield, Purify, Radiance, Flash Heal
- Gamepad: PlayStation `□`, `△`, `○`, `✕`, `L1`, `R1` map to those abilities
- `Select` / `Tab`: open or close the tactical interface on one-screen devices
- `M`: tactical map
- `I`: inventory and item tooltip
- `Enter` / `START`: begin or reset encounter

Touch controls on lower display support party targeting, ability casting, map, and inventory.

## Character model rollout and rollback

Healer characters use the modular `Rig_Medium` renderer by default. Version 1 composes
head, upper body, lower body, headwear, back item, main hand, and offhand slots while
continuing to use Aelia's shared animation set.

Load a hunter, then open **Appearance Lab** from the main menu. The upper display shows
the real in-game healer renderer with idle, walk, and cast previews. The lower display
selects healer class and cycles every available part. **Save look** persists the current
class, **Reset** restores its authored default, and **Cancel** discards drafts. **Compare
legacy** shows the previous whole-character model without deleting the modular selection.

Some source assets currently fuse related pieces, so version 1 exposes honest combined
slots such as face + hair, shirt + arms, and pants + shoes. These can split into finer
customization slots when compatible rigged assets are added.

The previous whole-GLB renderer remains intact during rollout. Use either rollback:

```text
?characterModels=legacy
VITE_CHARACTER_MODEL_MODE=legacy
```

The query changes one browser/app launch. The environment variable produces a legacy
build. Remove the switch to return to modular rendering.

## Current game scope

- Five-member AI party with Disc Priest healer
- Animated Druid healer, Knight tank, Ranger, Rogue, and Mage party models
- Animated bosses using canonical tracked game models
- Telegraph, charge, 0.75-second knockdown, and return-to-tank behavior
- Three-charge cycle into a five-second stack marker and 300-damage shared pounce
- Tank pressure, party-wide Cinder Nova, dispellable Ember Brand
- Full cooldown, mana, healing-over-time, shield absorption, and victory/defeat simulation
- True display ratios: 1920×1080 top and 1240×1080 bottom
- Android layout targets: approximately 960×540 CSS pixels top and 620×540 CSS pixels bottom
- Lower-screen typography scales against its own container, never the main page viewport

The Android build uses distinct display contexts that project one authoritative
game state across both physical Thor displays. PC and Steam Deck use the same top
surface with an adaptive tactical overlay.
