From 061e6adaf998dcc9cfdc3dc24031c7e4e7aab4d0 Mon Sep 17 00:00:00 2001 From: unity-hub-bot Date: Wed, 16 Sep 2026 10:26:36 +0000 Subject: [PATCH] sync: align skills catalog with Unity-Technologies/skills Mirrors the approved skills/ catalog from Unity-Technologies/skills@main into this plugin. --- skills/implement-in-app-purchases/README.md | 233 ++++++++++++++++++ .../levelplay-unity-integration/CHANGELOG.md | 69 ++++++ skills/levelplay-unity-integration/README.md | 78 ++++++ .../setup-vivox-voice-chat/evals/.env.example | 6 + .../setup-vivox-voice-chat/evals/.gitignore | 7 + skills/setup-vivox-voice-chat/evals/README.md | 101 ++++++++ .../evals/promptfooconfig.yaml | 32 +++ .../evals/tests/init-and-login.yaml | 76 ++++++ .../evals/tests/text-chat.yaml | 65 +++++ .../evals/tests/voice-channels.yaml | 74 ++++++ 10 files changed, 741 insertions(+) create mode 100644 skills/implement-in-app-purchases/README.md create mode 100644 skills/levelplay-unity-integration/CHANGELOG.md create mode 100644 skills/levelplay-unity-integration/README.md create mode 100644 skills/setup-vivox-voice-chat/evals/.env.example create mode 100644 skills/setup-vivox-voice-chat/evals/.gitignore create mode 100644 skills/setup-vivox-voice-chat/evals/README.md create mode 100644 skills/setup-vivox-voice-chat/evals/promptfooconfig.yaml create mode 100644 skills/setup-vivox-voice-chat/evals/tests/init-and-login.yaml create mode 100644 skills/setup-vivox-voice-chat/evals/tests/text-chat.yaml create mode 100644 skills/setup-vivox-voice-chat/evals/tests/voice-channels.yaml diff --git a/skills/implement-in-app-purchases/README.md b/skills/implement-in-app-purchases/README.md new file mode 100644 index 0000000..f714fd4 --- /dev/null +++ b/skills/implement-in-app-purchases/README.md @@ -0,0 +1,233 @@ +# Unity In-App Purchases Skill + +This skill helps you implement, configure, debug, and migrate Unity In-App Purchases (IAP) using `com.unity.purchasing` v5. It covers standard Apple App Store / Google Play billing, IAP D2C Capabilities (Direct-to-Customer — Stripe/Coda via Unity Cloud), and conversion from a wide range of third-party and native billing implementations. + +--- + +## What This Skill Covers + +| Path | When to use | +|---|---| +| **Add IAP to a new project** | No existing IAP — start from scratch with Unity IAP 5 | +| **Migrate v4 → v5** | Project uses `IStoreListener`, `UnityPurchasing.Initialize`, or `ConfigurationBuilder` | +| **Convert native Google BillingClient** | Project calls Android BillingClient via `AndroidJavaObject` / JNI bridge | +| **Convert native iOS StoreKit** | Project uses a custom ObjC/Swift StoreKit plugin bridged via `DllImport("__Internal")` | +| **Convert Essential Kit billing** | Project uses VoxelBusters Essential Kit for in-app purchases (without losing other features) | +| **Assess/Convert UniPay (FLOBUK)** | Project uses UniPay — determines whether migration is needed or possible | +| **Assess/Convert RevenueCat** | Project uses RevenueCat — evaluate/implement observer mode to work with Unity IAP 5 | +| **Assess/Convert Adapty** | Project uses Adapty — evaluate/implement observer mode to work with Unity IAP 5 | +| **Implement IAP D2C Capabilities** | Add third-party payment provider (Stripe or Coda) via Unity Cloud | + +The skill always scans the project first and routes to the correct path automatically. You can also specify a path manually if you know exactly which one you need. + +--- + +## Example Prompts + +``` +Add in-app purchases code to my game. I have a 100-coin pack and a Remove Ads unlock. +``` +``` +Migrate my existing IAP code in the project from Unity IAP v4 to v5. +``` +``` +My project uses AndroidJavaObject to call Google Play BillingClient. Convert it to Unity IAP. +``` +``` +I have a custom ObjC StoreKit plugin with SKPaymentQueue. Replace it with Unity IAP. +``` +``` +Disable Essential Kit billing and replace with Unity IAP 5. Keep other Essential Kit features working as before. +``` +``` +Can my RevenueCat project implement app store purchases through Unity's IAP v5? +``` +``` +My project uses Adapty. What features will we lose if we use Unity IAP instead? Give me a detailed report and do not make any changes now. +``` +``` +Add Stripe payment support via Unity IAP D2C Capabilities. +``` +``` +Add a web checkout option for my players using Unity's IAP D2C Capabilities with Coda. +``` + +--- + +## Path Details, Limitations, and Best Practices + +### Add IAP to a New Project + +**What it does:** Installs Unity IAP 5, creates an `IAPManager`, wires up the two-step purchase flow (pending → confirm), integrates with your existing save system, and connects to your shop UI. + +**Limitations:** +- The skill cannot create App Store Connect or Google Play Console product entries — you must do that manually. +- If you use local Google Play receipt validation, you must run the Receipt Validation Obfuscator manually in the Unity Editor (**Services > In-App Purchasing > Receipt Validation Obfuscator**). +- Apple local receipt validation is a no-op under StoreKit 2. Use server-side JWS validation for Apple. + +**Best practices:** +- Define all products in one authoritative catalog (a `ScriptableObject` or constants class) — not scattered across button click handlers. +- Always call `ConfirmPurchase` **after** saving the granted reward. An unconfirmed purchase re-delivers safely; a confirmed-but-unsaved one loses the reward permanently. +- Subscribe to all events **before** calling `Connect()` — pending purchases from a previous session may fire immediately on reconnect. +- Add a "Restore Purchases" button for any project with NonConsumable or Subscription products — required on iOS. + +--- + +### Migrate v4 → v5 + +**What it does:** Replaces the listener-based `IStoreListener` / `UnityPurchasing.Initialize` / `ConfigurationBuilder` pattern with the event-driven `StoreController` pattern. + +**Limitations:** +- `product.receipt` and `product.hasReceipt` are removed. The skill migrates ownership checks to `store.CheckEntitlement(product)` — verify your entitlement logic after migration. +- Apple local validation via `CrossPlatformValidator` still compiles but is a no-op under StoreKit 2. If your project validates Apple receipts locally, this validation silently stops working after migration. +- Developer payload (the third `Purchase()` argument) is removed. If your backend uses it, a backend change is required. +- `SubscriptionManager` is replaced — subscription info is now on `order.Info.PurchasedProductInfo`, not on `CartItem`. + +**Best practices:** +- Migrate one system at a time: get initialization working first, then purchase flow, then restore. +- Run the migration in a branch. The skill uses `#if` guards where needed, but test thoroughly before removing the old code. +- After migration, recheck all `OnPurchaseConfirmed` handlers — the event now receives `Order` (base type) and you must pattern-match `ConfirmedOrder` vs `FailedOrder`. + +--- + +### Convert Native Google BillingClient + +**What it does:** Converts C# → JNI → BillingClient bridge code to Unity IAP 5, preserves the public billing API via a compatibility facade, and wraps old native code in `#if !USE_UNITY_IAP_V5` for easy rollback. + +**Limitations:** +- **Multiple subscription base plans / offer tokens** (`basePlanId`, `offerToken`, `offerId`) are a hard blocker — Unity IAP does not expose offer-level selection. You must either simplify your Play Console subscription setup to one base plan per product, or stay on native billing. +- **Personalized price disclosure** (`setIsPersonalizedPrice`) and **alternative billing / external offers** are hard blockers. +- **Multi-quantity purchases** (quantity > 1 per transaction) are not supported in Unity IAP. +- A mixed native BillingClient + Unity IAP architecture is not supported — both compete for the same `PurchasesUpdatedListener` slot. The skill will not generate a mixed setup. +- The receipt format changes: the backend receives `order.Info.Receipt` (a JSON object containing `purchaseToken`, `orderId`, and `signature`) instead of raw fields. If your backend parses these fields individually, a backend update is required. + +**Best practices:** +- Run the blocker scan and read the migration report before making any changes. Hard blockers require a decision before code is written. +- Keep the Gradle billing dependency in place until migration is validated — Unity IAP brings its own BillingClient, but removing the old dependency prematurely can break the build in unexpected ways. +- Use the `#if USE_UNITY_IAP_V5` / `#if !USE_UNITY_IAP_V5` guards throughout. The rollback path (remove the define) must work cleanly before you ship. +- Test on a physical Android device with a real sandbox account — the BillingClient behavior in the Unity Editor and on emulators is not representative of production. + +--- + +### Convert Native iOS StoreKit + +**What it does:** Converts a custom Objective-C or Swift StoreKit plugin (bridged via `DllImport("__Internal")` and `UnitySendMessage`) to Unity IAP 5, preserving the C# game-facing API via a compatibility facade. + +**Limitations:** +- **Receipt format is a breaking backend change.** Unity IAP uses per-transaction JWS (StoreKit 2 style) rather than the SK1 base64 app receipt bundle. If your backend validates the receipt bundle against Apple's `/verifyReceipt` endpoint, it must migrate to Apple's App Store Server API or JWS validation before you can ship. +- **Promotional offer signing** (`SKPaymentDiscount` / SK2 signed offers) is a hard blocker — Unity IAP has no equivalent for server-signed promotional offers. +- **Win-back offers** (StoreKit 2) are not supported in Unity IAP 5.4. +- **`SKStorefront`** (region detection via storefront change observer) is not exposed in Unity IAP. +- **`SKReceiptRefreshRequest`** has no direct equivalent — `store.FetchPurchases()` covers the purchase re-delivery use case but not manual receipt refresh. +- ObjC/Swift plugin files cannot use C# `#if` defines. The skill guards only the C# call sites — the native files remain in `Assets/Plugins/iOS/` and compile unconditionally. +- A mixed native StoreKit + Unity IAP architecture is not supported — both register as `SKPaymentQueue` observers and only one reliably receives callbacks. + +**Best practices:** +- Audit the backend receipt validation endpoint before starting. If it calls `/verifyReceipt`, plan the backend migration in parallel with the client migration. +- If the plugin intercepts App Store promotional purchases (`shouldAddStorePayment`), wire up `OnPromotionalPurchaseIntercepted` and `ContinuePromotionalPurchases()` before removing the native interceptor. +- Test Ask-to-Buy explicitly — the deferred → pending two-stage flow behaves differently than a native `PURCHASING` → `PURCHASED` transition. +- Validate the migration on TestFlight, not just in the Unity Editor or Simulator. StoreKit behavior differs between Editor sandbox, Simulator, and TestFlight builds. + +--- + +### Convert Essential Kit Billing + +**What it does:** Disables the Essential Kit Billing service flag in `EssentialKitSettings.asset`, removes the conflicting `com.android.billingclient` Gradle dependency, and implements Unity IAP 5 using the product catalog extracted from the Essential Kit settings. + +**Limitations:** +- Essential Kit C# source files are **not deleted**. The Billing service is disabled via the settings flag — all EK code remains and compiles. Do not expect a clean removal. +- Only the Billing service is affected. All other Essential Kit services (Notification, GameServices, etc.) are left completely untouched. +- **Product IDs must not change.** The same IDs used in Essential Kit must be carried over to Unity IAP to preserve store history. +- If your project uses **server-side receipt validation**, the receipt format changes from `transaction.RawData` (Android) and `transaction.Receipt` (iOS JWS) to `order.Info.Receipt` and `order.Info.Apple?.jwsRepresentation`. Document the backend change required. +- Subscriptions are fully supported, but the restore path changes — EK's `OnRestorePurchasesComplete` maps to both `store.OnPurchasesFetched` and the `RestoreTransactions` callback in Unity IAP. + +**Best practices:** +- Run `Assets > External Dependency Manager > Android Resolver > Force Resolve` after removing the Gradle billing dependency — do not skip this step. +- Verify the Essential Kit Billing service is visually disabled in **Window > Voxel Busters > Essential Kit > Open Settings → Services** after the settings file edit. +- Confirm all product IDs in App Store Connect and Play Console match the Unity IAP catalog exactly before testing. + +--- + +### Assess UniPay (FLOBUK) + +**What it does:** This path does **not** perform a conversion. It assesses whether migration is needed and routes to one of three outcomes: unsupported platform (stop), upgrade required (stop), or no action needed (UniPay already wraps Unity IAP 5). + +**Limitations:** +- UniPay's Steam, Meta Quest, PayPal, and Facebook Instant Games integrations have **no Unity IAP equivalent**. If your project targets any of these platforms, there is no migration path — keep UniPay. +- If `com.unity.purchasing` is below v5.4, IAP D2C Capabilities (Stripe/Coda) are not available — an upgrade to the latest stable v5.4+ is needed before adding D2C support. +- The skill does not perform a "remove UniPay" conversion — that is a manual refactor scoped to what features you are replacing. + +**Best practices:** +- If the assessment concludes "no action needed," there is no code to write. Work within UniPay's API for new products or purchase logic changes. +- If you want to remove UniPay entirely and use Unity IAP directly, ask the skill explicitly and describe which UniPay features you are replacing — it will assess feasibility and scope. + +--- + +### Assess/Convert RevenueCat + +**What it does:** Evaluates whether the project can switch to Unity IAP 5 for handling purchases. Produces one of three outcomes: already in observer mode (no action), blockers detected (report + two choices), or no blockers (two conversion paths: observer mode or full removal). + +**Limitations:** +- **Amazon Appstore** is a hard blocker — Unity IAP 5 has removed Amazon support. RevenueCat observer mode also does not work reliably on Amazon builds. If your project targets Amazon, conversion is not viable. +- **RevenueCat Offerings / remote paywalls** have no Unity IAP equivalent — all products must be defined in code or a local catalog. +- **RevenueCat A/B testing (Experiments)** has no Unity IAP equivalent. +- **Cross-platform entitlement sync** (a user who buys on iOS retains access on Android) has no Unity IAP equivalent without a custom backend. +- **RevenueCat webhook events** (subscription renewals, cancellations, billing issues) are not delivered by Unity IAP — you would need to build your own subscription event infrastructure. +- In **observer mode**, `SyncPurchases()` must be called after every Unity IAP confirmed purchase. Missing this call means RevenueCat does not validate the receipt and `CustomerInfo` is not updated. + +**Best practices:** +- Run the full feature check before deciding. RevenueCat's value often comes from features that are not obvious in the codebase (e.g., webhooks configured server-side). +- If the project has a marketing team managing paywall copy or pricing remotely via RevenueCat Offerings, a full removal will require significant UI work to replace that capability. +- Observer mode is the lower-risk path — it preserves RevenueCat's server-side validation and subscription lifecycle tracking while Unity IAP handles the native purchase flow. + +--- + +### Assess/Convert Adapty + +**What it does:** Evaluates whether the project can switch to Unity IAP 5 for app store purchase handling. Produces one of three outcomes: already in observer mode (no action), blockers detected (report + two choices), or no blockers (observer mode or full removal). + +**Limitations:** +- **Adapty Paywall Builder** has no Unity IAP equivalent — and it is also **unavailable in Adapty's own Observer Mode**. Switching to observer mode loses Paywall Builder regardless of whether Unity IAP is involved. +- **Adapty A/B testing** has no Unity IAP equivalent. In observer mode, A/B testing is possible but requires significant manual instrumentation. +- **Cross-platform entitlement sync** has no Unity IAP equivalent without a custom backend. +- In **observer mode**, `Adapty.ReportTransaction()` must be called after every Unity IAP confirmed purchase. Missing this call means Adapty does not validate the receipt server-side. + +**Best practices:** +- If your marketing team uses Adapty's Paywall Builder to update paywall layouts without app releases, note that this capability is lost in observer mode. Make sure all stakeholders understand this before proceeding. +- Full removal requires replacing any `AdaptyUI` / `PaywallView` shop UI with custom Unity UI before Adapty can be removed. Budget time for this work. +- Observer mode is the lower-risk path and preserves Adapty's webhook delivery and analytics integrations. + +--- + +### Implement IAP D2C Capabilities + +**What it does:** Adds Direct-to-Customer (D2C) third-party payment provider support (Stripe or Coda) via Unity Cloud, including remote catalog setup, deep link configuration, the built-in payment options picker UI, Apple/Google external purchase compliance tools, and entitlement delivery guidance. + +**Limitations:** +- Requires **Unity IAP v5.4+**, **Unity Editor 2022.3+**, `com.unity.services.authentication` **v3.7.1+**, and `com.unity.services.core` **v1.18.0+**. All must be satisfied before any code is written. +- **Subscriptions are not supported** by IAP D2C Capabilities in v5.4. Only Consumable and NonConsumable products can be used. +- Requires a **Stripe or Coda account** connected in the Unity Cloud IAP dashboard, and Unity must enable D2C for your organization — contact your Unity Client Partner if it is not yet enabled. +- **Routing rules** must be configured in the Unity Dashboard before any player is offered a D2C payment option. Without a routing rule, no provider is offered even if a provider account is connected. +- **External web payments via Stripe/Coda are permitted in select regions only.** Apple and Google have their own program eligibility requirements. The developer is responsible for determining eligibility and meeting disclosure requirements — the skill does not perform compliance on your behalf. +- **Anonymous sign-in** must not be used as the authentication method. If a player's session token is lost (reinstall, app data clear), purchase history tied to an anonymous identity becomes unrecoverable. +- The **receipt format is different from standard IAP 5** — D2C purchases go through Unity Cloud, not the device's native store, so `order.Info.Apple` and `order.Info.Receipt` behave differently for D2C orders. + +**Best practices:** +- Set up **routing rules** in the Unity Dashboard before testing. Without them, `GetEligiblePaymentProviders()` returns an empty list and the purchase UI never appears — this is a common "nothing happens" issue during initial integration. +- Use a **proxy HTML page** for the Success Redirect URL rather than a direct app-scheme URL. On some Android and iOS devices, direct app-scheme redirects from the payment provider domain are silently dropped. Host the proxy on a stable HTTPS domain you control. +- Use **`ShowPurchaseOption(catalogListingId)`** as the primary purchase entry point — it shows the built-in picker UI and handles provider selection automatically. Only fall back to `PurchaseProduct` directly when `GetEligiblePaymentProviders()` returns an empty `Providers` list. +- Configure a **Cancel Redirect URL** in the payment provider dashboard — without it, the checkout page has no "Back" button and players who change their mind are stuck in the browser. +- The SDK remembers the **last used payment provider per player per device** (provider memory). This is expected behavior, not a bug. Clear app data to reset it during testing. +- Deploy the **Deployment package** (`com.unity.services.deployment`) early — it is required to push `.ucat` product definitions to the Remote Catalog. Without it, the catalog cannot be deployed and `FetchRemoteCatalog()` returns no products. + +--- + +## General Best Practices + +- **Always let the skill scan first.** The pre-check (`pre-check.md`) detects third-party packages, native billing code, and existing Unity IAP versions before routing. Skipping it leads to incompatible changes. +- **Read the migration report before approving any code changes.** Every conversion path produces a report covering what will change, what blockers were found, and what manual steps remain. Review it before proceeding. +- **Never confirm a purchase before saving the reward.** An unconfirmed purchase re-delivers safely. A confirmed-but-unsaved one is gone permanently. +- **Subscribe to all failure events.** `OnProductsFetchFailed`, `OnPurchasesFetchFailed`, `OnStoreDisconnected` — not subscribing generates runtime warnings and leaves failures silently unhandled. +- **Subscribe to `OnPurchaseDeferred`.** Ask-to-Buy (iOS) and Google Play deferred purchases fire this event. Not subscribing silently drops them. +- **Use `#if USE_UNITY_IAP_V5` guards** for all migration work. The rollback path (remove the define from Player Settings) must work cleanly before shipping. +- **Test with real sandbox accounts on real devices.** Editor sandbox and emulators do not reproduce all edge cases — particularly pending purchases, deferred flows, and restore behavior. diff --git a/skills/levelplay-unity-integration/CHANGELOG.md b/skills/levelplay-unity-integration/CHANGELOG.md new file mode 100644 index 0000000..0950dc5 --- /dev/null +++ b/skills/levelplay-unity-integration/CHANGELOG.md @@ -0,0 +1,69 @@ +# Changelog + +## v0.10.0 — 2026-08-21 — Workflow spine, and a hard install gate + +Moves reference material out of `SKILL.md` and makes the SDK install a verified step rather than an assumed one. + +**Changed:** +- `SKILL.md` is now the workflow spine only, going from about 1,100 lines to 435. The dependency-resolution, testing-and-validation and troubleshooting material that was inlined in it moves into `references/`. Nothing was dropped; it is read on demand instead. `SKILL.md` is loaded in full on every invocation while a reference file is read only when a step links it, so this is a saving on every run that does not need the detail. +- The reference set grows from nine files to twelve: `dependency-resolution.md`, `testing-and-validation.md` and `troubleshooting.md` are now separate files. + +**Added:** +- **A hard install-verification gate at Step 3.** No LevelPlay code is written until `com.unity.services.levelplay` is confirmed present in `Packages/packages-lock.json`, read from the project rather than taken from the Package Manager window or an earlier turn. The package is easy to believe is installed: its display name is **Ads Mediation** while the recorded id is `com.unity.services.levelplay`, two similarly named packages are the wrong ones, and the install prompts for a second package partway through. Code written before the id resolves fails with `CS0246` on every LevelPlay symbol, which reads as a code problem rather than an install problem. The gate also distinguishes "the install never happened" from "Unity has not resolved it yet", because the fix differs. +- The deprecated-APIs section now states explicitly that `SetGDPRConsents(Dictionary)` is **not** deprecated on SDK 9.4.x, where it is the correct call, and only becomes `[Obsolete]` on 9.5.0+. It is kept out of the deprecated list rather than listed with a caveat, so it cannot be read the wrong way round. + +## v0.9.0 — 2026-08-17 — SDK 9.x migration support + +Adds guided migration to the LevelPlay 9.x SDK and the current Ad Unit (MADU) APIs. + +**New:** +- "New Integration or Migration?" routing step at the start of the workflow +- Migration reference guide covering five scenarios: SDK upgrade (.unitypackage or UPM, including switching from .unitypackage to UPM), init API migration (IronSource.Agent to LevelPlay.Init), ad unit API migration (rewarded, interstitial, banner, and the ILRD handler), Maven Central dependency build failures, and Unity Ads (Advertisement Legacy) migration +- Upgrade safety flow: Developer Settings values and installed adapters are inventoried, and the user confirms, before any folder deletion; post-upgrade steps cover adapter reinstall, settings re-entry, and removal of the stale LEVELPLAY_DEPENDENCIES_INSTALLED scripting define when switching from .unitypackage to UPM +- Migration completeness checklist covering requirements that a line-by-line translation misses: placement capping checks when dashboard placements are used, an explicit rewarded load trigger, the version API mappings, correct ILRD event names, preserved logging, HideAd vs DestroyAd intent for legacy destroyBanner calls, and removal of onApplicationPause +- Compilation check after migration edits, with errors fixed before presenting results +- Skill description now also triggers on SDK upgrades, deprecated IronSource.Agent APIs, and Unity Ads migration + +**Fixed:** +- Banner adaptive-size example used a constructor form that does not compile on any 9.x version; now configured through Config.Builder (verified against 9.0.0, 9.4.0, and 9.5.0 source) +- API mapping corrections: validateIntegration maps to LevelPlay.ValidateIntegration (not LaunchTestSuite); pluginVersion maps to LevelPlay.PluginVersion (distinct from UnityVersion); onApplicationPause is removed in 9.x with no replacement; the legacy ILRD subscription maps to LevelPlay.OnImpressionDataReady on SDK 9.4.x and earlier or per-instance OnAdImpressionDataReady on 9.5.0+ +- Unity Ads migration now surfaces that LevelPlay.Init has no test-mode parameter (Test Suite or dashboard test mode are the equivalents) instead of dropping the flag silently +- Package edits during upgrades touch only manifest.json; packages-lock.json is never hand-edited +- Maven Central migration is mentioned only when the project actually needs it +- Corrected a consent-callback name mismatch in the privacy reference, and a banner troubleshooting example that called a method banners do not have + +## v0.8.0 — 2026-08-05 — Version-aware ILRD (SDK 9.5.0), rewarded load lifecycle, and improved activation + +Accuracy and activation updates reflecting current LevelPlay SDK behavior. + +**Impression-level revenue (ILRD) — SDK 9.5.0 API change** +- ILRD now documents both delivery mechanisms: the single global `LevelPlay.OnImpressionDataReady` event (SDK 9.4.x and earlier) and the per-ad-instance `OnAdImpressionDataReady` events on each ad object (SDK 9.5.0+), which replace the global event. +- The global event still exists but is deprecated on SDK 9.5.0+ and generates a compiler warning. +- Updated the initialization step and the rewarded/interstitial/banner references to direct SDK 9.5.0+ users to the per-instance approach. + +**Rewarded ad load lifecycle** +- Clarified that `LoadAd()` must be called explicitly; the SDK does not auto-manage rewarded loading (unlike the legacy IronSource API). +- Reframed the guidance so explicit, publisher-triggered loading is the default, with eager preloading documented as an optional pattern. Applies to `references/rewarded-api.md` and the loading-strategy guidance in `SKILL.md`. + +**Description and activation** +- Reworked the skill description to increase activation on general ad and monetization requests, not only when a developer names LevelPlay. +- Added guidance at the top of the skill directing the agent to run it as an interactive, step-by-step workflow and use the reference files, rather than answering from general knowledge. + +## v0.7.0 — 2026-06-12 — Initial public beta release + +First release of the LevelPlay Unity integration skill, released as public beta. + +**Features:** +- Step-by-step installation of the LevelPlay SDK using the Ads Mediation package in Unity Package Manager +- Native dependency resolution for Android and iOS +- SDK initialization with three code organization options +- Ad unit strategy recommendations based on business goals (revenue-focused, UX-focused, or balanced) +- Implementation guides for rewarded ads, interstitials, and banner ads +- Privacy compliance support (GDPR, CCPA, COPPA) +- iOS setup (App Tracking Transparency, SKAdNetwork) +- Impression-level revenue tracking (ILRD) +- Testing guidance using mock ads and the LevelPlay Test Suite + +## Feedback + +This skill is currently in beta. [Share your feedback here](https://docs.google.com/forms/d/e/1FAIpQLSe7WvWozJ67KjgOLglSBvLug8JdgEYk895nn_BHZs0HS_bWJA/viewform). diff --git a/skills/levelplay-unity-integration/README.md b/skills/levelplay-unity-integration/README.md new file mode 100644 index 0000000..fe4b205 --- /dev/null +++ b/skills/levelplay-unity-integration/README.md @@ -0,0 +1,78 @@ +# LevelPlay Unity Integration Skill + +![Beta](https://img.shields.io/badge/status-beta-orange) ![Version](https://img.shields.io/badge/version-0.7.0-blue) ![License](https://img.shields.io/badge/license-Unity%20Companion-blue) + +> 🧪 **Note:** This skill is in beta and will be shaped by your feedback. Try it out and [let us know what you think](https://docs.google.com/forms/d/e/1FAIpQLSe7WvWozJ67KjgOLglSBvLug8JdgEYk895nn_BHZs0HS_bWJA/viewform)! + +A skill that guides Unity developers through integrating the LevelPlay SDK using the Ads Mediation package in Unity Package Manager: from installation to fully working rewarded ads, interstitials, and banners. + +Compatible with Claude Code, GitHub Copilot, Cursor, Cline, and [50+ other agents](https://skills.sh). + +## What it does + +When you activate this skill, your agent walks you step by step through the complete LevelPlay integration: + +1. **Installing the SDK** via the Ads Mediation package in Unity Package Manager +2. **Resolving native dependencies** for Android and iOS builds +3. **Collecting credentials** from the LevelPlay dashboard +4. **Configuring privacy compliance** (GDPR, CCPA, COPPA) if needed +5. **Initializing the SDK** in your project, with three code organization options to fit your existing setup +6. **Recommending an ad unit strategy** based on your goals (revenue-focused, UX-focused, or balanced) +7. **Implementing ad formats** — rewarded ads, interstitials, and banners — with production-ready C# code +8. **Testing and validating** using mock ads in the Unity Editor and the LevelPlay Test Suite on device + +The skill also covers iOS-specific setup (App Tracking Transparency, SKAdNetwork), impression-level revenue tracking (ILRD) for analytics platforms, bid floors, and common troubleshooting. + +## Requirements + +- A Unity project using an LTS or actively developed version of the Unity Editor +- LevelPlay Unity package and SDK version 9.4.0+ +- A LevelPlay account: [get started here](https://platform.ironsrc.com/) + +Documentation for setting up the LevelPlay Unity package: see the [Unity Package Integration guide](https://docs.unity.com/en-us/grow/levelplay/sdk/unity/package-integration). + +## Installation + +```bash +npx skills add Unity-Technologies/skills +``` + +Then activate the `levelplay-unity-integration` skill in your agent. + +## Using the skill + +Type `/levelplay-unity-integration` to activate the skill, then describe what you want to do: + +- *"I want to add rewarded ads to my Unity game"* +- *"Help me integrate LevelPlay into my project"* +- *"I need to add interstitial ads between levels"* +- *"I have LevelPlay installed, help me implement banner ads"* + +You can jump in at any step. If the Unity package and SDK are already installed, your agent will pick up from where you are. + +## Privacy & Legal + +> **Note:** This skill provides technical integration guidance, including for LevelPlay's privacy APIs. It is not legal advice, and it does not determine which laws apply to your app — that depends on your users, your data practices, and your distribution. Consult your own legal counsel, and refer to [Regulation Advanced Settings for Unity](https://docs.unity.com/en-us/grow/levelplay/sdk/unity/regulation-advanced-settings) for the authoritative LevelPlay documentation. + +## What's in this folder + +``` +levelplay-unity-integration/ +├── SKILL.md # The workflow spine: decisions, checkpoints, questions +├── references/ # Detail read on demand, linked from the step that needs it +│ ├── initialization-api.md +│ ├── rewarded-api.md +│ ├── interstitial-api.md +│ ├── banner-api.md +│ ├── ilrd-api.md +│ ├── privacy-settings.md +│ ├── ios-setup.md +│ ├── dependency-resolution.md +│ ├── testing-and-validation.md +│ ├── troubleshooting.md +│ ├── migration-sdk-9.md +│ └── best-practices.md +├── CHANGELOG.md +└── README.md +``` + diff --git a/skills/setup-vivox-voice-chat/evals/.env.example b/skills/setup-vivox-voice-chat/evals/.env.example new file mode 100644 index 0000000..6358e47 --- /dev/null +++ b/skills/setup-vivox-voice-chat/evals/.env.example @@ -0,0 +1,6 @@ +# Copy this file to .env and fill in your credentials: +# cp .env.example .env +# +# Get your LiteLLM API key from: https://uai-litellm.internal.unity.com +OPENAI_API_KEY=your-litellm-api-key-here +OPENAI_BASE_URL=https://uai-litellm.internal.unity.com diff --git a/skills/setup-vivox-voice-chat/evals/.gitignore b/skills/setup-vivox-voice-chat/evals/.gitignore new file mode 100644 index 0000000..7a33519 --- /dev/null +++ b/skills/setup-vivox-voice-chat/evals/.gitignore @@ -0,0 +1,7 @@ +# API keys - never commit +.env + +# Promptfoo output +output/ +promptfoo-output/ +*.html diff --git a/skills/setup-vivox-voice-chat/evals/README.md b/skills/setup-vivox-voice-chat/evals/README.md new file mode 100644 index 0000000..32a5336 --- /dev/null +++ b/skills/setup-vivox-voice-chat/evals/README.md @@ -0,0 +1,101 @@ +# Vivox Voice & Text Chat Skill Eval Suite + +Evaluation suite for the `setup-vivox-voice-chat` skill, powered by [Promptfoo](https://www.promptfoo.dev/). Validates that the skill routes the model to the correct Vivox v16 APIs (no v4/legacy hallucinations) across init, channel join, and messaging. + +## Prerequisites + +- **Node.js** v18 or later +- A **LiteLLM API key** (from https://uai-litellm.internal.unity.com) + +## Setup + +### 1. Install Promptfoo + +```bash +# Option A: install globally +npm install -g promptfoo + +# Option B: use npx (no install needed) +npx promptfoo@latest eval +``` + +### 2. Configure your API key + +```bash +cd evals/ +cp .env.example .env +``` + +Open `.env` and set your personal LiteLLM key: + +``` +OPENAI_API_KEY=your-litellm-api-key-here +OPENAI_BASE_URL=https://uai-litellm.internal.unity.com +``` + +> **Important:** Never commit your `.env` file. It is already in `.gitignore`. + +## Running the evals + +All commands should be run from the `evals/` directory. + +Use `-j 10` to run up to 10 eval requests concurrently. + +### Run the full suite + +```bash +promptfoo eval -j 10 +``` + +### Run a specific test file + +```bash +promptfoo eval --tests tests/init-and-login.yaml -j 10 +promptfoo eval --tests tests/voice-channels.yaml -j 10 +promptfoo eval --tests tests/text-chat.yaml -j 10 +``` + +## Viewing results + +### Terminal output + +Results are printed to the terminal with pass/fail per assertion. + +### Interactive web UI + +```bash +promptfoo view +``` + +Opens a local UI (usually `http://localhost:15500`) for browsing results, filtering, and comparing runs. + +## Assertions used + +| Type | What it checks | +|---|---| +| `icontains` | Response contains a substring (case-insensitive), e.g. an exact Vivox API name | +| `not-icontains` | Response does NOT contain a substring (used to catch v4 legacy names like `Client.Instance`) | +| `llm-rubric` | An LLM judges whether the response meets a semantic requirement (e.g. correct init order) | + +## Adding new tests + +1. Create a new YAML file in `tests/`: + +```yaml +- description: "Short description of what is being tested" + vars: + user_message: "The user request to test" + reference_content: "file://../references/your-reference.md" # optional + assert: + - type: icontains + value: "VivoxService.Instance.JoinGroupChannelAsync" + - type: not-icontains + value: "SendDirectedTextMessageAsync" + - type: llm-rubric + value: | + Describe the semantic requirement the response must meet. +``` + +2. Add the file to `promptfooconfig.yaml` under `tests:`. + +3. Run it: `promptfoo eval --tests tests/your-new-test.yaml`. diff --git a/skills/setup-vivox-voice-chat/evals/promptfooconfig.yaml b/skills/setup-vivox-voice-chat/evals/promptfooconfig.yaml new file mode 100644 index 0000000..5986d38 --- /dev/null +++ b/skills/setup-vivox-voice-chat/evals/promptfooconfig.yaml @@ -0,0 +1,32 @@ +description: "Vivox Voice & Text Chat Skill Eval Suite" + +providers: + # Evaluated model: used by the test itself + - id: openai:chat:claude-sonnet-4-5 + config: + max_tokens: 2048 + +prompts: + - id: eval-prompt + label: "Eval prompt" + raw: "{{skill_content}}\n\n{{reference_content}}\n\n{{custom_instructions}}\n\n## User Request\n\n{{user_message}}" + +defaultTest: + options: + # Assertion judge: used by llm-rubric + provider: openai:chat:claude-sonnet-4-6 + vars: + skill_content: file://../SKILL.md + reference_content: "" + custom_instructions: | + IMPORTANT: + - This is a planning eval. Do not emit MCP XML/tool-call tags. + - Refer to APIs with their exact names as documented in the provided skill and references. Do not invent or paraphrase symbol names. + - Do not mention tool names you will not call. If a step is inapplicable, explain the behavior without naming the omitted API. + - Always present the complete plan up front. If a step requires a user action, describe what you will do after it succeeds and what happens if it fails, in a single response. + - Answer only the step or phase requested by the user message. Do not include unrelated setup or migration content that was not asked for. + +tests: + - file://tests/init-and-login.yaml + - file://tests/voice-channels.yaml + - file://tests/text-chat.yaml diff --git a/skills/setup-vivox-voice-chat/evals/tests/init-and-login.yaml b/skills/setup-vivox-voice-chat/evals/tests/init-and-login.yaml new file mode 100644 index 0000000..5f47423 --- /dev/null +++ b/skills/setup-vivox-voice-chat/evals/tests/init-and-login.yaml @@ -0,0 +1,76 @@ +# ============================================================ +# WORKFLOW: Init + Login — correct order and re-init guard +# ============================================================ + +- description: "Cold-start init: UGS Core -> Auth -> Vivox Init -> Vivox Login, in order" + vars: + user_message: | + I'm adding Vivox to a fresh Unity project. Walk me through + the initialization from an empty MonoBehaviour Start method. + reference_content: file://../references/init-and-login.md + assert: + - type: icontains + value: "UnityServices.InitializeAsync" + - type: icontains + value: "AuthenticationService.Instance.SignInAnonymouslyAsync" + - type: icontains + value: "VivoxService.Instance.InitializeAsync" + - type: icontains + value: "VivoxService.Instance.LoginAsync" + - type: llm-rubric + value: | + The response MUST describe the four initialization calls in this + exact order: + 1. UnityServices.InitializeAsync + 2. AuthenticationService.Instance.SignInAnonymouslyAsync + 3. VivoxService.Instance.InitializeAsync + 4. VivoxService.Instance.LoginAsync + Any other ordering (e.g. Vivox init before UGS init, or Login + before Vivox init) is a failure. + The response MUST NOT use v4 legacy patterns (Client.Instance, + ILoginSession, AccountId, ChannelId) as callable code. It is + fine — even helpful — to mention those names in a "don't use + these" warning or migration note; a failure is only when the + code samples or step-by-step instructions actually invoke them. + +- description: "Login with display name: LoginOptions.DisplayName" + vars: + user_message: | + After Vivox is initialized, sign the player in with the display name + "Sunbeam" and enable text-to-speech. + reference_content: file://../references/init-and-login.md + assert: + - type: icontains + value: "LoginOptions" + - type: icontains + value: "DisplayName" + - type: icontains + value: "EnableTTS" + - type: icontains + value: "VivoxService.Instance.LoginAsync" + - type: llm-rubric + value: | + The response must construct a LoginOptions with DisplayName set to + "Sunbeam" and EnableTTS set to true, then pass it to + VivoxService.Instance.LoginAsync. It must not USE the v4 AccountId + or ILoginSession types as callable code (mentioning them in a + "don't use" warning is acceptable — failure is only when the code + actually invokes them). + +- description: "Double-init must warn about VxErrorAlreadyInitialized (5041)" + vars: + user_message: | + My Start method runs every time the main scene reloads and I'm + seeing Vivox errors. How do I stop it from re-initializing? + reference_content: file://../references/init-and-login.md + assert: + - type: icontains + value: "5041" + - type: llm-rubric + value: | + The response must identify the underlying issue as + VivoxService.Instance.InitializeAsync being called more than once, + cite the 5041 VxErrorAlreadyInitialized error, and propose a fix + such as guarding with IsInitialized or making the bootstrap + object DontDestroyOnLoad. The fix must NOT be to catch and + swallow the exception. diff --git a/skills/setup-vivox-voice-chat/evals/tests/text-chat.yaml b/skills/setup-vivox-voice-chat/evals/tests/text-chat.yaml new file mode 100644 index 0000000..2102039 --- /dev/null +++ b/skills/setup-vivox-voice-chat/evals/tests/text-chat.yaml @@ -0,0 +1,65 @@ +# ============================================================ +# WORKFLOW: Text chat — channel messages, directed messages, history +# ============================================================ + +- description: "Send a channel message and receive channel messages" + vars: + user_message: | + Send the text "gg" into the "lobby" channel from a UI button, and + log every message received in that channel to the console. + reference_content: file://../references/text-chat.md + assert: + - type: icontains + value: "VivoxService.Instance.SendChannelTextMessageAsync" + - type: icontains + value: "VivoxService.Instance.ChannelMessageReceived" + - type: icontains + value: "VivoxMessage" + - type: llm-rubric + value: | + The response must call VivoxService.Instance.SendChannelTextMessageAsync + with channelName "lobby" and message "gg", AND subscribe to + VivoxService.Instance.ChannelMessageReceived with a handler that + takes a VivoxMessage and logs at least MessageText and + SenderDisplayName. It must NOT wire the send path to the + DirectedMessageReceived event. + +- description: "Send a directed (direct) message — must use SendDirectTextMessageAsync, not SendDirected..." + vars: + user_message: | + Whisper "meet me at the north gate" to the player whose PlayerId + is "abc123". + reference_content: file://../references/text-chat.md + assert: + - type: icontains + value: "VivoxService.Instance.SendDirectTextMessageAsync" + - type: llm-rubric + value: | + The response must call VivoxService.Instance.SendDirectTextMessageAsync + with playerId "abc123" and the exact message + "meet me at the north gate". The method name must be + SendDirectTextMessageAsync (with "Direct", no "ed" before "Text"). + SendDirectedTextMessageAsync does not exist in the SDK and MUST + NOT be USED as callable code — but mentioning it in a "common + hallucination, don't use this" warning is fine and even helpful. + The response may also mention subscribing to the + DirectedMessageReceived event on the recipient side. + +- description: "Fetch the most recent channel chat history" + vars: + user_message: | + Fetch the last 25 messages from the "lobby" channel and print them + to the console in oldest-to-newest order. + reference_content: file://../references/text-chat.md + assert: + - type: icontains + value: "GetChannelTextMessageHistoryAsync" + - type: llm-rubric + value: | + The response must call + VivoxService.Instance.GetChannelTextMessageHistoryAsync with + channelName "lobby" and requestSize 25 (or equivalent). It must + note that the returned collection is newest-first and must + reverse the collection (or iterate in reverse) before printing to + achieve oldest-to-newest ordering. It should reference + VivoxMessage.SenderDisplayName and VivoxMessage.MessageText. diff --git a/skills/setup-vivox-voice-chat/evals/tests/voice-channels.yaml b/skills/setup-vivox-voice-chat/evals/tests/voice-channels.yaml new file mode 100644 index 0000000..4a8a311 --- /dev/null +++ b/skills/setup-vivox-voice-chat/evals/tests/voice-channels.yaml @@ -0,0 +1,74 @@ +# ============================================================ +# WORKFLOW: Voice channels — group, positional, join lifecycle +# ============================================================ + +- description: "Join a lobby group channel with voice and text" + vars: + user_message: | + The player is logged into Vivox. Have them join a non-positional + channel called "lobby" with both voice and text enabled. + reference_content: file://../references/voice-channels.md + assert: + - type: icontains + value: "VivoxService.Instance.JoinGroupChannelAsync" + - type: icontains + value: "ChatCapability.TextAndAudio" + - type: not-icontains + value: "JoinPositionalChannelAsync" + - type: not-icontains + value: "IChannelSession" + - type: llm-rubric + value: | + The response must call VivoxService.Instance.JoinGroupChannelAsync + with the channel name "lobby" and ChatCapability.TextAndAudio. It + must NOT use the positional or echo join methods, and must not + use any v4 IChannelSession API. + +- description: "Join a 3D positional channel with Channel3DProperties" + vars: + user_message: | + Set up proximity voice: players near each other in the world should + hear each other, and voices fall off with distance. Name the + channel "world-proximity". + reference_content: file://../references/voice-channels.md + assert: + - type: icontains + value: "Channel3DProperties" + - type: icontains + value: "Set3DPosition" + - type: not-icontains + value: "JoinGroupChannelAsync" + - type: llm-rubric + value: | + This is a planning eval: judge the plan's correctness, not whether + it names APIs literally or includes runnable code. The plan must: + (a) identify positional (3D) channels as the mechanism — mentioning + "positional channel", "3D channel", or JoinPositionalChannelAsync + all count, since positional channels have exactly one join method; + (b) use the channel name "world-proximity"; + (c) configure Channel3DProperties (naming at least the audible/ + conversational distance concept, and ideally the fade model); + (d) state that each player's 3D position needs a per-frame update + via Set3DPosition (or an equivalent per-frame transform sync) so + distance attenuation actually works; + (e) make clear that awaiting the join call is not sufficient — + ChannelJoined must be subscribed to first for the join to be + observable. + +- description: "Subscribe to ChannelJoined BEFORE calling JoinGroupChannelAsync" + vars: + user_message: | + When I call JoinGroupChannelAsync my UI never activates for the + newly joined channel. What am I doing wrong? + reference_content: file://../references/voice-channels.md + assert: + - type: icontains + value: "ChannelJoined" + - type: llm-rubric + value: | + The response must diagnose the problem as the ChannelJoined event + being subscribed AFTER the join call, and instruct the user to + subscribe to VivoxService.Instance.ChannelJoined BEFORE calling + JoinGroupChannelAsync. It must clearly state that awaiting the + JoinGroupChannelAsync call does NOT mean the join is complete — + the join completes when the ChannelJoined event fires.