Compare commits

...
1 Commits
Author SHA1 Message Date
Warren H ff64c6ddcc Android build v1.1.32 2026-07-07 15:07:14 -04:00
8 changed files with 69 additions and 12 deletions
+4
View File
@@ -15,6 +15,9 @@
- Controller navigation must not depend on touch, mouse, keyboard, or hidden developer-only shortcuts.
- Focus state must always be visible and predictable when controller navigation is active.
- Avoid interaction patterns that trap focus, lose focus, or require precise pointer input.
- Controller navigation fixes must not break live combat bindings. When changing shared input routing, verify that combat-only actions still win during active combat: movement, ability casts, party targeting, pause, and overlay navigation must all work on AYN Thor controls.
- `data-game-nav-active` and other custom navigation markers may coexist with live combat HUDs. Do not treat those markers as menu mode during active combat unless a pause/dialog/result overlay is visible.
- Before considering any controller input change complete, test both states: no-overlay combat and menu/dialog navigation. No-overlay combat must include at least one successful ability cast from a controller binding and continuous analog movement on the AYN Thor control scheme.
## Architecture Requirements
@@ -34,6 +37,7 @@
- IWT2 rendering can use a 2D canvas/game runtime, but renderer objects must not become the source of truth for saveable gameplay state.
- IWT2 needs continuous movement input: left analog stick on AYN Thor and WASD on keyboard. Do not rely only on existing menu-style navigation actions for arena movement.
- IWT2 menus, dialogs, inventory, collection log, pause overlays, and game-select screen must still follow controller navigation requirements.
- IWT2 combat must preserve AYN Thor face/shoulder button ability bindings even when party frames, HUD controls, or target rails use custom controller navigation state. Menu navigation priority must never swallow ability inputs while `data-combat-active="true"` and no overlay is open.
- IWT2 boss mechanics and indicators must be modular. Add reusable hit-shape, damage/status, telegraph/indicator, hazard, and marker primitives under `src/modes/iwt2/sim/` or `src/modes/iwt2/render/` instead of hardcoding boss-specific rules in Phaser scenes. Boss AI may compose these primitives, but renderer code should draw sim-owned indicators generically.
- Every IWT2 boss must have a boss pet collection-log reward. Add the pet to IWT2 boss reward metadata when adding the boss, and keep boss kill pet drops at a rare 1 in 500 chance.
- IWT2 boss and party movement must be built with crowding and hazard escape in mind from the first implementation. New bosses must not rely on straight-line chase or single-vector hazard avoidance if the boss can create ground effects, wall pressure, summons, or multi-boss overlap.
+31 -3
View File
@@ -54,12 +54,18 @@ code updates to be a Git pull plus app restart.
Portainer is not required. Use TrueNAS **Apps > Discover > Install via YAML**.
Repository:
Repository URL:
```text
https://git.whoagland.com/phenom/i-want-to-heal.git
```
Local Gitea repository path on the same TrueNAS host:
```text
/mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/i-want-to-heal.git
```
TrueNAS paths:
```text
@@ -67,12 +73,15 @@ TrueNAS paths:
/mnt/usbssds/apps/iwanttoheal/data
```
Create the app directory and clone the repo:
Create the app directory and clone the repo. When Gitea is hosted on the same
TrueNAS box, prefer the local repository path instead of HTTPS. This avoids
Git-over-HTTPS TLS failures while downloading large pack files.
```sh
sudo mkdir -p /mnt/usbssds/apps/iwanttoheal
cd /mnt/usbssds/apps/iwanttoheal
sudo git clone https://git.whoagland.com/phenom/i-want-to-heal.git app
sudo git config --global --add safe.directory /mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/i-want-to-heal.git
sudo git clone /mnt/.ix-apps/app_mounts/gitea/data/git/repositories/phenom/i-want-to-heal.git app
```
Because the clone was run with `sudo`, give the normal TrueNAS user ownership:
@@ -101,6 +110,25 @@ cd /mnt/usbssds/apps/iwanttoheal/app
git pull
```
If setting up a different app hosted by the same Gitea instance, first find its
bare repository path:
```sh
sudo find /mnt -type d -name "REPO-NAME.git" -prune -print 2>/dev/null
```
Then substitute that path in the `safe.directory` and `git clone` commands:
```sh
sudo git config --global --add safe.directory /mnt/.ix-apps/app_mounts/gitea/data/git/repositories/OWNER/REPO-NAME.git
sudo git clone /mnt/.ix-apps/app_mounts/gitea/data/git/repositories/OWNER/REPO-NAME.git app
sudo chown -R truenas_admin:truenas_admin app
```
`safe.directory` is a Git trust exception for the Gitea-owned bare repository.
It is needed because Gitea's repository files are owned by the app/container
user, not by `truenas_admin`.
If Git fails with `chmod ... Operation not permitted`, do not use a media or SMB
dataset for the repo. Git needs normal file locking and chmod behavior. Create or
use a dedicated apps dataset and clone under `/mnt/usbssds/apps/...`.
Binary file not shown.
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "com.warren.iwanttoheal"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 112
versionName "1.1.31"
versionCode 113
versionName "1.1.32"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+4
View File
@@ -104,6 +104,10 @@ npm run release:cli -- --release-only 1.1.26
## Step 5: Update TrueNAS
The TrueNAS app checkout should be cloned from the local Gitea bare repository
path when Gitea runs on the same TrueNAS host. After that setup, normal updates
still use `git pull`; the remote is local filesystem storage instead of HTTPS.
```sh
cd /mnt/usbssds/apps/iwanttoheal/app
git pull
+1 -1
View File
@@ -748,7 +748,7 @@ export function InputProvider({ children }: { children: ReactNode }) {
'navigateLeft',
'navigateRight',
] satisfies InputAction[]
const dedicatedNavigationActive = hasDedicatedGameNavigation()
const dedicatedNavigationActive = hasDedicatedGameNavigation() && (!combatActive || uiOverlay)
const action = dedicatedNavigationActive
? uiPriority.find((candidate) => bindingsRef.current.controller[candidate] === token)
?? (DPAD_NAV_ACTIONS[token] && isDedicatedNavigationAction(DPAD_NAV_ACTIONS[token])
+18 -6
View File
@@ -47,9 +47,11 @@ import {
type Iwt2RoguelikeVariant,
} from './content/roguelike'
import {
createIwt2PveRoguelikeBossPair,
createIwt2WeightedRoguelikeBossPair,
createUniformIwt2RoguelikeBossPair,
IWT2_PVE_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED,
IWT2_PVP_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED,
IWT2_PVP_STADIUM_WEIGHTED_BOSS_PROGRESSION_ENABLED,
} from './content/roguelikeBossProgression'
type Iwt2Screen =
@@ -527,7 +529,7 @@ function createRoguelikeRun(
contentType: Iwt2RoguelikeContentType,
): Iwt2RoguelikeRunState {
return {
bossIds: createRoguelikeBossPair(variant, 1),
bossIds: createRoguelikeBossPair(variant, contentType, 1),
buffs: [],
contentType,
debuffs: [],
@@ -595,15 +597,25 @@ function applyRoguelikeChoice(
return {
...run,
...nextBase,
bossIds: createRoguelikeBossPair(run.variant, run.stage + 1),
bossIds: createRoguelikeBossPair(run.variant, run.contentType, run.stage + 1),
...buildRoguelikeChoices(save, run.variant),
stage: run.stage + 1,
}
}
function createRoguelikeBossPair(variant: Iwt2RoguelikeVariant, stage: number): Iwt2BossId[] {
if (variant === 'pve' && IWT2_PVE_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED) {
return createIwt2PveRoguelikeBossPair(stage)
function createRoguelikeBossPair(
variant: Iwt2RoguelikeVariant,
contentType: Iwt2RoguelikeContentType,
stage: number,
): Iwt2BossId[] {
const weightedProgressionEnabled = variant === 'pve'
? IWT2_PVE_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED
: contentType === 'stadium'
? IWT2_PVP_STADIUM_WEIGHTED_BOSS_PROGRESSION_ENABLED
: IWT2_PVP_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED
if (weightedProgressionEnabled) {
return createIwt2WeightedRoguelikeBossPair(stage)
}
return createUniformIwt2RoguelikeBossPair()
}
@@ -1,6 +1,8 @@
import { IWT2_BOSS_METADATA, type Iwt2BossId } from './bosses'
export const IWT2_PVE_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED = true
export const IWT2_PVP_ROGUELIKE_WEIGHTED_BOSS_PROGRESSION_ENABLED = true
export const IWT2_PVP_STADIUM_WEIGHTED_BOSS_PROGRESSION_ENABLED = true
type Iwt2RoguelikeBossTier = 'early' | 'mid' | 'late'
@@ -43,6 +45,13 @@ const IWT2_ROGUELIKE_BOSS_TIER_BY_ID: Record<Iwt2BossId, Iwt2RoguelikeBossTier>
export function createIwt2PveRoguelikeBossPair(
stage: number,
random: () => number = Math.random,
): Iwt2BossId[] {
return createIwt2WeightedRoguelikeBossPair(stage, random)
}
export function createIwt2WeightedRoguelikeBossPair(
stage: number,
random: () => number = Math.random,
): Iwt2BossId[] {
const choices: Iwt2BossId[] = []
const maxThreat = maxThreatForStage(stage)