Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: android-build

on:
pull_request:
paths:
- 'mobile/android/**'
- 'engine/**'
- 'render/**'
- '.github/workflows/android.yml'
workflow_dispatch:

permissions:
contents: read

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'

jobs:
assemble-debug:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- uses: android-actions/setup-android@v4
with:
packages: 'platform-tools platforms;android-36 build-tools;36.0.0'
- uses: gradle/actions/setup-gradle@v6
with:
gradle-version: '9.4.1'
- name: Assemble debug APK
working-directory: mobile/android
run: gradle --no-daemon :app:assembleDebug
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.12'
Expand Down
56 changes: 56 additions & 0 deletions mobile/android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# SCARED SACRED Android shell

This module wraps the existing tested Python game rather than rewriting it.
Chaquopy packages `engine/` and `render/` directly; `mobile_bridge.py` starts
the same table server on `127.0.0.1`, and the Android WebView is only a shell.

## Store boundary

RevenueCat exists here to satisfy a real product boundary without selling game
power, Witness Accounts, or access to the ruleset.

- entitlement: `sacred_table` by default
- product: configure one Google Play one-time product as **non-consumable**
- offering: place that product in the current RevenueCat offering
- effect: permanent cosmetic Sacred Table skin only
- restore: exposed in the native shell

Witness Accounts remain giver-owned, table-bound, and unsellable per base canon.
All playable mechanics remain available without purchase.

## Build

The build intentionally pins the newest combination currently claimed compatible
by both Android and Chaquopy rather than using incompatible latest versions:

- Android Gradle Plugin 9.2.1
- Gradle 9.4.1
- Chaquopy 17.0.0
- Python 3.12
- compile/target API 36
- RevenueCat Android SDK 10.15.1

From `mobile/android`:

```sh
gradle :app:assembleDebug
```

The public RevenueCat SDK key is injected at build time and is not committed:

```sh
gradle :app:assembleDebug \
-PREVENUECAT_API_KEY=goog_xxx \
-PREVENUECAT_ENTITLEMENT_ID=sacred_table
```

With no key, the game still builds and runs; the purchase controls remain
visibly unconfigured rather than pretending monetization works.

## Store release boundary

A Shipaton-eligible release still requires the external store configuration:
Google Play app, one-time product, RevenueCat project/offering/entitlement,
signed AAB, store listing, judge access, icon, screenshot, and demo video.

hmmm — commerce belongs around the table, not between a witness and the room.
61 changes: 61 additions & 0 deletions mobile/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
plugins {
id("com.android.application")
id("com.chaquo.python")
}

fun quoted(value: String): String = "\"" + value
.replace("\\", "\\\\")
.replace("\"", "\\\"") + "\""

val revenueCatApiKey = providers.gradleProperty("REVENUECAT_API_KEY")
.orElse(providers.environmentVariable("REVENUECAT_API_KEY"))
.getOrElse("")
val revenueCatEntitlement = providers.gradleProperty("REVENUECAT_ENTITLEMENT_ID")
.orElse(providers.environmentVariable("REVENUECAT_ENTITLEMENT_ID"))
.getOrElse("sacred_table")

android {
namespace = "org.theinterdependency.scaredsacred"
compileSdk = 36

defaultConfig {
applicationId = "org.theinterdependency.scaredsacred"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "0.1.0"

ndk {
abiFilters += listOf("arm64-v8a", "x86_64")
}

buildConfigField("String", "REVENUECAT_API_KEY", quoted(revenueCatApiKey))
buildConfigField("String", "REVENUECAT_ENTITLEMENT_ID", quoted(revenueCatEntitlement))
}

buildFeatures {
buildConfig = true
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}

chaquopy {
defaultConfig {
version = "3.12"
}
sourceSets {
getByName("main") {
srcDir("src/main/python")
srcDir("../../../engine")
srcDir("../../../render")
}
}
}

dependencies {
implementation("com.revenuecat.purchases:purchases:10.15.1")
}
22 changes: 22 additions & 0 deletions mobile/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name="com.chaquo.python.android.PyApplication"
android:allowBackup="true"
android:label="SCARED SACRED"
android:supportsRtl="true"
android:theme="@android:style/Theme.Material.NoActionBar"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Loading