feat(mobile): v1.18.0 "Dormant" - monetization scaffold#107
Conversation
…th shells New standalone rustysnes-monetization UniFFI crate (dormant entitlement/ ad-pacing policy scaffold, never a dependency of the deterministic core, placeholder-only figures). Wired into Android (real Gradle build, AVD install, logcat-verified) and iOS (staticlib/rlib cross-compiles for real here; the cdylib/xcframework pipeline needs the real macOS ios.yml CI runner, matching this platform's standing scaffolded-only disposition).
There was a problem hiding this comment.
Code Review
This pull request introduces the rustysnes-monetization crate (Mobile Phase 5), a standalone UniFFI library providing a dormant entitlement and ad-pacing policy scaffold. It is integrated as an inert dependency into both the Android and iOS mobile shells, where it is invoked once at startup for logging purposes only. Feedback on the changes identifies a potential runtime crash in the Swift implementation when casting a negative Double to UInt64 if the system clock is set before 1970, and suggests safeguarding the conversion using max(0.0, ...).
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new mobile-only UniFFI crate (rustysnes-monetization) that provides a dormant entitlement + ad-pacing policy API, and wires it into both Android and iOS shells as an inert startup-time log (no gating/UI/SDK integration). It keeps the determinism boundary intact by not depending on any rustysnes-* core chip/core crates and by keeping the monetization APIs pure and host-time-injected.
Changes:
- Add new standalone
crates/rustysnes-monetizationUniFFI crate with placeholder policy APIs (check_entitlement,default_ad_pacing_policy,should_show_ad) plus unit tests. - Integrate the new crate into Android (Gradle
cargo ndkbuild + separate UniFFI bindgen output) and iOS (xcframework packaging + Swift bindgen generation) as a one-time startup log. - Update iOS/Android app versions and document the Mobile Phase 5 verification status in
docs/mobile-readiness.mdandCHANGELOG.md.
Reviewed changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/build-ios-xcframework.sh | Builds/packaging updated to include the new monetization crate and generate Swift bindings + xcframework. |
| ios/RustySNES/Sources/RustySNESApp.swift | Adds an iOS startup hook to log the dormant monetization scaffold state. |
| ios/project.yml | Bumps iOS marketing version and adds the monetization xcframework dependency. |
| docs/mobile-readiness.md | Documents Mobile Phase 5 verification for the new monetization scaffold and inert shell wiring. |
| crates/rustysnes-monetization/src/lib.rs | New UniFFI-exported pure policy API + tests for entitlement and ad pacing. |
| crates/rustysnes-monetization/src/bin/uniffi-bindgen.rs | New bindgen helper binary (feature-gated) for UniFFI Swift/Kotlin generation. |
| crates/rustysnes-monetization/Cargo.toml | New crate manifest defining crate-types and UniFFI dependency/feature gating. |
| CHANGELOG.md | Adds Unreleased notes describing the new crate and inert Android/iOS wiring. |
| Cargo.toml | Adds rustysnes-monetization to the workspace members list. |
| Cargo.lock | Records the new rustysnes-monetization package entry. |
| android/app/src/main/kotlin/com/doublegate/rustysnes/MainActivity.kt | Logs dormant monetization scaffold once at Android startup via generated UniFFI bindings. |
| android/app/build.gradle.kts | Builds/copies the new .so and adds a dedicated UniFFI bindgen task + source dir. |
| .gitignore | Ignores generated Swift bindings for the monetization UniFFI surface. |
…ollision Real macOS CI failure: xcodebuild copies every "library"+-headers xcframework's headers into one directory shared across the whole target, so two separate per-crate xcframeworks each contributing a same-named module.modulemap produced "Multiple commands produce" build failure. Merge rustysnes-mobile and rustysnes-monetization into one combined RustysnesFFI.xcframework via libtool -static + a combined umbrella modulemap instead.
Gemini/Copilot review: UInt64(Date().timeIntervalSince1970) traps if the device clock is set before 1970 - clamp with max(0.0, ...). Copilot review: Entitlement's doc comment claimed an "and why" the struct doesn't have a field for - trimmed to match the actual API.
Summary
rustysnes-monetizationUniFFI crate (Mobile Phase 5): a dormantentitlement/ad-pacing policy scaffold (
check_entitlement,default_ad_pacing_policy,should_show_ad). Never a dependency of the deterministic core — norustysnes-core/-cpu/-ppu/-apu/-cartdependency in either direction. Every concrete pricing/pacingnumber is an explicit placeholder default, not a committed figure (unlike RustyNES's own
already-shipped module) — the real store-launch decision stays with
docs/mobile-readiness.md's standing "Mobile Phase 6" gate.logged only, no real store SDK calls, no paywall/UI shown.
build.gradle.ktsbuilds the new crate's native.soalongside the existing twoand generates its own separate UniFFI Kotlin bindings. Verified for real: rebuilt via a real
Gradle build, installed on the real AVD, launched, and confirmed via
logcat:monetization scaffold (dormant): unlocked=true minIntervalSecs=300 sessionsBeforeFirstAd=3,app stayed alive afterward.
scripts/build-ios-xcframework.shgained a third crate to build/package(
RustysnesMonetizationFFI.xcframework) andios/project.ymlgained it as a targetdependency. The Rust
staticlib/rliboutputs cross-compile for real in this dev environment;the
cdyliboutput the bindgen/xcframework step needs only links with a real Apple toolchain(confirmed pre-existing, not introduced by this PR —
rustysnes-mobile's owncdylibfailsidentically in isolation), so the full pipeline is compile-verified via
ios.yml's real macOSCI only, matching the platform's standing scaffolded-only disposition since
v1.16.0.Test plan
cargo test --workspace --exclude rustysnes-android— all greencargo clippy --workspace --exclude rustysnes-android --all-targets -- -D warnings— cleancargo fmt --check— cleanRUSTDOCFLAGS="-D warnings" cargo doc --workspace --exclude rustysnes-android --no-deps— cleancargo build -p rustysnes-core --target thumbv7em-none-eabihf --no-default-features— no_std gate greencargo deny check— advisories/bans/licenses/sources all okcargo audit— 0 vulnerabilitieslogcatverification of the dormant scaffold log line, app remained stable (no crash)aarch64-apple-iosstaticlib/rlib cross-compile forrustysnes-monetizationxcodebuildpipeline — pendingios.yml's macOS CI run on this PR🤖 Generated with Claude Code