Skip to content

feat(monetization): build-out — core expansion, rewarded gates, foss/play split (ADR 0025) - #183

Merged
doublegate merged 6 commits into
mainfrom
feat/v1.8.9-monetization-buildout
Jun 23, 2026
Merged

doublegate merged 6 commits into
mainfrom
feat/v1.8.9-monetization-buildout

Conversation

@doublegate

Copy link
Copy Markdown
Owner

Monetization build-out (v1.8.9)

Builds the harvested rustynes-monetization scaffold out toward final, per the
2026-06-23 maintainer decisions. Dormant until the v2.1.0 joint store launch.

Core (crates/rustynes-monetization) — 17 tests

  • PremiumFeature → six variants — added FastForward / Shaders / Cheats to the
    three persistence locks ("expand the premium set"; overrides the old "fast-forward free"
    doc stance).
  • Session-awareness — begin_session, 30-min generous first session
    (first_session_play_ms), interstitials suppressed in session chore(ci): Bump actions/cache from 4 to 5 #1.
  • Offline grace — one-time +2 min when offline at run-out so it doesn't dead-end.
  • In-progress persistence — export/restore_progress (survives kill-relaunch).
  • Remote-config clamp — clamp_ad_config (a bad push can't brick the gate).
  • Rewarded model = +11 min × 2 ads (was +2 × 11) — same +22-min ceiling → 30-min total,
    far less burdensome (two ad watches, not eleven).

Shells

Android — foss/play flavor split locked + crate wired (ADR 0025)

  • ADR 0025 + plan/DEFERRED/docs/android.md: the v2.1.0 build splits into a foss
    flavor (no Google SDKs, no ads, no tracking — the F-Droid + sideload artifact) and a
    play flavor (all proprietary SDKs + AppLovin + RevenueCat — Google Play). F-Droid
    requires a Google-/ad-free build, so the flavor is the only clean-channel path.
  • Wired now (dormant): build.gradle.kts builds librustynes_monetization.so via
    cargo-ndk and generates its UniFFI Kotlin bindings — our own clean AdPolicy core, no
    Google/ads, so flavor-agnostic. The full split (5 existing SDKs behind src/play façades +
    the installDebug alias + both-flavor on-device verify) is a v2.1.0 deliverable.

Docs

All docs/monetization/ reconciled (FFI table, the expanded premium set, the +11×2 reward
model, the in-repo locations).

Core stays host-side only — never touches rustynes-core; AccuracyCoin/determinism untouched.

🤖 Generated with Claude Code

doublegate and others added 5 commits June 22, 2026 21:47
…ersist/clamp

Build-out of the AdPolicy core per the 2026-06-23 maintainer decisions (all pure
logic, host-injected time, determinism boundary intact; 13 -> 17 tests):

- PremiumFeature expanded to six: + FastForward, Shaders, Cheats on top of the three
  persistence locks. This OVERRIDES the earlier "fast-forward stays free" doc stance
  (maintainer "expand the premium set"); the free tier keeps accuracy/video/audio/
  input/pause/rewind but these power tools now need the unlock.
- Session-awareness + first-impression protection: begin_session(index, now) +
  AdConfig.suppress_first_session (no interstitials in session #1) +
  AdConfig.first_session_play_ms (a generous/ungated first game; default 30 min).
- Offline grace: AdConfig.offline_grace_ms + can_grant_offline_grace() /
  grant_offline_grace() — a one-time per-session continuation so an offline free user
  at run-out (no rewarded fill) degrades gracefully instead of dead-ending.
- In-progress persistence: PlayProgress record + export_progress() / restore_progress()
  so the timer/cap survive a kill-relaunch, closing the restart-to-reset hole.
- Remote-config safety: clamp_ad_config() clamps every field to a sane range so a bad
  remote push can't brick the gate (host fetches -> overlay on defaults -> clamp -> build).

Core stays mobile-only + no_std-irrelevant (host concern); never touches rustynes-core.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…he shells

- RewardedGate.kt / RewardedGate.swift: the MaxRewardedAd / MARewardedAd gate
  mirroring AdGate, the sibling that extends the free-tier budget. Grants time ONLY
  on the reward callback (onUserRewarded / didRewardUser) via grantRewardedTime();
  the offer is gated on can_offer_rewarded() and falls back to the Full Version
  prompt / offline-grace path when no ad is ready.
- Clarify the AdConfig::default doc: 8-min regular session, 30-min generous first
  session (the 8_000/20_000 in the test helpers are seconds, for fast unit tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… set

Update the FFI table for the core expansion: begin_session, can/grant_offline_grace,
export/restore_progress, clamp_ad_config, the three new AdConfig fields, PlayProgress,
and the six-variant PremiumFeature. Flag that FastForward/Shaders/Cheats are now
premium (overriding the earlier "fast-forward free" stance) and the 8-min/30-min-first
free-tier budget. Note bindings are generated on demand (committed snapshot dropped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Less burdensome free-tier extension: each completed rewarded ad now adds +11 minutes,
capped at 2 grants per session (still +22 min max -> 30 min total on a regular 8-min
session), so a fully-engaged free user watches only TWO ads instead of eleven.

- Core: reward_play_ms 120_000 -> 660_000 (11 min), max_reward_grants_per_session
  11 -> 2; clamp ceiling raised to 20 min so 11 fits; default-contract test updated
  (8 + 2*11 = 30 min). 17 tests pass.
- Shells (RewardedGate.{kt,swift}) + all docs/monetization swept from "+2 min /
  11-grant" to "+11 min / 2-grant".

The offline-grace continuation stays a separate one-time +2 min; the +22-min ceiling
and 30-min total are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…play split (ADR 0025)

Per the maintainer's 2026-06-23 decision: capture the foss/play Android flavor split as
v2.1.0 architecture, and wire the clean monetization crate into the Android build now.

Decision (ADR 0025): split the Android build into a `foss` flavor (default — no Google
SDKs, no ads, no tracking; the F-Droid + sideload artifact) and a `play` flavor (all six
proprietary SDK groups — Billing, Cast, Play Games, Integrity, update/review + AppLovin +
RevenueCat — for Google Play). F-Droid requires a Google-/ad-free build and a BuildConfig
flag gates code not packaging, so a product flavor is the only clean-channel path. The
five existing proprietary subsystems move behind src/play façades (no-op in src/foss) +
an installDebug alias; both flavors verified on-device at v2.0.9; foss -> F-Droid and
play -> Google Play at v2.1.0. Captured in v2.0.x-mobile-finalization-plan.md (work items
across v2.0.1-v2.0.4), DEFERRED-AND-CARRYOVER, docs/android.md.

Wired now (the clean part): android/app/build.gradle.kts builds librustynes_monetization.so
via cargo-ndk and generates its UniFFI Kotlin bindings (com.doublegate.rustynes.monetization.ffi)
into the main source set — dormant; nothing calls it yet (the AppLovin/RevenueCat glue is the
v2.1.0 play-flavor step). The crate is our own AdPolicy core: no Google, no ads, no tracking,
so it is flavor-agnostic. Verified: the crate cross-builds for arm64 and the bindgen emits the
full AdPolicy surface (incl. the build-out API).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 23, 2026 02:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Expands the rustynes-monetization subsystem toward the planned v2.1.0 mobile store launch by updating the freemium time-gate model, adding session/offline/persistence/remote-config hardening to the Rust core, documenting the Android foss/play flavor split (ADR 0025), and wiring the monetization UniFFI bindings into the Android Gradle build (still dormant).

Changes:

  • Updated monetization core contract: expanded PremiumFeature, added first-session behavior, offline grace, progress export/restore, and remote-config clamping.
  • Added rewarded-ad gate shells for Android (Kotlin) and iOS (Swift) that grant time only on the reward callback.
  • Documented the ADR + rollout plan for foss/play Android distribution flavors and updated monetization documentation to the new reward model.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
to-dos/plans/v2.0.x-mobile-finalization-plan.md Adds the planned foss/play flavor split workstream and sequencing notes.
to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md Adds a deferred item summarizing the flavor split and related wiring/verification.
docs/monetization/rustynes-integration.md Updates monetization integration doc for the new rewarded grant model.
docs/monetization/recommendations.md Updates recommendation copy to reflect +11 min reward wording/cap references.
docs/monetization/README.md Updates monetization doc index/status; still needs consistency fixes (see comments).
docs/monetization/pre-implementation-addendum.md Updates the core model narrative; still needs a few value references aligned (see comments).
docs/monetization/platform-setup-runbook.md Updates AppLovin/MAX setup wording to match +11 min reward grant.
docs/monetization/implementation-brief.md Partially updated; still contains outdated constant values (see comments).
docs/monetization/build-and-bindings.md Updates FFI surface + adds build-out additions section; needed corrections noted.
docs/monetization/AGENT-GUIDANCE.md Updates internal agent guidance; needed corrections noted.
docs/android.md Documents the planned foss/play split and the (dormant) monetization-core wiring.
docs/adr/0025-foss-play-android-flavor-split.md New ADR defining the distribution flavor split decision and mechanics.
crates/rustynes-monetization/src/monetization.rs Core logic expansion: new config fields, session/offline/progress/clamp behavior, tests.
crates/rustynes-monetization/shells/ios/RewardedGate.swift New iOS rewarded-ad gate shell (AppLovin MAX) calling grantRewardedTime() only on reward.
crates/rustynes-monetization/shells/android/RewardedGate.kt New Android rewarded-ad gate shell (AppLovin MAX) calling grantRewardedTime() only on reward.
android/app/build.gradle.kts Builds rustynes-monetization via cargo-ndk and generates Kotlin UniFFI bindings in preBuild.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/monetization/build-and-bindings.md Outdated
Comment thread docs/monetization/AGENT-GUIDANCE.md
Comment thread docs/monetization/AGENT-GUIDANCE.md Outdated
Comment thread docs/monetization/pre-implementation-addendum.md Outdated
Comment thread docs/monetization/README.md Outdated
Comment thread docs/monetization/README.md Outdated
Comment thread docs/monetization/implementation-brief.md Outdated
Comment thread crates/rustynes-monetization/src/monetization.rs Outdated
Comment thread docs/monetization/pre-implementation-addendum.md Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the monetization model for the RustyNES mobile ports, changing the free-tier rewarded ad extension from +2 minutes (capped at 11 grants) to +11 minutes (capped at 2 grants) per session. It also introduces a generous first-session budget, first-session interstitial suppression, a one-time offline grace budget, progress persistence, and expands the premium feature set to include fast-forward, shaders, and cheats. Additionally, it integrates the monetization crate into the Android build, adds Android and iOS rewarded ad gates, and documents a planned foss/play flavor split. The reviewer feedback suggests clamping the minimum first-session play budget to 1 minute in clamp_ad_config to prevent potential app bricking from a bad remote config, and highlights several leftover references to the old 11-grant/2-minute model in the documentation.

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.

Comment thread crates/rustynes-monetization/src/monetization.rs Outdated
Comment thread docs/monetization/AGENT-GUIDANCE.md
Comment thread docs/monetization/README.md Outdated
Comment thread docs/monetization/build-and-bindings.md Outdated
…doc sweep

- clamp_ad_config: floor first_session_play_ms at 60_000 (Gemini HIGH) — a remote `0`
  would set session #1's budget to 0 ms and brick the app for new users; +1 test.
- Reconcile the reward-model doc stragglers my earlier sweep missed (and one over-reach):
  the +11 min × 2-ad model now consistent everywhere (cap 11 -> 2, reward_play_ms
  120_000 -> 660_000, the §9 tuning table, the "reward 2 min" open-decision lines), the
  offline-grace over-reach reverted to +2 min, the unit-test count 13 -> 17, and the
  "fast-forward stays free" lines updated to the six-feature premium set (FastForward /
  Shaders / Cheats now premium) across README / rustynes-integration / implementation-brief
  / shells README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate
doublegate merged commit 4a47374 into main Jun 23, 2026
22 checks passed
@doublegate
doublegate deleted the feat/v1.8.9-monetization-buildout branch June 23, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants