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/ahbg-android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: ahbg-android

on:
pull_request:
paths:
- "ahbg/android/**"
- "ahbg/presentation/**"
- ".github/workflows/ahbg-android.yml"
push:
branches: [main]
paths:
- "ahbg/android/**"
- "ahbg/presentation/**"
- ".github/workflows/ahbg-android.yml"

permissions:
contents: read

jobs:
android-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: android-actions/setup-android@v3
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.10.2"
- name: Build debug APK
working-directory: ahbg/android
run: gradle assembleDebug --no-daemon
- name: Presentation asset drift check
working-directory: ahbg/android
run: |
./sync_presentation.sh
git diff --exit-code -- app/src/main/assets/board.html app/src/main/assets/board.js app/src/main/assets/board.css app/src/main/assets/sample_snapshot.json app/src/main/assets/PRESENTATION.sha256
44 changes: 44 additions & 0 deletions .github/workflows/ahbg-runtime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: ahbg-runtime

on:
pull_request:
paths:
- "ahbg/runtime/**"
- "ahbg/grok/**"
- "libs/ucns/**"
- ".github/workflows/ahbg-runtime.yml"
push:
branches: [main]
paths:
- "ahbg/runtime/**"
- "ahbg/grok/**"
- "libs/ucns/**"
- ".github/workflows/ahbg-runtime.yml"

permissions:
contents: read

jobs:
runtime:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Runtime harness contract tests
env:
PYTHONPATH: libs/ucns/src
run: python -m unittest discover -s ahbg/runtime/tests -q
- name: Runtime CLI smoke
env:
PYTHONPATH: ".:libs/ucns/src"
run: |
python -m ahbg.runtime play --agent a0 --turns 3 --out /tmp/ahbg-runtime-ci
python - <<'PY'
import json
result = json.load(open("/tmp/ahbg-runtime-ci/result.json"))
assert result["final_turn"] == 3, result["final_turn"]
assert len(result["final_snapshot"]["tiles"]) == 7
print("runtime CLI smoke ok")
PY
50 changes: 50 additions & 0 deletions ahbg/android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# AHBG Android surface

Thinnest Android-first application around the canonical runtime. The mobile
layer presents and controls AHBG; it is **not** a second game engine and not a
geometry authority.

## Architecture

```text
Android app (WebView + JS bridge + JSON transport)
│ observe/plan/act JSON over HTTP
ahbg.runtime.server (ahbg/runtime/server.py)
canonical runtime (ahbg/runtime + frozen Grok engine + UCNS geometry)
```

- `MainActivity` hosts the canonical presentation board (`ahbg/presentation`)
in a WebView and exposes `window.ahbg` to the board.
- `HarnessClient` transports JSON to the runtime bridge; it owns no game state.
- `Entitlements.kt` wraps one RevenueCat entitlement, `benchmark_lab`.
- Bundled board assets are a pinned copy of `ahbg/presentation` (see
`sync_presentation.sh` and `PRESENTATION.sha256`).

## Build

```bash
cd ahbg/android
gradle assembleDebug -PruntimeUrl=http://10.0.2.2:8765 -PrevenueCatApiKey=rc_public_key
```

- `runtimeUrl` defaults to `http://10.0.2.2:8765` (host machine from emulator).
- `revenueCatApiKey` is a RevenueCat **public** API key provisioned at build
time and never committed. Without a key the app builds and runs on the free
tier (`NoopPremiumStore`).

## Entitlement

One clean entitlement: `benchmark_lab` — advanced scenarios, saved/replayed
run comparison, and adversarial benchmark packs. Basic gameplay and external
harness connectivity remain free. The runtime side only checks claims; the
Android side verifies with the RevenueCat SDK.

## hmmm

- Store publication needs signing, versioning policy, and submission assets —
outside this pass.
- Local emulator uses cleartext HTTP; a release build must switch the runtime
URL to HTTPS and disable `usesCleartextTraffic`.
46 changes: 46 additions & 0 deletions ahbg/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}

