From d648990a81a16a81848547af1afe6380cf5f2c8a Mon Sep 17 00:00:00 2001
From: Adrian Castro <22133246+castdrian@users.noreply.github.com>
Date: Thu, 13 Aug 2026 02:18:25 +0200
Subject: [PATCH] feat(ios): brand app and tune haptic feedback
---
.github/workflows/ci.yml | 4 +-
.github/workflows/ios-artifact-comment.yml | 4 +-
.github/workflows/release.yml | 23 ++++----
README.md | 2 +-
docs/ios-sideload.md | 4 +-
mobile/ios/README.md | 18 +++---
mobile/ios/app-repo.json | 8 +--
mobile/ios/overlays/love-ios.plist | 4 +-
mobile/ios/patch_love_src.py | 54 ++++++++++++++++-
scripts/build.sh | 2 +-
scripts/build_ios.sh | 68 ++++++++++++++--------
src/core/SaveData.lua | 10 ++--
src/core/TouchControls.lua | 14 +++--
src/ui/OptionsMenu.lua | 4 +-
14 files changed, 146 insertions(+), 73 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1d5d4456..0db3b785 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -94,8 +94,8 @@ jobs:
if: github.repository == 'bryanthaboi/gen1recomp'
uses: actions/upload-artifact@v7
with:
- name: gen1recomp-ios-ipa
- path: dist/ios/gen1recomp.ipa
+ name: gen1recomp++-ios-ipa
+ path: dist/ios/gen1recomp++.ipa
if-no-files-found: error
retention-days: 7
- name: clean up signing keychain
diff --git a/.github/workflows/ios-artifact-comment.yml b/.github/workflows/ios-artifact-comment.yml
index b0bd58ce..3ba5386f 100644
--- a/.github/workflows/ios-artifact-comment.yml
+++ b/.github/workflows/ios-artifact-comment.yml
@@ -22,7 +22,7 @@ jobs:
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
HEAD_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }}
run: |
- artifact_id="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[] | select(.name == "gen1recomp-ios-ipa") | .id')"
+ artifact_id="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[] | select(.name == "gen1recomp++-ios-ipa") | .id')"
[ -n "$artifact_id" ] || exit 0
head_owner="${HEAD_REPOSITORY%%/*}"
pr_number="$(gh api "repos/$GITHUB_REPOSITORY/pulls?state=open&head=$head_owner:$HEAD_BRANCH" --jq '.[0].number // empty')"
@@ -44,7 +44,7 @@ jobs:
uses: thollander/actions-comment-pull-request@v3
with:
message: |
- [gen1recomp.ipa](${{ steps.artifact.outputs.artifact_url }})
+ [gen1recomp++.ipa](${{ steps.artifact.outputs.artifact_url }})
**Commit**: [#${{ steps.build-info.outputs.hash }}](https://github.com/${{ github.event.workflow_run.head_repository.full_name }}/commit/${{ github.event.workflow_run.head_sha }})
**Build Time**: `${{ steps.build-info.outputs.time }}`
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c111670d..58e35a5a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -429,9 +429,9 @@ jobs:
[ -n "$apk" ] || { echo "::error::no Android APK found under dist/android/debug"; exit 1; }
cp "$apk" "$outdir/gen1recomp-${v}-android.apk"
- ipa="dist/ios/gen1recomp.ipa"
+ ipa="dist/ios/gen1recomp++.ipa"
[ -f "$ipa" ] || { echo "::error::$ipa not found (expected from scripts/build_ios.sh --device)"; exit 1; }
- cp "$ipa" "$outdir/gen1recomp-${v}-ios.ipa"
+ cp "$ipa" "$outdir/gen1recomp++-${v}-ios.ipa"
swzip="dist/switch/gen1recomp-${v}-switch.zip"
[ -f "$swzip" ] || { echo "::error::$swzip not found (expected from scripts/build_switch.sh --fused → pack_sd_zip.sh)"; exit 1; }
@@ -560,7 +560,7 @@ jobs:
"dist/release/gen1recomp-${v}-linux.zip"
"dist/release/gen1recomp-${v}-linux-arm64.AppImage"
"dist/release/gen1recomp-${v}-android.apk"
- "dist/release/gen1recomp-${v}-ios.ipa"
+ "dist/release/gen1recomp++-${v}-ios.ipa"
"dist/release/gen1recomp-${v}-switch.zip"
"dist/release/gen1recomp-${v}-xbox-uwp.zip"
"dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip"
@@ -581,14 +581,15 @@ jobs:
run: |
set -euo pipefail
v="${{ needs.version.outputs.version }}"
- ipa="dist/release/gen1recomp-${v}-ios.ipa"
+ ipa="dist/release/gen1recomp++-${v}-ios.ipa"
app_repo="mobile/ios/app-repo.json"
[ -f "$ipa" ] || { echo "::error::$ipa not found"; exit 1; }
[ -f "$app_repo" ] || { echo "::error::$app_repo not found"; exit 1; }
date="$(date -u +"%Y-%m-%d")"
size="$(wc -c < "$ipa" | tr -d '[:space:]')"
- download_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/v${v}/gen1recomp-${v}-ios.ipa"
+ download_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/v${v}/gen1recomp++-${v}-ios.ipa"
+ bundle_id="com.theboisclub.gen1recompplusplus"
localized_description="Gen1Recomp - A native Lua / LÖVE2D recreation of Gen 1 Poke"
release_notes="$(GH_TOKEN="${{ github.token }}" gh release view "v${v}" --json body --jq '.body // ""' 2>/dev/null || true)"
if [ -n "$release_notes" ]; then
@@ -602,15 +603,15 @@ jobs:
--argjson size "$size" \
'{version: $version, date: $date, size: $size, downloadURL: $download_url, localizedDescription: $localized_description}')"
- if jq -e --arg version "$v" \
- 'any(.apps[] | select(.bundleIdentifier == "com.theboisclub.gen1recomp").versions[]?; .version == $version)' \
+ if jq -e --arg bundle_id "$bundle_id" --arg version "$v" \
+ 'any(.apps[] | select(.bundleIdentifier == $bundle_id).versions[]?; .version == $version)' \
"$app_repo" >/dev/null; then
- jq --arg version "$v" --argjson entry "$entry" \
- '(.apps[] | select(.bundleIdentifier == "com.theboisclub.gen1recomp").versions) |= map(if .version == $version then $entry else . end)' \
+ jq --arg bundle_id "$bundle_id" --arg version "$v" --argjson entry "$entry" \
+ '(.apps[] | select(.bundleIdentifier == $bundle_id).versions) |= map(if .version == $version then $entry else . end)' \
"$app_repo" > "$app_repo.tmp"
else
- jq --argjson entry "$entry" \
- '(.apps[] | select(.bundleIdentifier == "com.theboisclub.gen1recomp").versions) |= [$entry] + .' \
+ jq --arg bundle_id "$bundle_id" --argjson entry "$entry" \
+ '(.apps[] | select(.bundleIdentifier == $bundle_id).versions) |= [$entry] + .' \
"$app_repo" > "$app_repo.tmp"
fi
mv "$app_repo.tmp" "$app_repo"
diff --git a/README.md b/README.md
index c5bbecff..3710fd9e 100644
--- a/README.md
+++ b/README.md
@@ -242,7 +242,7 @@ in [docs/linux-arm64-build.md](docs/linux-arm64-build.md).
## iOS
-Every release ships `gen1recomp-*-ios.ipa`. Sideload it with AltStore
+Every release ships `gen1recomp++-*-ios.ipa`. Sideload it with AltStore
(Windows or Mac) — see [docs/ios-sideload.md](docs/ios-sideload.md). To
build and install from source on a Mac instead, see
[docs/ios-install.md](docs/ios-install.md).
diff --git a/docs/ios-sideload.md b/docs/ios-sideload.md
index ba4907da..8c19cf01 100644
--- a/docs/ios-sideload.md
+++ b/docs/ios-sideload.md
@@ -1,6 +1,6 @@
# Sideload the iOS build with AltStore
-Every GitHub Release ships an IPA (`gen1recomp-*-ios.ipa`). Install it on
+Every GitHub Release ships an IPA (`gen1recomp++-*-ios.ipa`). Install it on
your iPhone or iPad with [AltStore Classic](https://altstore.io/) — AltStore
re-signs the app with **your** free Apple ID so you do not need a Mac or
Xcode.
@@ -23,7 +23,7 @@ Stuck? Start here:
## 2. Install the game
-1. Download `gen1recomp-*-ios.ipa` from
+1. Download `gen1recomp++-*-ios.ipa` from
[Releases](https://github.com/bryanthaboi/gen1recomp/releases).
2. Open **AltStore** on the phone (AltServer must be running on the same
Wi‑Fi, or keep the phone plugged into the computer).
diff --git a/mobile/ios/README.md b/mobile/ios/README.md
index 5ae5439c..37f48b4e 100644
--- a/mobile/ios/README.md
+++ b/mobile/ios/README.md
@@ -7,7 +7,7 @@ an iOS app with LÖVE 12.0.
The app uses the public iOS Documents directory as its LÖVE save directory.
There is no `pokemon-love2d` subdirectory and the app does not create a
-README file there. When browsing `On My iPhone > gen1recomp` in Files, the
+README file there. When browsing `On My iPhone > gen1recomp++` in Files, the
directory contains the app's runtime data directly, including:
- installed mods and downloaded ROMs
@@ -46,7 +46,7 @@ scripts/build_ios.sh --device --release --install
Device builds require a paired, unlocked device and a valid Apple signing
identity. Set `DEVELOPMENT_TEAM` or `CODE_SIGN_IDENTITY` when automatic
signing cannot select the intended account. Add `--ipa` to create
-`dist/ios/gen1recomp.ipa`.
+`dist/ios/gen1recomp++.ipa`.
The script verifies the final app before packaging it:
@@ -76,10 +76,10 @@ option.
Simulator and device app bundles are copied to:
```text
-dist/ios/Debug-iphonesimulator/gen1recomp.app
-dist/ios/Release-iphonesimulator/gen1recomp.app
-dist/ios/Debug-iphoneos/gen1recomp.app
-dist/ios/Release-iphoneos/gen1recomp.app
+dist/ios/Debug-iphonesimulator/gen1recomp++.app
+dist/ios/Release-iphonesimulator/gen1recomp++.app
+dist/ios/Debug-iphoneos/gen1recomp++.app
+dist/ios/Release-iphoneos/gen1recomp++.app
```
The intermediate Xcode products are under `mobile/ios/build/`. Both locations
@@ -94,9 +94,9 @@ saves; those are created at runtime in Documents.
| Field | Default |
| --- | --- |
-| Display name | `gen1recomp` |
-| Product name | `gen1recomp` |
-| Bundle identifier | `com.theboisclub.gen1recomp` |
+| Display name | `gen1recomp++` |
+| Product name | `gen1recomp++` |
+| Bundle identifier | `com.theboisclub.gen1recompplusplus` |
| Save directory | Public `Documents` root |
| Orientation | Portrait |
diff --git a/mobile/ios/app-repo.json b/mobile/ios/app-repo.json
index d5b1db26..e6c0cc84 100644
--- a/mobile/ios/app-repo.json
+++ b/mobile/ios/app-repo.json
@@ -1,11 +1,11 @@
{
- "name": "gen1recomp App Repo",
- "identifier": "com.theboisclub.gen1recomp.repo",
+ "name": "gen1recomp++ App Repo",
+ "identifier": "com.theboisclub.gen1recompplusplus.repo",
"iconURL": "https://raw.githubusercontent.com/bryanthaboi/gen1recomp/main/assets/logo/gen1recomp_cover.png",
"apps": [
{
- "name": "gen1recomp",
- "bundleIdentifier": "com.theboisclub.gen1recomp",
+ "name": "gen1recomp++",
+ "bundleIdentifier": "com.theboisclub.gen1recompplusplus",
"developerName": "bryanthaboi",
"iconURL": "https://raw.githubusercontent.com/bryanthaboi/gen1recomp/main/assets/logo/gen1recomp_cover.png",
"localizedDescription": "Gen1Recomp - A native Lua / LÖVE2D recreation of Gen 1 Poke",
diff --git a/mobile/ios/overlays/love-ios.plist b/mobile/ios/overlays/love-ios.plist
index 6d7e2b30..d2f97a54 100644
--- a/mobile/ios/overlays/love-ios.plist
+++ b/mobile/ios/overlays/love-ios.plist
@@ -5,7 +5,7 @@
CFBundleDevelopmentRegion
en
CFBundleDisplayName
- gen1recomp
+ gen1recomp++
CFBundleDocumentTypes
@@ -32,7 +32,7 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- gen1recomp
+ gen1recomp++
CFBundlePackageType
APPL
CFBundleShortVersionString
diff --git a/mobile/ios/patch_love_src.py b/mobile/ios/patch_love_src.py
index b6d6eb40..e9a90a43 100644
--- a/mobile/ios/patch_love_src.py
+++ b/mobile/ios/patch_love_src.py
@@ -29,6 +29,9 @@ WRAP_SYSTEM = LOVE_SRC / "src" / "modules" / "system" / "wrap_System.cpp"
PBXPROJ = LOVE_SRC / "platform" / "xcode" / "love.xcodeproj" / "project.pbxproj"
APPLE_MM = LOVE_SRC / "src" / "common" / "apple.mm"
FILESYSTEM_CPP = LOVE_SRC / "src" / "modules" / "filesystem" / "physfs" / "Filesystem.cpp"
+IOS_MM = LOVE_SRC / "src" / "common" / "ios.mm"
+IOS_H = LOVE_SRC / "src" / "common" / "ios.h"
+SYSTEM_CPP = LOVE_SRC / "src" / "modules" / "system" / "System.cpp"
ENTITLEMENTS_SRC = IOS_DIR / "overlays" / "love-ios.entitlements"
NATIVE_FILES = ("GRPickerBridge.swift", "GRHealthBridge.swift", "GRBootstrap.m")
@@ -120,7 +123,7 @@ int w_pickFileKinds(lua_State *L)
typedef const char *(*GRUTF8)(id, SEL);
const char *bytes = ((GRUTF8)objc_msgSend)(kinds,
sel_registerName("UTF8String"));
- if (bytes == nullptr || bytes[0] == '\0')
+ if (bytes == nullptr || bytes[0] == '\\0')
{
lua_pushnil(L);
return 1;
@@ -358,6 +361,50 @@ def patch_public_documents():
print("patch_love_src: iOS save directory routed to Documents root")
+def patch_ios_haptics():
+ text = pristine(IOS_MM, ("UIImpactFeedbackGenerator",))
+ original = """void vibrate()
+{
+ @autoreleasepool
+ {
+ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+ }
+}
+"""
+ replacement = """void vibrate(double seconds)
+{
+ @autoreleasepool
+ {
+ UIImpactFeedbackStyle style = UIImpactFeedbackStyleLight;
+ if (seconds >= 0.035)
+ style = UIImpactFeedbackStyleHeavy;
+ else if (seconds >= 0.02)
+ style = UIImpactFeedbackStyleMedium;
+ UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc]
+ initWithStyle:style];
+ [generator prepare];
+ [generator impactOccurred];
+ }
+}
+"""
+ if original not in text:
+ fail(f"iOS haptic anchor not found in {IOS_MM}")
+ IOS_MM.write_text(text.replace(original, replacement, 1))
+
+ header = pristine(IOS_H, ("void vibrate(double seconds);",))
+ header_original = "void vibrate();"
+ if header_original not in header:
+ fail(f"iOS haptic declaration not found in {IOS_H}")
+ IOS_H.write_text(header.replace(header_original, "void vibrate(double seconds);", 1))
+
+ system = pristine(SYSTEM_CPP, ("love::ios::vibrate(seconds)",))
+ system_original = "love::ios::vibrate();"
+ if system_original not in system:
+ fail(f"iOS haptic call site not found in {SYSTEM_CPP}")
+ SYSTEM_CPP.write_text(system.replace(system_original, "love::ios::vibrate(seconds);", 1))
+ print("patch_love_src: iOS haptics use Taptic Engine impact presets")
+
+
def patch_pbxproj():
text = pristine(PBXPROJ)
@@ -408,7 +455,9 @@ def patch_pbxproj():
fail(f"build configuration {config_id} not found")
settings = (
"\t\t\t\tSWIFT_VERSION = 5.0;\n"
- "\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.0;\n"
+ "\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.0;\n"
+ "\t\t\t\tPRODUCT_NAME = \"gen1recomp++\";\n"
+ "\t\t\t\tEXECUTABLE_NAME = \"gen1recomp++\";\n"
'\t\t\t\tCODE_SIGN_ENTITLEMENTS = "ios/native/love-ios.entitlements";\n'
)
text = text[: m.end()] + settings + text[m.end():]
@@ -422,6 +471,7 @@ def main():
fail("love-src/ missing; run scripts/build_ios.sh --fetch first")
copy_native_files()
patch_public_documents()
+ patch_ios_haptics()
patch_wrap_system()
patch_pbxproj()
diff --git a/scripts/build.sh b/scripts/build.sh
index 2828ac83..5c6f8bc5 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -13,7 +13,7 @@
# dist/linux/gen1recomp-linux.zip (fused x86_64 AppImage)
# dist/android/debug/*.apk (full gradle output stays under
# mobile/android/app/build/outputs/apk/embedNoRecord/)
-# dist/ios/-/gen1recomp.app (full xcodebuild output stays
+# dist/ios/-/gen1recomp++.app (full xcodebuild output stays
# under mobile/ios/build/Build/Products/)
set -euo pipefail
diff --git a/scripts/build_ios.sh b/scripts/build_ios.sh
index 3a87dce9..9c7fac93 100755
--- a/scripts/build_ios.sh
+++ b/scripts/build_ios.sh
@@ -20,9 +20,9 @@
# - macOS + Xcode (xcodebuild)
# - mobile/ios/love-src/ (see --fetch / mobile/ios/README.md)
#
-# Output: dist/ios/-/gen1recomp.app (convenience copy)
-# dist/ios/gen1recomp.ipa (device builds only)
-# mobile/ios/build/Build/Products/-/gen1recomp.app
+# Output: dist/ios/-/gen1recomp++.app (convenience copy)
+# dist/ios/gen1recomp++.ipa (device builds only)
+# mobile/ios/build/Build/Products/-/gen1recomp++.app
set -euo pipefail
@@ -39,8 +39,9 @@ RESOURCES_DIR="$XCODE_DIR/ios/resources"
LOVE_FILE="$RESOURCES_DIR/game.love"
LIBS_DIR="$XCODE_DIR/ios/libraries"
-APP_NAME="gen1recomp"
-DISPLAY_NAME="gen1recomp"
+APP_NAME="gen1recomp++"
+DISPLAY_NAME="gen1recomp++"
+PRODUCT_NAME="gen1recomp++"
# Bundle ID resolution, most specific wins:
# 1. GEN1_BUNDLE_ID env var
# 2. mobile/ios/bundle_id.local (one line, gitignored — pins YOUR install
@@ -50,7 +51,7 @@ DISPLAY_NAME="gen1recomp"
# capabilities like HealthKit are involved), so a per-team default
# lets anyone build without colliding with someone else's app
# 4. simulator: the project default (no App ID registration involved)
-BUNDLE_ID="${GEN1_BUNDLE_ID:-com.theboisclub.gen1recomp}"
+BUNDLE_ID="${GEN1_BUNDLE_ID:-}"
if [ -z "$BUNDLE_ID" ] && [ -f "$IOS_DIR/bundle_id.local" ]; then
BUNDLE_ID="$(tr -d '[:space:]' < "$IOS_DIR/bundle_id.local")"
fi
@@ -138,11 +139,7 @@ if $DEVICE && [ -z "${DEVELOPMENT_TEAM:-}" ]; then
fi
fi
if [ -z "$BUNDLE_ID" ]; then
- if $DEVICE; then
- BUNDLE_ID="com.gen1recomp.t$(printf '%s' "$DEVELOPMENT_TEAM" | tr '[:upper:]' '[:lower:]')"
- else
- BUNDLE_ID="com.theboisclub.pokemonred"
- fi
+ BUNDLE_ID="com.theboisclub.gen1recompplusplus"
fi
# --------------------------------------------------------------- host checks
@@ -226,13 +223,26 @@ verify_documents_overlay() {
}
apply_ios_icon() {
- local source="$ROOT/assets/logo/gen1recomp_cover.png"
+ local source="$ROOT/assets/logo/logo.png"
local target="$XCODE_DIR/Images.xcassets/iOS AppIcon.appiconset"
[ -f "$source" ] || fail "missing iOS icon source: $source"
[ -d "$target" ] || fail "missing iOS app icon set: $target"
+ local icon="$BUILD_DIR/gen1recomp-ios-icon.png"
+ mkdir -p "$BUILD_DIR"
+ if command -v magick >/dev/null 2>&1; then
+ magick -size 1024x1024 xc:black \
+ \( "$source" -resize 900x900 \) -gravity center -composite \
+ -alpha off "$icon" || fail "could not create iOS app icon: $source"
+ else
+ local scaled="$BUILD_DIR/gen1recomp-logo.png"
+ sips -Z 900 "$source" --out "$scaled" >/dev/null \
+ || fail "could not resize iOS app icon source: $source"
+ sips -p 1024 1024 --padColor 000000 "$scaled" --out "$icon" >/dev/null \
+ || fail "could not center iOS app icon source: $source"
+ fi
local entry name size
while IFS=: read -r name size; do
- sips -z "$size" "$size" "$source" --out "$target/$name" >/dev/null
+ sips -z "$size" "$size" "$icon" --out "$target/$name" >/dev/null
done <<'EOF'
icon-1024pt@1x.png:1024
icon-29pt@1x.png:29
@@ -647,6 +657,7 @@ run_xcodebuild() {
CURRENT_PROJECT_VERSION="$project_version"
INFOPLIST_KEY_UIFileSharingEnabled=YES
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace=YES
+ IPHONEOS_DEPLOYMENT_TARGET=15.0
ONLY_ACTIVE_ARCH=NO
DISABLE_MANUAL_TARGET_ORDER_BUILD_WARNING=YES
)
@@ -666,6 +677,9 @@ run_xcodebuild() {
if [ -n "${CODE_SIGN_IDENTITY:-}" ]; then
args+=(CODE_SIGN_IDENTITY="$CODE_SIGN_IDENTITY")
fi
+ if [ "${GEN1_DISABLE_HEALTHKIT:-0}" = "1" ]; then
+ args+=(CODE_SIGN_ENTITLEMENTS=)
+ fi
fi
if ! xcodebuild -showsdks 2>/dev/null | grep -q "$sdk"; then
@@ -708,18 +722,22 @@ run_xcodebuild() {
fi
local products="$BUILD_DIR/Build/Products/${config}-${sdk}"
- local app="$products/$APP_NAME.app"
- if [ ! -d "$app" ]; then
- # PRODUCT_NAME override can still leave love.app on older projects
- if [ -d "$products/love.app" ]; then
- app="$products/$APP_NAME.app"
- mv "$products/love.app" "$app"
- warn "renamed love.app to $APP_NAME.app"
- else
- warn "xcodebuild finished but no .app under $products"
- find "$BUILD_DIR/Build/Products" -name '*.app' 2>/dev/null | head -20 || true
- return 0
+ local app=""
+ local candidate
+ for candidate in "$products/$PRODUCT_NAME.app" "$products/$APP_NAME.app" "$products/love.app"; do
+ if [ -d "$candidate" ]; then
+ app="$candidate"
+ break
fi
+ done
+ if [ -z "$app" ]; then
+ warn "xcodebuild finished but no .app under $products"
+ find "$BUILD_DIR/Build/Products" -name '*.app' 2>/dev/null | head -20 || true
+ return 0
+ fi
+ if [ "$app" != "$products/$APP_NAME.app" ]; then
+ mv "$app" "$products/$APP_NAME.app"
+ app="$products/$APP_NAME.app"
fi
verify_documents_configuration "$app"
@@ -760,7 +778,7 @@ run_xcodebuild() {
fi
}
-# Pack Payload/.app into dist/ios/gen1recomp.ipa for release / sideload tools.
+# Pack Payload/.app into dist/ios/gen1recomp++.ipa for release / sideload tools.
package_ipa() {
local app="$1"
local ipa="$DIST/$APP_NAME.ipa"
diff --git a/src/core/SaveData.lua b/src/core/SaveData.lua
index 70dd44c7..9f1e58f2 100644
--- a/src/core/SaveData.lua
+++ b/src/core/SaveData.lua
@@ -335,11 +335,11 @@ function SaveData.defaultOptions()
-- TouchControls.normalizeConfig folds it into both orientations on load.
touchControls = { enabled = true },
-- Haptic feedback level for on-screen pad presses (#806):
- -- off | light | medium | heavy, mapped to a love.system.vibrate
- -- duration in src/core/TouchControls.lua. LIGHT by default, like the
- -- overlay itself defaulting on, so an options.lua predating this key
- -- gets the tick without going looking for the row. Inert wherever the
- -- overlay never appears (desktop) or LOVE has no vibrator.
+ -- off | light | normal | strong, mapped to a love.system.vibrate duration
+ -- in src/core/TouchControls.lua. LIGHT by default, so an options.lua
+ -- predating this key gets the tick without going looking for the row.
+ -- Inert wherever the overlay never appears (desktop) or LOVE has no
+ -- vibrator.
haptics = "light",
-- Shared UI/mod timestamp presentation. DEVICE follows the process time
-- locale where the platform exposes one; otherwise DateTime falls back to
diff --git a/src/core/TouchControls.lua b/src/core/TouchControls.lua
index d4d645bf..b3422edb 100644
--- a/src/core/TouchControls.lua
+++ b/src/core/TouchControls.lua
@@ -105,17 +105,21 @@ end
-- would be dropped on every editor save.
-- love.system.vibrate takes a duration and nothing else, so "intensity" is a
-- duration preset: Android runs the platform vibrator for exactly that long,
--- while iOS ignores the duration and fires the fixed system vibration, so
--- there the three levels all read as simply on.
-TouchControls.HAPTICS = { "off", "light", "medium", "heavy" }
+-- while iOS maps each duration to a matching Taptic Engine impact.
+TouchControls.HAPTICS = { "off", "light", "normal", "strong" }
TouchControls.HAPTIC_DEFAULT = "light"
-local HAPTIC_SECONDS = { off = 0, light = 0.012, medium = 0.025, heavy = 0.045 }
+local HAPTIC_SECONDS = {
+ off = 0, light = 0.012, normal = 0.025, strong = 0.045,
+}
local HAPTIC_LABELS = {
- off = "OFF", light = "LIGHT", medium = "MEDIUM", heavy = "HEAVY",
+ off = "OFF", light = "LIGHT", normal = "NORMAL", strong = "STRONG",
}
function TouchControls.normalizeHaptics(level)
+ if level == "physical" then return "light" end
+ if level == "medium" then return "normal" end
+ if level == "heavy" then return "strong" end
if HAPTIC_SECONDS[level] then return level end
return TouchControls.HAPTIC_DEFAULT
end
diff --git a/src/ui/OptionsMenu.lua b/src/ui/OptionsMenu.lua
index ef21f209..c83c5450 100644
--- a/src/ui/OptionsMenu.lua
+++ b/src/ui/OptionsMenu.lua
@@ -501,7 +501,7 @@ local function buildRows(game)
return true
end },
-- Haptic feedback for on-screen pad presses (#806): OFF / LIGHT /
- -- MEDIUM / HEAVY, where the intensity is a vibration duration --
+ -- NORMAL / STRONG, where the intensity is a vibration duration --
-- love.system.vibrate takes nothing else. Hidden with TOUCH PAD below,
-- since the only thing that buzzes is a virtual button press.
{ id = "haptics", label = Strings("VIBRATION"),
@@ -515,7 +515,7 @@ local function buildRows(game)
o.haptics = TC.cycleHaptics(o.haptics, dir)
TC:applyOptions(o)
-- sample the level being selected: stepping the row is the only way
- -- to compare LIGHT against HEAVY without leaving the menu
+ -- to compare LIGHT against STRONG without leaving the menu
TC.buzz(o.haptics)
return true
end },