aaa6e772a7
Commodore ROMs are copyrighted and can no longer ship inside the APK. MainActivity now detects missing ROMs on first launch and blocks startup with a dialog offering two paths: pick files via the system file picker, or download the official VICE 3.8 tarball and extract the four ROMs from it client-side (minimal USTAR reader, no extra deps). - Drop the extractViceRoms Gradle task and asset bundling; add res/extract_roms.sh for local sideloading during development instead - gitignore keystores/keystore.properties ahead of a signed release - Move architecture.md into docs/, refresh it for the screen-mirroring and watch-input additions, and add accompanying Mermaid diagrams - Add docs/debugging.md and docs/publish.md (Play Store release notes, ROM-import compliance rationale)
130 lines
5.3 KiB
Markdown
130 lines
5.3 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## Project goal
|
||
|
||
Port the classic German C64 RPG series **Schwert und Magie** (8 episodes, German Design Group, 1989–1992) to a Pebble Time 2 smartwatch. The phone runs a VICE C64 emulator as a companion app; the watch displays the game and sends button presses back.
|
||
|
||
## Repository layout
|
||
|
||
```
|
||
SchwertUndMagieOnPebbleWatchApp/ Pebble watchapp (C + PebbleKit JS)
|
||
SchwertUndMagieOnPebbleCompanionApp/ Android companion app (Kotlin + NDK)
|
||
versions/ Original .d64 disk images (4 disks, 8 episodes)
|
||
docs/ Background notes
|
||
```
|
||
|
||
## Watch app
|
||
|
||
### Build & install
|
||
|
||
```bash
|
||
cd SchwertUndMagieOnPebbleWatchApp
|
||
pebble build # all platforms
|
||
pebble install --phone <phone-ip> # sideload via Pebble/Core app on the phone
|
||
pebble logs --phone <phone-ip> # stream JS + C app logs
|
||
```
|
||
|
||
In headless / CI environments, add `--vnc` to every emulator command:
|
||
```bash
|
||
pebble install --emulator emery --vnc
|
||
pebble screenshot --vnc --scale 6 --no-open screenshot.png
|
||
pebble emu-button --emulator emery --vnc click select
|
||
```
|
||
|
||
### Key files
|
||
|
||
| File | Purpose |
|
||
|---|---|
|
||
| `src/c/SchwertUndMagieOnPebbleFrontend.c` | Single-file C watchapp |
|
||
| `src/pkjs/index.js` | PebbleKit JS — runs inside Core for Pebble on the phone |
|
||
| `package.json` | UUID, target platforms, `messageKeys` |
|
||
|
||
### Communication protocol
|
||
|
||
AppMessage keys (defined in both C and JS):
|
||
|
||
| Key | Direction | Value |
|
||
|---|---|---|
|
||
| `0` (TIME) | phone → watch | `"HH:mm:ss"` string (later: C64 framebuffer) |
|
||
| `1` (COMMAND) | watch → phone | button name string (`"UP"`, `"DOWN"`, `"SELECT"`) |
|
||
|
||
The watch UUID is `8039ba8c-f1e8-4620-838d-650fa35335b7`.
|
||
|
||
### PebbleKit JS notes (Core for Pebble quirks)
|
||
|
||
- **Use `127.0.0.1` not `localhost`** — Core for Pebble's JS runtime does not resolve `localhost`.
|
||
- **Use numeric keys in `sendAppMessage`** (`{ 0: value }` not `{ 'TIME': value }`) — symbolic key resolution from `package.json` messageKeys is unreliable with Core for Pebble.
|
||
- PebbleKit Android (`com.getpebble.android.*`) does **not** work with Core for Pebble; all phone↔watch communication goes through PebbleKit JS + the HTTP server.
|
||
|
||
## Android companion app
|
||
|
||
### Build
|
||
|
||
Open `SchwertUndMagieOnPebbleCompanionApp/` in Android Studio, or:
|
||
|
||
```bash
|
||
cd SchwertUndMagieOnPebbleCompanionApp
|
||
./gradlew assembleDebug
|
||
./gradlew installDebug
|
||
```
|
||
|
||
Android SDK is at `/opt/android-sdk`.
|
||
|
||
### Communication architecture
|
||
|
||
```
|
||
Watch (AppMessage BT)
|
||
↕
|
||
PebbleKit JS — index.js runs inside Core for Pebble
|
||
↕ HTTP on 127.0.0.1:8888
|
||
Android companion app — NanoHTTPD server
|
||
GET /time → {"time":"HH:mm:ss"}
|
||
GET /key?cmd=UP → logs keystroke
|
||
```
|
||
|
||
`MainActivity` starts both the NanoHTTPD server and the VICE emulator loop thread. All HTTP callbacks marshal to the main thread via `mainHandler`.
|
||
|
||
### VICE integration (NDK)
|
||
|
||
VICE 3.8 tarball is at `SchwertUndMagieOnPebbleCompanionApp/res/vice-3.8.tar.gz`.
|
||
|
||
**Without VICE built** (first state): `vice_jni.c` fills the framebuffer with a placeholder blue screen; the app builds and runs normally.
|
||
|
||
**VICE compilation is automatic.** The Gradle `buildVice` task runs before every native CMake build. On the first build it:
|
||
1. Calls `app/src/main/jni/build_vice.sh` with `$NDK` set from the Android Studio SDK config
|
||
2. The script unpacks the tarball, cross-compiles VICE headless for ARM64 and x86_64, and produces `vice-libs/<abi>/libvice.a`
|
||
3. `CMakeLists.txt` auto-detects the library via `EXISTS` and links it in — no manual flags needed
|
||
|
||
The NDK must be installed: **Android Studio → SDK Manager → SDK Tools → NDK (Side by side)**.
|
||
|
||
After the first successful build, subsequent builds skip the VICE compilation step entirely (outputs are up-to-date).
|
||
|
||
**C64 ROM files** (`kernal`, `basic`, `chargen`, `1541`) are copyrighted and are
|
||
**not** bundled in the APK. On first launch, `MainActivity` checks the app's
|
||
external files dir for these four files; if any are missing, it shows a blocking
|
||
dialog that lets the user import their own legally-obtained ROM dump via the
|
||
system file picker (see `docs/publish.md` §0). `res/extract_roms.sh` can pull
|
||
the ROMs out of `res/vice-3.8.tar.gz` into `res/roms/` for local sideloading
|
||
during development, but nothing in the Gradle build copies them into the APK.
|
||
|
||
### Key Kotlin/C files
|
||
|
||
| File | Purpose |
|
||
|---|---|
|
||
| `MainActivity.kt` | Emulator loop thread (50 fps), NanoHTTPD server, keyboard event logging |
|
||
| `C64Engine.kt` | JNI interface to VICE — init, runFrame, getVideoBuffer, injectKey, loadDisk |
|
||
| `C64DisplayView.kt` | SurfaceView — blits 320×200 ARGB framebuffer, scaled with correct aspect ratio |
|
||
| `C64KeyboardView.kt` | Multi-touch virtual C64 keyboard; fires `KeyEventListener` on press/release |
|
||
| `jni/vice_jni.c` | JNI wrapper — custom VICE video canvas writes frames to `g_framebuf[320×200]` |
|
||
| `jni/CMakeLists.txt` | NDK build; conditionally links `libvice.a` when `HAVE_VICE_SRC=1` |
|
||
|
||
### Disk images
|
||
|
||
The four original `.d64` disk images are in `versions/`. Copy the relevant image to the
|
||
same external files directory as the ROMs (via USB or adb), then load via:
|
||
```kotlin
|
||
engine.loadDisk(getExternalFilesDir(null)!!.absolutePath + "/schwert_und_magie_1.d64")
|
||
```
|