android {
namespace = "org.interdependency.ahbg"
compileSdk = 35

defaultConfig {
applicationId = "org.interdependency.ahbg"
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "0.1.0"
// Runtime bridge URL. 10.0.2.2 reaches the host machine from the
// Android emulator. Override with -PruntimeUrl=https://... for a real
// deployment; the mobile layer never embeds the engine itself.
val runtimeUrl = (project.findProperty("runtimeUrl") as String?) ?: "http://10.0.2.2:8765"
buildConfigField("String", "RUNTIME_URL", "\"$runtimeUrl\"")
// RevenueCat public API key. Provisioned at build time; never committed.
val revenueCatKey = (project.findProperty("revenueCatApiKey") as String?) ?: "REVENUECAT_KEY_NOT_PROVISIONED"
buildConfigField("String", "REVENUECAT_API_KEY", "\"$revenueCatKey\"")
}

buildTypes {
release {
isMinifyEnabled = false
}
}
buildFeatures {
buildConfig = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}

dependencies {
implementation("androidx.webkit:webkit:1.12.1")
implementation("com.revenuecat.purchases:purchases:8.10.1")
}
21 changes: 21 additions & 0 deletions ahbg/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:label="@string/app_name"
android:usesCleartextTraffic="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar">
<activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
4 changes: 4 additions & 0 deletions ahbg/android/app/src/main/assets/PRESENTATION.sha256
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
e439734fba459722d9f7d60b6b316c21cbef00952169cc35d1bfd5a377f1e2ca board.html
62726b4b78bda13dfbc05e6f422065b8870d5e0c6b1be053b6d7db4d730464a4 board.js
40560742bb9a897415d68aa50b5e68622cc13deba195783276efa97145455893 board.css
62d91e8456dd3a2fb75d57818d1f7cd8cde5c8456596415bdd1b467e9aee4dd4 sample_snapshot.json
87 changes: 87 additions & 0 deletions ahbg/android/app/src/main/assets/board.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
:root {
--ink: #1b1a17;
--paper: #f4efe4;
--tile: #e6dcc8;
--tile-stroke: #6b5d44;
--selected: #c45c26;
--unit: #1f4b99;
}

html,
body {
margin: 0;
background: var(--paper);
color: var(--ink);
font: 16px/1.4 "Iowan Old Style", "Palatino Linotype", Palatino, serif;
}

main {
display: grid;
grid-template-columns: minmax(0, 1fr) 18rem;
min-height: 100vh;
}

.board-wrap { display: flex; flex-direction: column; padding: 1.5rem; }
h1, h2 { font-weight: 600; letter-spacing: 0.04em; margin: 0 0 0.75rem; }
.note { margin: 0 0 1rem; font-size: 0.95rem; }
svg { width: min(100%, 36rem); height: auto; align-self: center; }
.seed-circle { fill: none; stroke: var(--tile-stroke); stroke-width: 1.5; pointer-events: none; }
.tile-point { fill: var(--tile-stroke); stroke: var(--paper); stroke-width: 1.5; pointer-events: none; }
.tile-point.selected { fill: var(--selected); stroke: var(--selected); }
.motion-path { fill: none; stroke: var(--selected); stroke-width: 2.5; stroke-dasharray: 6 4; pointer-events: none; }
.tile-hit { fill: transparent; cursor: pointer; stroke: transparent; stroke-width: 3; }
.tile-hit:focus { outline: none; stroke: var(--selected); }
.tile-label { fill: var(--ink); font-size: 11px; pointer-events: none; text-anchor: middle; }
.unit { fill: var(--unit); stroke: var(--paper); stroke-width: 2; pointer-events: none; }
.unit-label { fill: var(--paper); font-size: 10px; pointer-events: none; text-anchor: middle; }
.selection-ring { fill: none; stroke: var(--selected); stroke-width: 3; pointer-events: none; }
.feed { background: #efe7d6; border-left: 1px solid var(--tile-stroke); padding: 1.5rem; }
.feed ol { margin: 0; padding-left: 1.2rem; }
.inspect { margin-top: 1rem; font-size: 0.95rem; }

@media (max-width: 720px) {
main { grid-template-columns: 1fr; }
.feed { border-left: 0; border-top: 1px solid var(--tile-stroke); }
}

.overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(10, 10, 20, 0.72);
z-index: 10;
}
.overlay[hidden] { display: none; }
.overlay-card {
max-width: 30rem;
background: #10121c;
color: #e8e6f0;
border: 1px solid #3c3f5c;
border-radius: 0.75rem;
padding: 1.25rem 1.5rem;
}
.overlay-card button,
.controls button {
background: #2f3154;
color: #f2f0ff;
border: 1px solid #4a4d78;
border-radius: 0.4rem;
padding: 0.45rem 0.8rem;
cursor: pointer;
}
.controls {
margin-top: 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
align-items: center;
}
.controls label { margin-right: 0.25rem; }
.premium {
margin-top: 1rem;
border-top: 1px dashed #4a4d78;
padding-top: 0.75rem;
}
.premium button:disabled { opacity: 0.75; cursor: wait; }
46 changes: 46 additions & 0 deletions ahbg/android/app/src/main/assets/board.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AHBG presentation board</title>
<link rel="stylesheet" href="board.css" />
</head>
<body>
<section id="onboarding" class="overlay" role="dialog" aria-label="Onboarding">
<div class="overlay-card">
<h2>Welcome to AHBG</h2>
<p>One plane, seven UCNS tiles, one move per turn. Connect an agent, play a turn, and every consequence is resolved simultaneously and persisted. Basic play and external harness connectivity are free.</p>
<button id="onboarding-dismiss">Start</button>
</div>
</section>
<main>
<section class="board-wrap">
<h1>AHBG plane</h1>
<p class="note">Presentation only. Not mechanics. Tile centers are supplied by the declared UCNS source; the browser only scales them for display. Dashed traces show already-resolved motion. Click a center, or focus it and press Enter/Space, to inspect.</p>
<svg id="board" role="group" aria-label="Interactive Seed of Life presentation with inspectable tile centers and resolved unit motion traces"></svg>
</section>
<aside class="feed">
<h2>Feed</h2>
<ol id="feed-list"></ol>
<p class="inspect" id="inspect" aria-live="polite"></p>
<section class="controls" aria-label="Submission path controls">
<label for="agent-select">Agent</label>
<select id="agent-select">
<option value="a0">A0 (reference)</option>
<option value="external">External harness</option>
</select>
<button id="start-plane">Start plane</button>
<button id="play-turn">Play turn</button>
<button id="persist-reload">Persist / reload</button>
</section>
<section id="premium-surface" class="premium" hidden>
<h3>Benchmark Lab</h3>
<p>Advanced scenarios, saved/replayed run comparison, and adversarial benchmark packs.</p>
<button id="premium-status" disabled>Benchmark Lab: checking…</button>
</section>
</aside>
</main>
<script src="board.js"></script>
</body>
</html>
Loading
Loading