Files
schwert_und_magie_on_pebble/CLAUDE.md
T

5.0 KiB
Raw Blame History

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, 19891992) 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

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:

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:

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) all ship inside the VICE 3.8 tarball at res/vice-3.8.tar.gz and are bundled in the APK under app/src/main/assets/. They are copied automatically to the external files dir on first launch — no user action needed.

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:

engine.loadDisk(getExternalFilesDir(null)!!.absolutePath + "/schwert_und_magie_1.d64")