Builder's Manual · Night Arcade Cabinet
Native Host Apps
The technical docs now sit inside the same arcade world as the game: a service panel for the speed curve, ReactLynx architecture, sprite engine, native hosts, and release pipeline.
Native Host Apps
Lynx bundles do not run standalone — they need a thin native shell that embeds the Lynx runtime and loads the bundle. This project includes a ready-to-build Android host app and iOS source files that must be added to a locally created Xcode project before building.
Android
The Android host app is a minimal Kotlin application in android/.
Key Files
| File | Purpose |
|---|---|
SpeedyBirdApplication.kt |
Initializes Lynx engine, Fresco (image loading), and registers services |
MainActivity.kt |
Creates a LynxView and loads main.lynx.bundle from assets |
AssetTemplateProvider.kt |
Implements AbsTemplateProvider to read bundles from APK assets |
AndroidManifest.xml |
Fullscreen, portrait-only, internet permission |
build.gradle.kts |
Lynx SDK 3.7.0 dependencies, signing config from env vars |
proguard-rules.pro |
Keep rules for Lynx SDK classes during R8 minification |
Dependencies
| Artifact | Purpose |
|---|---|
org.lynxsdk.lynx:lynx |
Core rendering engine |
org.lynxsdk.lynx:lynx-jssdk |
JavaScript bridge |
org.lynxsdk.lynx:primjs |
QuickJS JavaScript engine |
org.lynxsdk.lynx:lynx-trace |
Performance tracing |
org.lynxsdk.lynx:lynx-service-image |
Image loading (wraps Fresco) |
org.lynxsdk.lynx:lynx-service-log |
Logging |
org.lynxsdk.lynx:lynx-service-http |
Network requests |
org.lynxsdk.lynx:xelement |
Extended UI elements |
org.lynxsdk.lynx:xelement-input |
Extended input elements used by the Android Lynx runtime |
com.facebook.fresco:* |
Image loading and animated image support required by lynx-service-image |
com.squareup.okhttp3:okhttp |
HTTP client support for Lynx services |
com.google.code.gson:gson |
JSON (required by Lynx internals) |
How It Works
SpeedyBirdApplication.onCreate()initializes Fresco, registers Lynx services, and callsLynxEnv.inst().init()MainActivity.onCreate()builds aLynxViewviaLynxViewBuilder, attaches theAssetTemplateProvider, and callsrenderTemplateUrl("main.lynx.bundle", "")- The
AssetTemplateProviderreads the bundle bytes fromassets/main.lynx.bundleand passes them to the Lynx engine
Signing
The build.gradle.kts reads signing configuration from environment variables:
KEYSTORE_FILE— path to keystore fileKEYSTORE_PASSWORD— keystore passwordKEY_ALIAS— key aliasKEY_PASSWORD— key password
These are populated by CI from GitHub Secrets. For local release builds, export the same environment variables in your shell before running Gradle.
Android Artifacts
| Build path | Command/workflow | Output | Signing |
|---|---|---|---|
| Local debug | bun run build, copy dist/main.lynx.bundle into android/app/src/main/assets/, then cd android && ./gradlew assembleDebug |
android/app/build/outputs/apk/debug/app-debug.apk |
Debug-signed by Android tooling |
| Local release | Same bundle copy, then cd android && ./gradlew assembleRelease |
android/app/build/outputs/apk/release/ |
Signed only when KEYSTORE_FILE, KEYSTORE_PASSWORD, KEY_ALIAS, and KEY_PASSWORD are exported |
| CI release | build-android.yml on main, v* tags, or manual dispatch |
APK artifact and GitHub Release attachment | Signed only when KEYSTORE_BASE64, KEYSTORE_PASSWORD, KEY_ALIAS, and KEY_PASSWORD GitHub Secrets are configured |
Native Audio Status
The game audio abstraction in src/audio/audio.ts uses HTMLAudioElement on web. Native builds currently have stubs: if an Android or iOS AudioModule is not implemented and registered with the Lynx bridge, the game logs a warning and continues without sound. To enable native sound effects, implement the platform-specific module, expose the same play/preload contract used by the web implementation, and register it during app startup.
iOS
The iOS host app source files are in ios/. An Xcode project must be created manually before the app can be built or archived.
Included Files
| File | Purpose |
|---|---|
Podfile |
Lynx 3.7.0, PrimJS, SDWebImage, XElement |
AppDelegate.swift |
Initializes LynxEnv |
SceneDelegate.swift |
Creates window with ViewController |
ViewController.swift |
Fullscreen LynxView, portrait-only, hidden status bar |
BundleTemplateProvider.swift |
Loads main.lynx.bundle from the app bundle |
SpeedyBird-Bridging-Header.h |
Objective-C bridge for Lynx SDK headers |
Info.plist |
App metadata, scene configuration |
Setup Steps
- Open Xcode > File > New > Project > App
- Product Name:
SpeedyBird - Bundle Identifier:
com.jonathanperis.speedybird - Language: Swift
- Product Name:
- Delete the auto-generated Swift files
- Add all files from
ios/SpeedyBird/to the project - Build Settings > Swift Compiler > Objective-C Bridging Header > set to
SpeedyBird/SpeedyBird-Bridging-Header.h - Build Settings > User Script Sandboxing > set to NO
- Terminal:
cd ios && pod install - Open
SpeedyBird.xcworkspace(not.xcodeproj) - Build Phases > Copy Bundle Resources > add
main.lynx.bundle
Apple Developer Program
| Feature | Free | Paid ($99/year) |
|---|---|---|
| Simulator builds | Yes | Yes |
| Sideload to own device | 7-day expiry | Unlimited |
| TestFlight | No | Yes |
| App Store distribution | No | Yes |
| CI signing (certificates) | No | Yes |
Without an Apple Developer Program configuration, CI/local workflows can only produce unsigned simulator or archive outputs after the Xcode project exists. TestFlight/App Store distribution requires signing certificates, provisioning profiles, and workflow secrets.