From 898cedd5db6b937a1a41c4b6a8d6041686119388 Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 10:13:53 -0400
Subject: [PATCH 01/10] Create SKILL.md
---
skills/levelplay-unity-integration/SKILL.md | 1111 +++++++++++++++++++
1 file changed, 1111 insertions(+)
create mode 100644 skills/levelplay-unity-integration/SKILL.md
diff --git a/skills/levelplay-unity-integration/SKILL.md b/skills/levelplay-unity-integration/SKILL.md
new file mode 100644
index 0000000..2801e55
--- /dev/null
+++ b/skills/levelplay-unity-integration/SKILL.md
@@ -0,0 +1,1111 @@
+---
+name: levelplay-unity-integration
+description: Use when integrating LevelPlay (IronSource) ads into a Unity project — installing the Ads Mediation package, resolving native dependencies, initializing the SDK, or implementing rewarded ads, interstitials, or banners. Also use for LevelPlay-related errors, privacy compliance (GDPR, CCPA, COPPA), iOS setup (ATT, SKAdNetwork), or impression-level revenue tracking (ILRD).
+---
+
+# LevelPlay Unity package/SDK Integration
+
+Use Unity.RunCommand for editor-side checks and operations. The C# scripts generated in this skill are MonoBehaviour files for the user to save to their project, not for inline execution.
+
+Follow the integration workflow sequentially, one step at a time. Ask only the questions for the current step — do not gather information for future steps in advance. Wait for the user's response at each checkpoint before proceeding.
+
+LevelPlay is Unity's ad mediation platform: it connects your game to multiple ad networks simultaneously and automatically shows the highest-paying ad available. This guide walks you through the full integration: installing the SDK, configuring dependencies for Android and iOS, initializing LevelPlay in your project, and implementing rewarded, interstitial, and banner ads. If you already have part of this set up, you can skip ahead to the relevant step.
+
+## Integration Workflow
+
+### 1. Verify Unity Environment
+
+Check that the user is working in a Unity project by verifying Assets/ and ProjectSettings/ directories exist. If not in a Unity project, instruct the user to navigate to their Unity project directory. If those directories are not found but the user believes they are in the right place, ask: "It looks like you may not be at your project root — can you navigate to the top-level folder of your Unity project and confirm you can see Assets/ and ProjectSettings/ there?"
+
+### 2. Understand Business Goals
+
+Before implementing ad units, determine the user's optimization priorities to recommend the appropriate ad unit strategy. Ask:
+
+**"What's your primary optimization goal?"**
+- **Revenue-focused**: Maximize ad revenue and impression opportunities
+- **UX-focused**: Prioritize gameplay flow and user satisfaction
+- **Balanced**: Optimize for both revenue and UX
+- **Not sure yet**: Default to Balanced and proceed. At Step 8, briefly note you're using Balanced since they were unsure, and invite them to indicate a different preference now that they've seen the format options.
+
+Record this answer for later strategy recommendation in Step 8.
+
+### 3. Install LevelPlay SDK via UPM
+
+**If the SDK is already installed:** Ask the user to verify 'Ads Mediation' appears under 'Packages: In Project' in the Package Manager. If it does, proceed directly to Step 4.
+
+Guide through installing the LevelPlay Unity package using Unity Package Manager:
+
+1. Open Unity → Window > Package Manager
+2. Select Unity Registry dropdown or Services tab
+3. Search for "Ads Mediation" package
+4. Review package description to confirm it's the LevelPlay SDK
+5. Click Install button
+6. Wait for package to download and import
+
+When you install the package, you may see a prompt to install Mobile Dependency Resolver — click **Import** if it appears. This is covered in more detail in the next step.
+
+Verify the package appears under "Packages: In Project" in Package Manager after installation.
+
+**Network Manager:** Access **Ads Mediation > Network Manager** at any time to install additional ad network adapters and check for SDK and adapter updates.
+
+For iOS builds, note that SKAdNetwork configuration will be needed later (reference `references/ios-setup.md` when ready for iOS builds).
+
+### 4. Resolve Native Dependencies (Critical)
+
+**Critical for Android/iOS builds**: LevelPlay requires native dependency resolution. Without this, code compiles in Unity Editor but fails during platform builds with gradle (Android) or CocoaPods (iOS) errors.
+
+**Platform checkpoint — ask before proceeding:** "Which platform(s) are you targeting — iOS, Android, or both?" Record this. It determines which dependency resolution steps apply here, whether ATT is required (Step 6.5), and which testing steps are relevant (Step 10).
+
+LevelPlay requires native Android/iOS libraries that Unity's package manager alone doesn't handle. A dependency manager bridges this gap. If you've never added one to your project, don't worry; we'll walk you through it.
+
+**Check for existing dependency manager:**
+
+Ask: "Do you have a dependency manager like Mobile Dependency Resolver (MDR), Unity External Dependency Manager (UEDM), or External Dependency Manager for Unity (EDM4U) installed? Check your Assets folder for these tools."
+
+**If unsure**: Check Assets folder in Unity for folders named 'Mobile Dependency Resolver', 'External Dependency Manager', or 'EDM4U'. If you see any folder with these names, answer 'Yes'. If not, answer 'No'.
+
+**If they HAVE a dependency manager:**
+
+**For Android:**
+- With newer versions of Mobile Dependency Resolver (shipped with the Ads Mediation package): Dependencies auto-resolve on build (no manual action needed). If unsure, try building first — if it fails, manually resolve via the Android Resolver menu.
+- With older MDR or other managers: Go to Assets > [Your Dependency Manager] > Android Resolver > Resolve
+- Example paths:
+ - MDR: `Assets > Mobile Dependency Resolver > Android Resolver > Resolve`
+ - EDM4U: `Assets > External Dependency Manager > Android Resolver > Resolve`
+- Menu paths may vary depending on dependency manager version. Look for 'Android Resolver' under your dependency manager's menu.
+
+**For iOS:**
+- All dependency managers require manual CocoaPods installation:
+ - MDR: `Assets > Mobile Dependency Resolver > iOS Resolver > Install Cocoapods`
+ - EDM4U: `Assets > External Dependency Manager > iOS Resolver > Install Cocoapods`
+ - UEDM: Similar path under Unity External Dependency Manager
+
+Ask: "Have you run the dependency resolution for your target platform(s)?"
+
+**If targeting both Android and iOS**, complete dependency resolution for both platforms before proceeding.
+
+**If they DON'T have a dependency manager:**
+
+If the user didn't see the Mobile Dependency Resolver prompt during installation (see Step 3), restart Unity Editor — the prompt may appear after restart. If it still doesn't appear, they may already have a dependency manager installed.
+
+When prompted:
+1. Click **Import** on the prompt to install Mobile Dependency Resolver
+2. After installation:
+ - **Android**: Dependencies will auto-resolve on build (newer MDR versions)
+ - **iOS**: Go to Assets > Mobile Dependency Resolver > iOS Resolver > Install Cocoapods
+
+**Alternative**: Install another dependency manager like EDM4U if preferred (search for installation instructions in their documentation).
+
+**Note**: Unity is transitioning to Unity External Dependency Manager (UEDM). If available in your Unity version, prefer UEDM over MDR.
+
+**Verification:**
+
+After resolution, verify:
+- **Android**: Gradle dependencies in `Assets/Plugins/Android/`
+- **iOS**: Podfile references or CocoaPods installation confirmation in console
+
+**If dependency resolution fails**: Check the Unity console for specific error messages and share them for troubleshooting — or see the **Common Issues and Solutions** section below for gradle and CocoaPods error guidance.
+
+**Android Custom Main Gradle Template (Older LevelPlay Versions):**
+
+For older LevelPlay Unity package versions, manually enable the Custom Main Gradle Template:
+1. Go to Edit > Project Settings > Player
+2. Select Android tab
+3. Expand Publishing Settings
+4. Under Build, check Custom Main Gradle Template
+
+In newer LevelPlay Unity package versions (with newer Mobile Dependency Resolver), this is enabled automatically by default.
+
+**Android API 33+ Requirement:**
+
+If targeting Android API level 33 or higher, declare the AD_ID permission in AndroidManifest.xml:
+
+```xml
+
+```
+
+This permission is required for advertising ID access on Android 13+.
+
+**If you skip this step and target Android API 33+:** advertising ID access will fail on Android 13+ devices.
+
+### 5. Get App Key and Ad Unit IDs
+
+Before initializing LevelPlay, collect credentials from the LevelPlay dashboard.
+
+**Dashboard:** https://platform.ironsrc.com/
+
+**New to LevelPlay?** Set up your app and ad units first:
+- [Add your app](https://docs.unity.com/en-us/grow/levelplay/platform/get-started/add-app)
+- [Create ad units](https://docs.unity.com/en-us/grow/levelplay/platform/get-started/ad-units)
+
+**App Key:** In the dashboard, go to **Apps** in the left sidenav → find your app → copy the alphanumeric string displayed under the app title.
+
+**Ad Unit IDs:** Go to **Ad units** in the left sidenav → select your app → copy the ID for each format you plan to implement (Rewarded, Interstitial, Banner).
+
+**Note:** You need your App Key now for initialization (Step 7). Ad Unit IDs are only needed at Step 9 — if you haven't decided which ad formats to implement yet, just copy your App Key for now and return here after Step 8.
+
+Keep both accessible — you'll need them in the next steps.
+
+### 6. Configure AdMob Keys (If Using AdMob Network)
+
+**When to use**: Only if using AdMob as a mediation network adapter in LevelPlay.
+
+If using AdMob, configure platform-specific app keys in Unity Editor:
+
+**Access**: Ads Mediation > Developer Settings > LevelPlay Mediation Settings
+
+**Configuration:**
+- **Android App Key**: AdMob Android app key
+- **iOS App Key**: AdMob iOS app key
+
+This configuration is required for AdMob to work as a mediation network in LevelPlay.
+
+**Troubleshooting**: If you don't see the 'Ads Mediation' menu in Unity Editor, verify the Ads Mediation package is installed (Step 3) and restart Unity Editor.
+
+### 6.5. Privacy & Regulation Settings (If Required)
+
+**When to use**: If your app serves users in the EU (GDPR), California (CCPA), or is directed at children (COPPA).
+
+Ask the user: "Does your app need to comply with any privacy regulations?"
+- **GDPR** (EU users): Requires user consent for data collection
+- **CCPA** (California users): Requires "Do Not Sell" opt-out option
+- **COPPA** (Child-directed apps): Restricts data collection for children under 13
+
+If you're unsure whether your app reaches EU users, implement GDPR handling as a precaution — it's better to request consent you don't strictly need than to miss it where you do.
+
+**If YES to any:**
+
+Privacy settings must be configured **BEFORE** SDK initialization. See `references/privacy-settings.md` for complete implementation guide.
+
+**Quick examples:**
+
+**GDPR:**
+
+**SDK 9.5.0+** — global consent boolean:
+```csharp
+using Unity.Services.LevelPlay;
+
+// true = user has granted consent, false = user has not consented
+LevelPlayPrivacySettings.SetGDPRConsent(true);
+```
+
+**SDK 9.4.x** — per-network consent dictionary (check your SDK version in Network Manager):
+```csharp
+using Unity.Services.LevelPlay;
+using System.Collections.Generic;
+
+// Add an entry for each ad network you have installed
+LevelPlayPrivacySettings.SetGDPRConsents(new Dictionary {
+ { "UnityAds", true },
+ { "IronSource", true }
+ // See references/privacy-settings.md for the full network key list
+});
+```
+
+If neither API compiles, your Unity package/SDK may be below 9.4.0 — upgrade via **Ads Mediation > Network Manager**.
+
+**CCPA (SDK 9.4.0+):**
+```csharp
+using Unity.Services.LevelPlay;
+
+LevelPlayPrivacySettings.SetCCPA(true); // User opted out of data sale
+```
+
+**COPPA (SDK 9.4.0+):**
+```csharp
+using Unity.Services.LevelPlay;
+
+LevelPlayPrivacySettings.SetCOPPA(true); // Child-directed app
+```
+
+The CCPA and COPPA APIs above also require SDK 9.4.0+. If either fails to compile, upgrade your Unity package/SDK via **Ads Mediation > Network Manager**.
+
+Call these BEFORE `LevelPlay.Init()` in Step 7.
+
+For complete implementation with UI, consent management, and combined regulations, see `references/privacy-settings.md`.
+
+**For iOS builds — required regardless of privacy regulations above:** Also implement App Tracking Transparency (ATT) before proceeding to Step 7. ATT must be called before `LevelPlay.Init()` to maximize ad fill rate on iOS 14+. See `references/ios-setup.md` for the ATT implementation code.
+
+**If NO privacy regulations and not targeting iOS:** Skip this step and proceed to Step 7.
+
+### 7. Initialize LevelPlay SDK
+
+**Installation checkpoint:**
+
+Before providing initialization code, confirm:
+
+**If the user confirmed they are not using AdMob, omit the Step 6 item from the confirmation below.**
+
+If following the sequential workflow and Steps 3 and 4 have already been confirmed in this conversation, skip those items — only ask about Step 5 and Step 6 (if AdMob).
+
+"Please confirm these are working correctly:
+- Step 3: Do you see the 'Ads Mediation' package in Unity Package Manager under 'Packages: In Project'?
+- Step 4: Have you run dependency resolution for your target platform(s) without errors?
+- Step 5: Do you have your App Key copied from the LevelPlay dashboard?
+- Step 6 (only if using AdMob): Have you configured AdMob keys in Unity Editor settings?
+
+Verify these are working before proceeding."
+
+**If they answer NO or are unsure:**
+- Missing Step 3: Code will show `CS0246` namespace errors → Direct to Step 3
+- Missing Step 4: Code compiles but Android/iOS builds will fail → Direct to Step 4
+- Missing Step 5: They won't have credentials to initialize → Direct to Step 5
+- Do not provide C# code until they confirm all steps are complete
+
+**If they answer YES:**
+- **Optional — Analytics: ILRD Wiring.** Ask this question verbatim — do not summarize or rephrase it: "Do you use an analytics or attribution platform (Firebase, AppsFlyer, Adjust, Singular, or custom backend) that needs ad revenue data? If yes, the init script will include a logging stub for Impression Level Revenue (ILRD) — 3 lines of code, no analytics platform setup required yet. (Yes / No / Not sure — defaults to yes)" Record the answer.
+- Proceed with initialization code.
+
+LevelPlay SDK must be initialized before loading or showing any ads. Initialization should happen early in the application lifecycle.
+
+**Ask how they want to handle initialization. Present all four options exactly as listed — do not condense or omit any:**
+1. Create a new dedicated script for LevelPlay initialization
+2. Add to an existing initialization/manager script they already have
+3. Create a new LevelPlay script that your existing manager calls
+4. Just show me the initialization code — I'll decide how to integrate it
+
+#### Option 1: Creating a New Script
+
+If creating a new script (e.g., `LevelPlayInitializer.cs`), use this complete class:
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class LevelPlayInitializer : MonoBehaviour
+{
+ [SerializeField] private string appKey;
+
+ void Awake()
+ {
+ // Persist across scene loads so ads stay initialized
+ DontDestroyOnLoad(gameObject);
+ }
+
+ void Start()
+ {
+ // Register initialization callbacks
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+
+ // Initialize the SDK with your App Key
+ LevelPlay.Init(appKey);
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay SDK initialized successfully");
+ // SDK is now ready to load ads
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
+ }
+
+ void OnDestroy()
+ {
+ LevelPlay.OnInitSuccess -= OnInitSuccess;
+ LevelPlay.OnInitFailed -= OnInitFailed;
+ }
+}
+```
+
+**File location**: Save as `Assets/Scripts/LevelPlayInitializer.cs` (or `Assets/Scripts/Ads/LevelPlayInitializer.cs` if you have an Ads subfolder)
+
+**After creating:**
+1. Attach script to a GameObject in your first scene
+2. In Unity Inspector, find the "App Key" field
+3. Paste your App Key from Step 5 into that field
+
+**If the user answered Yes or Not Sure to ILRD in Step 7**, also subscribe before Init. Add these lines inside `Start()` (before `LevelPlay.Init(appKey)`):
+
+```csharp
+// MUST be registered BEFORE LevelPlay.Init() to avoid losing early impressions.
+// Callback fires on a BACKGROUND thread — see references/ilrd-api.md for
+// thread-safe forwarding patterns and a Firebase example.
+LevelPlay.OnImpressionDataReady += OnImpressionDataReady;
+```
+
+Add this stub method to the class:
+
+```csharp
+private void OnImpressionDataReady(LevelPlayImpressionData impressionData)
+{
+ // See references/ilrd-api.md for full implementation.
+ // For now, just log so you can verify it fires:
+ Debug.Log($"ILRD: {impressionData.AdNetwork} / {impressionData.AdFormat} / ${impressionData.Revenue}");
+}
+```
+
+And unsubscribe in `OnDestroy()`:
+
+```csharp
+LevelPlay.OnImpressionDataReady -= OnImpressionDataReady;
+```
+
+**Next:** Once you confirm the log fires after your first ad impression, read `references/ilrd-api.md` to wire it up to your actual analytics platform. Note: ILRD callbacks do not fire with mock ads in the Unity Editor — you'll need a device build to verify this log fires (see Step 10).
+
+#### Option 2: Adding to Existing Script
+
+If adding to an existing script (e.g., `GameManager.cs`):
+
+**1. Add namespace at top of file:**
+```csharp
+using Unity.Services.LevelPlay;
+```
+
+**2. In existing Start() or Awake() method, add initialization:**
+```csharp
+void Start()
+{
+ // Register initialization callbacks
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+
+ // Initialize the SDK - REPLACE with your actual App Key from Step 5
+ LevelPlay.Init("YOUR_APP_KEY_HERE");
+
+ // ... your other existing Start() code
+}
+```
+
+**3. Add callback methods to class:**
+```csharp
+private void OnInitSuccess(LevelPlayConfiguration config)
+{
+ Debug.Log("LevelPlay SDK initialized successfully");
+ // SDK is ready - you can now create ad objects
+}
+
+private void OnInitFailed(LevelPlayInitError error)
+{
+ Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
+}
+```
+
+**4. In existing OnDestroy() (or create if it doesn't exist), add:**
+```csharp
+void OnDestroy()
+{
+ // Unregister callbacks
+ LevelPlay.OnInitSuccess -= OnInitSuccess;
+ LevelPlay.OnInitFailed -= OnInitFailed;
+}
+```
+
+**If the user answered Yes or Not Sure to ILRD in Step 7**, add the same ILRD subscription code shown in Option 1 above: subscribe before `LevelPlay.Init(...)`, add the stub handler method, and unsubscribe in `OnDestroy()`.
+
+Replace `"YOUR_APP_KEY_HERE"` with your actual App Key from Step 5 (the alphanumeric string copied from the LevelPlay dashboard).
+
+**Note:** If your existing script doesn't already persist across scenes, add `DontDestroyOnLoad(gameObject);` to its `Awake()` method to prevent re-initialization when loading new scenes.
+
+Before testing, double-check that you've replaced the placeholder with your actual App Key. The App Key should be an alphanumeric string, NOT 'YOUR_APP_KEY_HERE'.
+
+#### Option 3: Separate Script Called by Manager
+
+If you want to keep ads code in its own script but have your existing manager control initialization:
+
+**1. Create `LevelPlayInitializer.cs` using the complete class code from Option 1**
+
+**2. In existing manager script (e.g., `GameManager.cs`), add:**
+```csharp
+using UnityEngine;
+
+public class GameManager : MonoBehaviour
+{
+ void Awake()
+ {
+ // Add LevelPlay initialization as a component
+ gameObject.AddComponent();
+
+ // ... your other initialization code
+ }
+}
+```
+
+This keeps ad code isolated in `LevelPlayInitializer.cs` while `GameManager` controls when it initializes.
+
+**Note:** `LevelPlayInitializer.Awake()` calls `DontDestroyOnLoad(gameObject)`, which will mark this entire GameObject — including your GameManager and any other components on it — as persistent across scenes. Ensure this is compatible with your scene management strategy.
+
+**If the user answered Yes or Not Sure to ILRD in Step 7**, add the ILRD subscription to `LevelPlayInitializer.cs` as shown in Option 1 above. No changes needed in `GameManager`.
+
+Before testing, ensure you've entered your actual App Key in the Unity Inspector's App Key field on the LevelPlayInitializer component (see Option 1's After Creating steps above).
+
+#### Option 4: Just Show Me the Code
+
+Provide the complete initialization code from Option 1 as a standalone snippet, without instructions for attaching to a GameObject or setting up the Inspector. Include a note: "This is the full initialization class — save it as `LevelPlayInitializer.cs`, attach it to a persistent GameObject in your first scene, and set the App Key field in the Inspector."
+
+**Key points:**
+- Always register `OnInitSuccess` and `OnInitFailed` callbacks before calling `Init()`
+- Call initialization early in app lifecycle (in `Awake()` or `Start()` of first scene)
+- Only initialize once. If initialization script is in first scene and you have multiple scenes, add `DontDestroyOnLoad(gameObject);` in `Awake()` to prevent re-initialization when loading new scenes
+- Wait for `OnInitSuccess` before creating ad objects
+
+If initialization fails repeatedly, see the Common Issues section for troubleshooting. Common causes include incorrect App Key, no internet connection, or missing package dependencies.
+
+For more advanced initialization options (user ID, consent management, etc.), see `references/initialization-api.md`.
+
+### 8. Recommend Ad Unit Strategy
+
+Based on the optimization goal identified in Step 2, recommend an ad unit strategy.
+
+**Recall the user's optimization goal from Step 2.** If the conversation has been long or the answer is unclear, confirm: "Earlier you mentioned your optimization goal. To confirm, are you primarily focused on revenue, user experience, or a balance of both?"
+
+**Map the answer to the appropriate strategy:**
+- User focused on **maximizing revenue, impressions, or monetization** → Use **Revenue-Focused Strategy**
+- User focused on **user experience, retention, or avoiding annoyance** → Use **UX-Focused Strategy**
+- User wants to **balance both** or mentioned **both revenue and UX** → Use **Balanced Strategy**
+- User answered **"Not sure yet"** in Step 2 → Use **Balanced Strategy**. After presenting the recommendation, add: "Since you weren't sure of your goal earlier, I've gone with the Balanced approach — if you'd prefer to lean more toward revenue or user experience now that you've seen the options, just say so."
+- If unclear, ask: "Would you prioritize revenue, user experience, or a balance of both?"
+
+#### Revenue-Focused Strategy
+
+**Goal**: Maximize ad revenue and impression opportunities
+
+**Recommended ad units:**
+- **Rewarded ads**: Primary monetization driver
+ - Implement in multiple high-value moments
+ - Use for premium rewards, extra lives, bonus content
+ - Load ads proactively to ensure availability
+
+- **Interstitial ads**: Secondary revenue source
+ - Show at natural transition points (level complete, game over)
+ - Frequency cap: Every 3-5 minutes of gameplay (see `references/interstitial-api.md`)
+
+- **Banner ads**: Persistent revenue during gameplay
+ - Show during core gameplay loops
+ - Use smart positioning to avoid UI conflicts
+
+**Revenue lever: bid floor (optional)**
+
+Once ads are live, bid floors let you set a minimum bid price per ad unit — raising your average eCPM at the cost of lower fill rate. You'll be prompted in Step 9 to configure this or skip and add it later.
+
+**Implementation priority**: Rewarded → Interstitial → Banner
+
+The next step will ask you to choose which ad formats to implement from this priority list.
+
+#### UX-Focused Strategy
+
+**Goal**: Maintain excellent user experience while monetizing thoughtfully
+
+**Recommended ad units:**
+- **Rewarded ads**: Primary and often only ad format
+ - User-initiated only (explicit opt-in)
+ - High-value rewards that feel generous
+ - No forced ads, ever
+
+- **Interstitial ads**: Optional, sparingly used
+ - Only at major session boundaries (e.g., exiting to main menu)
+ - Never interrupt active gameplay
+
+- **Banner ads**: Generally avoided or minimized
+ - If used, only during menu screens, not gameplay
+ - Small, non-intrusive sizes
+
+**Implementation priority**: Rewarded only, or Rewarded → (optional) Interstitial
+
+The next step will ask you to choose which ad formats to implement from this priority list.
+
+#### Balanced Strategy
+
+**Goal**: Optimize both revenue and user satisfaction
+
+**Recommended ad units:**
+- **Rewarded ads**: Core monetization
+ - 2-3 strategic placements in high-engagement moments
+ - Rewards feel valuable but not exploitative
+
+- **Interstitial ads**: Moderate usage
+ - At natural breakpoints (level transitions, session ends)
+ - Frequency cap: Every 5-7 minutes (see `references/interstitial-api.md`)
+
+- **Banner ads**: Selective placement
+ - Show in menus or low-attention moments
+ - Hide during intense gameplay
+
+**Implementation priority**: Rewarded → Interstitial → Banner (selective)
+
+The next step will ask you to choose which ad formats to implement from this priority list. If the user wants to implement in a different order than recommended, accommodate that preference.
+
+### 9. Implement Ad Units
+
+**Important: Read Best Practices First**
+
+Before implementing ad code, read the **Best Practices** section below (after Step 10). It contains essential patterns for loading strategy, error handling, memory management, and placement strategy. These patterns should be incorporated into all ad implementations.
+
+**Implementation checkpoint:**
+
+Before implementing ad units, confirm:
+
+"Before providing ad implementation code, please confirm:
+- Did you complete SDK initialization in Step 7?
+- Did you receive the 'LevelPlay SDK initialized successfully' log message in your Unity console?
+
+Verify initialization is working before proceeding with ad units."
+
+**If they answer NO or are unsure:**
+- Direct back to Step 7 to complete initialization first
+- Do not provide ad implementation code until initialization is confirmed working
+
+**If they answer YES**, proceed with ad implementation.
+
+**Ad format checkpoint — ask before generating any code:** "Which ad formats do you want to implement? Rewarded, Interstitial, Banner, or a combination?" Only implement the formats the user selects. They can add more formats later using the 'Adding More Ad Formats Later' section.
+
+Based on the chosen strategy, implement the appropriate ad formats.
+
+**Implementation flexibility**: Implement formats one at a time or all at once. Choose which formats you want to implement now, and you can always add more formats later (see 'Adding More Ad Formats Later' section below).
+
+**First, ask the user how they want to organize the ad code. Do not generate any code until they have answered:**
+
+"How would you like to structure your ad implementation?"
+
+1. **Separate manager scripts for each ad format** - Create individual scripts like `RewardedAdManager.cs`, `InterstitialAdManager.cs`, `BannerAdManager.cs` (good for larger projects, clear separation of concerns)
+
+2. **One unified AdManager script** - Create a single `AdManager.cs` that handles all ad formats (simpler, everything in one place)
+
+3. **Just show me the code snippets** - Provide implementation code without wrapping it in specific files, so you can integrate it however you prefer
+
+4. **I already have ad manager code** - Review and help fix/update existing implementation
+
+Based on their answer, adapt your response accordingly.
+
+**Then present the optional bid floor feature (skip for Option 4 — review existing code instead):**
+
+Present bid floor ranges only for the formats the user is implementing in this session. Reference starting ranges: Rewarded: $0.50–$2.00 | Interstitial: $0.20–$1.00 | Banner: $0.05–$0.20. Include only the ranges for formats being implemented.
+
+"**Optional — Advanced: Bid Floors**
+
+Most publishers skip this initially and add it once they have real dashboard data. You can safely skip now and return to it later.
+
+If you'd like to set bid floors now: a bid floor sets a minimum bid price (USD) per ad unit — it raises your average eCPM at the cost of lower fill rate. Starting ranges:
+[ranges for formats being implemented]
+
+Reply with values per format, or just say 'skip' — you can add them any time."
+
+**Record the answer per format.** Wire `Config.Builder().SetBidFloor(...)` into the ad construction for any format where a value was provided. Formats marked 'skip' use the basic constructor.
+
+**If they choose Option 4 (existing code):**
+- Ask: "Please share your existing ad manager code for review"
+- Wait for them to provide the code
+- Analyze the existing implementation:
+ - Check if they're using current LevelPlay Ad Unit API (LevelPlayRewardedAd, LevelPlayInterstitialAd, LevelPlayBannerAd)
+ - Identify if they're using deprecated IronSource.Agent APIs
+ - Check for proper callback registration/unsubscription
+ - Look for missing error handling or memory leaks
+- Provide specific guidance:
+ - If using deprecated APIs: "You're using the old IronSource.Agent API. Here's how to migrate to the new LevelPlay Ad Unit API:"
+ - If using current APIs with issues: "Your implementation looks good but I noticed [specific issues]. Here's how to fix them:"
+ - If implementation is correct: "Your implementation looks solid. Which additional ad formats would you like to add?"
+- Offer to either:
+ - Provide fixes as code snippets to integrate into existing files
+ - Suggest refactoring if the code has structural issues
+- When adding new formats after reviewing existing code, present the bid floor prompt scoped to those new formats only, confirm whether to match their existing code organization pattern or use a new one, then follow the same implementation guidelines as Options 1–3 above.
+
+For each ad format, follow the implementation guidelines in detailed references:
+
+- **Rewarded ads**: See `references/rewarded-api.md`
+- **Interstitial ads**: See `references/interstitial-api.md`
+- **Banner ads**: See `references/banner-api.md`
+
+#### General Implementation Pattern
+
+All ad formats follow a similar lifecycle:
+
+1. **Load**: Request an ad from LevelPlay
+2. **Listen**: Register callbacks for ad events (loaded, failed, shown, clicked, closed)
+3. **Check readiness**: Verify ad is ready before showing
+4. **Show**: Display the ad to the user
+5. **Handle callbacks**: Respond to user interactions and ad lifecycle events
+
+#### Code Generation Guidelines
+
+**If the user mentions they have existing code**, ask to see it before providing implementation guidance. This allows you to provide targeted fixes rather than generating new code from scratch.
+
+Adapt output based on the user's chosen organization approach:
+
+**Option 1: Separate manager scripts**
+- Create complete, production-ready `.cs` files for each ad format
+- Name them clearly: `RewardedAdManager.cs`, `InterstitialAdManager.cs`, `BannerAdManager.cs`
+- Include full class structure with proper namespaces
+- Each manager handles one ad format completely
+
+**Option 2: Unified AdManager**
+- Create a single `AdManager.cs` file
+- Include methods and callbacks for all requested ad formats in one class
+- Use clear method naming to distinguish between formats (e.g., `LoadRewardedAd()`, `LoadInterstitial()`)
+- Keep code organized with regions or comments separating each ad format
+
+**Option 3: Code snippets only**
+- Provide focused code blocks without full class wrappers
+- Clearly label each snippet (e.g., "Rewarded Ad Initialization", "Interstitial Event Callbacks")
+- Explain where/how to integrate each snippet
+- Note any dependencies between snippets
+
+**Regardless of chosen approach, always include:**
+- All manager classes must inherit from `MonoBehaviour` — required for Unity lifecycle methods (`Start()`, `OnDestroy()`) and to attach the script to a GameObject
+- After generating any manager script, instruct the user to attach it to a persistent GameObject in their scene (the same one as the initializer, with `DontDestroyOnLoad`)
+- For banner and interstitial managers, call `DestroyAd()` in `OnDestroy()` so ads are destroyed and memory is freed when the manager is destroyed
+- Proper event subscription and unsubscription (to avoid memory leaks)
+- Null checks and defensive programming
+- Debug logs for troubleshooting
+- Clear variable names that match LevelPlay conventions
+- Error handling and graceful degradation
+
+**Bid floor handling (per-format):**
+- If the user provided a bid floor value for a format, wrap construction in `Config.Builder().SetBidFloor(value).Build()` and pass the config to the constructor.
+- If the user said 'skip' for that format, use the basic constructor (`new LevelPlayRewardedAd(adUnitId)`).
+- Apply per-format: a publisher may set a floor on rewarded but skip banner.
+
+Example (with bid floor):
+```csharp
+var config = new LevelPlayRewardedAd.Config.Builder()
+ .SetBidFloor(0.80)
+ .Build();
+rewardedAd = new LevelPlayRewardedAd(adUnitId, config);
+```
+
+Example (skipped):
+```csharp
+rewardedAd = new LevelPlayRewardedAd(adUnitId);
+```
+
+**Impression Level Revenue Tracking:** If the user answered Yes or Not Sure to ILRD in Step 7, the ILRD callback was wired up in the init script. See `references/ilrd-api.md` to forward the data to the analytics platform (Firebase, AppsFlyer, Adjust, Singular, or custom backend).
+
+If the user said "No" to ILRD and wants to add it now, see `references/ilrd-api.md` — subscribe to `LevelPlay.OnImpressionDataReady` **before** the existing `LevelPlay.Init()` call.
+
+### 10. Testing and Validation
+
+LevelPlay provides two validation approaches for different stages of development:
+
+#### Early Development: Mock Ads in Unity Editor
+
+For early iteration and callback testing, use **mock ads** in Unity Editor. Unity automatically provides mock ads when you press Play in the Editor - no special configuration needed.
+
+**How it works:**
+
+When you press Play in Unity Editor, Unity automatically provides mock ads - no special configuration needed. Mock ads work with ANY App Key and Ad Unit ID values (including dummy values like "test" or "editor"). However, **recommend using real App Key and real Ad Unit IDs** from the LevelPlay dashboard so you don't forget to update them before building to device.
+
+**Example setup (works in both Editor and device builds):**
+
+The same initialization code you wrote in Step 7 works in both Editor (with mock ads) and device builds (with real ads). For example:
+
+```csharp
+// Initialize with your actual App Key (replace "abc123..." with yours)
+// Mock ads work with any value, but use your real key to avoid forgetting to update it later
+LevelPlay.Init("abc123youractualappkey");
+```
+
+Example for rewarded ads (works in both Editor and device):
+```csharp
+// In OnInitSuccess callback:
+// Use your real ad unit ID from LevelPlay dashboard (replace "12345..." with yours)
+// Mock ads work with any value, but use your real IDs to avoid forgetting to update them later
+LevelPlayRewardedAd rewardedAd = new LevelPlayRewardedAd("12345youractualadunitid");
+rewardedAd.OnAdLoaded += OnAdLoaded;
+rewardedAd.OnAdRewarded += OnAdRewarded;
+rewardedAd.LoadAd();
+```
+
+**Key points:**
+- **Mock ads work with any App Key/Ad Unit ID values** (you can even use "test" or "editor")
+- **Recommended: Use your real credentials** from Step 5 to avoid forgetting to update them later
+- **Mock ads appear automatically** when testing in Unity Editor
+- **Real ads appear automatically** when building to device
+- **Same code works everywhere** - no switching or conditional compilation needed
+- **Android API 33+**: If targeting Android 13+ devices, verify you've added the AD_ID permission to AndroidManifest.xml (see Step 4)
+
+**What mock ads validate:**
+- Ad integration flow works correctly
+- Most callbacks fire as expected (see callback behavior below)
+- Ad loading, showing, and closing logic
+- Ad positioning and layout (for banners)
+- Basic ad logic and state management
+
+**Mock ad callback behavior:**
+
+Mock ads in Unity Editor fire most callbacks, but not all:
+
+**Callbacks that FIRE:**
+- `OnAdLoaded` - Always fires after LoadAd()
+- `OnAdDisplayed` - Fires when ShowAd() is called
+- `OnAdRewarded` - Fires for rewarded ads (with test reward)
+- `OnAdClosed` - Fires when mock ad is dismissed
+
+**Callbacks that DON'T fire:**
+- `OnAdLoadFailed` - Mock ads always succeed loading
+- `OnAdDisplayFailed` - Mock ads always succeed showing
+- `OnAdClicked` - Mock ads don't simulate user clicks
+- `OnAdExpanded` / `OnAdCollapsed` - Banner expand/collapse not simulated
+- `OnAdLeftApplication` - No real ad redirect in Editor
+- `OnAdInfoChanged` - Mock ads don't update ad info dynamically
+- `LevelPlay.OnImpressionDataReady` (ILRD) - No impression data generated in Editor
+
+This means you can test your happy-path flow in Editor, but must test error handling on real devices.
+
+**Note on SDK initialization in the Editor:** `LevelPlay.OnInitSuccess` may not fire in all SDK configurations when running in the Unity Editor. If your initialization callback doesn't trigger and your ad objects never load as a result, try creating them directly in `Start()` after calling `LevelPlay.Init()` rather than waiting for the callback — mock ads will appear even without `OnInitSuccess` firing.
+
+**Mock ads limitations:**
+- Don't simulate network latency or failures
+- Don't test real ad network behavior
+- Don't validate reward logic server-side
+- Placeholder UI instead of real ad creatives
+- Error callbacks never fire
+
+**Best for**: Early development, rapid iteration on ad logic, callback testing
+
+#### Integration Validation: LevelPlay Test Suite (Recommended)
+
+The **Test Suite** is the primary method for comprehensive validation. It tests your integration with real ad networks on device.
+
+**What Test Suite validates:**
+- All ad formats (Rewarded, Interstitial, Banner) with real ads
+- SDK initialization with production App Key
+- All callbacks fire correctly in production environment
+- Real ad network behavior, latency, and edge cases
+- Ad rendering and user interaction flows
+
+**Before running the Test Suite:**
+- **Unity Ads is pre-installed** — the Ads Mediation package includes the Unity Ads adapter by default, so you have at least one network ready without any additional setup. For ads to fill on device, verify your LevelPlay dashboard has active instances configured for your ad units.
+- **Enable Development Build** in **Build Profiles** (called **Build Settings** in Unity versions before Unity 6) before building to device. Without it, SDK console output won't be visible, making it very difficult to diagnose issues if something doesn't work as expected.
+
+**Setup (requires device build):**
+
+Add these two lines to your existing `LevelPlayInitializer.cs` — do not create a new file or replace your existing initializer:
+
+1. At the top of `Start()`, before `LevelPlay.Init(appKey)`:
+```csharp
+LevelPlay.SetMetaData("is_test_suite", "enable");
+```
+
+2. Inside your `OnInitSuccess` callback:
+```csharp
+LevelPlay.LaunchTestSuite();
+```
+
+**Important**: Remove both lines before your production release. Test Suite should only be used during development and testing.
+
+**Don't have a `LevelPlayInitializer.cs` yet?** Use this complete template:
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class LevelPlayInitializer : MonoBehaviour
+{
+ [SerializeField] private string appKey;
+
+ void Awake()
+ {
+ DontDestroyOnLoad(gameObject);
+ }
+
+ void Start()
+ {
+ // Enable Test Suite — REMOVE before production release
+ LevelPlay.SetMetaData("is_test_suite", "enable");
+
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+ LevelPlay.Init(appKey);
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay initialized successfully");
+ // Launch Test Suite — REMOVE before production release
+ LevelPlay.LaunchTestSuite();
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
+ }
+
+ void OnDestroy()
+ {
+ LevelPlay.OnInitSuccess -= OnInitSuccess;
+ LevelPlay.OnInitFailed -= OnInitFailed;
+ }
+}
+```
+
+**After creating this script:**
+1. Attach it to a GameObject in your first scene
+2. In the Unity Inspector, find the "App Key" field
+3. Paste your App Key from Step 5 into that field
+
+**Key points:**
+- `LevelPlay.SetMetaData("is_test_suite", "enable");` must be called **BEFORE** `LevelPlay.Init()` — if the Test Suite doesn't appear after launch, this is the most likely cause
+- `LevelPlay.LaunchTestSuite();` is called **AFTER** successful initialization (in `OnInitSuccess`)
+- **Requires device build** - Test Suite does not work in Unity Editor
+- Use production App Key, not "editor"
+- Build to Android or iOS device and run the app
+- Test Suite UI will appear automatically after initialization
+
+**Testing workflow:**
+1. Add Test Suite code (SetMetaData before Init, LaunchTestSuite after OnInitSuccess)
+2. Build to Android or iOS device
+3. Run the app on device
+4. Test Suite UI launches automatically
+5. Follow on-screen instructions to test each ad format
+6. Verify all ad formats load and callbacks fire correctly
+
+**Best for**: Integration validation before production, final testing with real ad networks
+
+#### Production Release Checklist
+
+Before releasing to production:
+
+- [ ] Test Suite validation completed successfully on device
+- [ ] All ad formats load correctly (Rewarded, Interstitial, Banner if implemented)
+- [ ] All callbacks fire as expected
+- [ ] App Key and ad unit IDs verified correct for production
+- [ ] Tested on multiple devices (different screen sizes, OS versions)
+- [ ] **iOS-specific requirements completed** (if targeting iOS):
+ - [ ] SKAdNetwork IDs configured in Info.plist (see `references/ios-setup.md`)
+ - [ ] App Tracking Transparency (ATT) framework implemented (see `references/ios-setup.md`)
+ - [ ] iOS privacy manifest configured if required
+ - [ ] Tested on physical iOS device (not just simulator)
+- [ ] **Android-specific requirements completed** (if targeting Android):
+ - [ ] Google Play Services dependencies resolved (Step 4 completed)
+ - [ ] AD_ID permission added to AndroidManifest.xml if targeting API 33+ (see Step 4)
+ - [ ] Tested on physical Android device
+- [ ] Tested with real ads in production environment
+- [ ] Ad frequency capping implemented (if using interstitials)
+- [ ] Error handling works correctly (test with airplane mode - ads should fail gracefully without crashing or blocking gameplay)
+
+## Adding More Ad Formats Later
+
+If you've already integrated some ad formats and want to add more:
+
+1. **Skip to Step 9** - You don't need to repeat the initial setup steps. Before proceeding, verify your existing initialization still works by checking the Unity console for the 'LevelPlay SDK initialized successfully' log.
+2. **Choose the additional formats** you want to implement
+3. **Follow the same organization pattern** you used before:
+ - If you created separate manager scripts, create a new manager script for the new format
+ - If you used a unified AdManager, add the new format's code to your existing AdManager class
+ - If you used code snippets, integrate new snippets following the same pattern
+4. **Follow the same implementation guidelines** from Step 9 for the new ad format
+5. **Test the new format** following Step 10 testing guidelines
+
+**Example**: If you initially implemented only Rewarded ads using separate manager scripts, and now want to add Interstitial ads:
+- Create `InterstitialAdManager.cs` following the same structure as your `RewardedAdManager.cs`
+- Follow the interstitial implementation guidelines from `references/interstitial-api.md`
+- Test the interstitial ads in Unity Editor and on device
+
+Your existing ad formats will continue to work while you add new ones. You can add formats incrementally without disrupting your current implementation.
+
+## Best Practices
+
+### Loading Strategy
+
+**Rewarded Ads:**
+- Load immediately after SDK initialization (load proactively means load early and keep ads preloaded)
+- Reload immediately after showing or when ad fails to load
+- Always keep a rewarded ad ready to show
+
+**Interstitial Ads:**
+- Load proactively before natural break points
+- Maintain a loaded interstitial for opportunistic moments
+- Reload after showing
+
+**Banner Ads:**
+- Load when entering scenes where banners will be displayed
+- Destroy banners when leaving those scenes to free memory
+
+### Placement Strategy
+
+Align ad placements with user engagement moments:
+
+- **High engagement** (post-victory, level up): Best for rewarded ads
+- **Natural transitions** (level complete, menu navigation): Good for interstitials
+- **Low attention** (waiting periods, menus): Acceptable for banners
+
+### Error Handling
+
+Always handle ad load failures gracefully:
+
+```csharp
+private void OnAdLoadFailed(LevelPlayAdError error)
+{
+ Debug.LogWarning($"Ad failed to load: {error.ErrorMessage}");
+
+ // Retry loading after a delay
+ Invoke(nameof(LoadAd), 30f); // Retry in 30 seconds
+
+ // Provide fallback UX if ad was requested by user
+ // Don't leave users stuck waiting for an ad that won't load
+}
+```
+
+### Memory Management
+
+Properly unsubscribe from events to prevent memory leaks:
+
+```csharp
+void OnDestroy()
+{
+ // Unsubscribe from all LevelPlay events
+ if (rewardedAd != null)
+ {
+ rewardedAd.OnAdLoaded -= OnAdLoaded;
+ rewardedAd.OnAdLoadFailed -= OnAdLoadFailed;
+ // ... unsubscribe from all other events
+ }
+}
+```
+
+## Common Issues and Solutions
+
+**Note**: This section addresses issues that can occur during integration. If you haven't started the integration yet, begin with Step 1: Verify Environment.
+
+### Issue: CS0246 - Type or namespace 'Unity.Services.LevelPlay' not found
+
+**Root cause**: Ads Mediation package not installed in Unity project
+
+**Symptoms:**
+- Compiler errors: `The type or namespace name 'LevelPlay' could not be found`
+- Compiler errors: `The type or namespace name 'Unity.Services.LevelPlay' could not be found`
+- Red underlines in Unity Editor on all LevelPlay code
+
+**Solutions:**
+1. Stop providing code immediately
+2. Ask: "Can you confirm the Ads Mediation package is installed? Check Window > Package Manager and verify 'Ads Mediation' appears under 'Packages: In Project'"
+3. If not installed, direct to Step 3 to install via Unity Package Manager
+4. Have them restart Unity Editor after installation (important!)
+5. Verify installation by checking that `using Unity.Services.LevelPlay;` no longer shows errors
+6. Only resume code generation after confirmation and verification
+
+**Prevention**: Always verify package installation at Step 7 checkpoint before generating any code.
+
+### Issue: Android gradle build fails / iOS build fails with dependency errors
+
+**Root cause**: Native dependencies not resolved
+
+**Symptoms:**
+- **Android**: Gradle build errors mentioning missing dependencies or classes
+- **iOS**: CocoaPods errors, missing frameworks, or linker errors
+- Code compiles perfectly in Unity Editor but fails during platform build
+- Build succeeds in Editor but crashes immediately on device
+
+**Solutions:**
+1. Verify you have a dependency manager installed (Mobile Dependency Resolver, Unity External Dependency Manager, or EDM4U)
+2. Check your project's Assets folder for dependency manager tools
+3. Run dependency resolution:
+ - **Android (newer MDR versions)**: Should auto-resolve on build. If failing, manually resolve via Assets > Mobile Dependency Resolver > Android Resolver > Resolve
+ - **Android (older/other managers)**: Assets > [Your Dependency Manager] > Android Resolver > Resolve
+ - **iOS (all managers)**: Assets > [Your Dependency Manager] > iOS Resolver > Install Cocoapods
+4. Verify resolution:
+ - **Android**: Check `Assets/Plugins/Android/` for gradle files
+ - **iOS**: Look for Podfile or CocoaPods confirmation in console
+5. If you don't have a dependency manager, restart Unity - you should see a prompt to install Mobile Dependency Resolver
+6. Rebuild for your target platform after resolution
+
+**Prevention**: Complete Step 4 (dependency resolution) before building for Android/iOS.
+
+### Issue: Ads not loading
+
+**Possible causes:**
+- SDK not initialized before loading ads
+- Incorrect App Key
+- Ad object created before initialization completes
+- Network connectivity issues
+- Ad inventory not available in test region
+
+**Solutions:**
+- Verify `LevelPlay.Init()` is called and `OnInitSuccess` fires before creating ad objects
+- Create ad objects only after `OnInitSuccess` callback
+- Check App Key matches LevelPlay dashboard
+- Test on real devices with active internet connection
+- Enable test mode in LevelPlay dashboard for guaranteed test ads (Note: dashboard test mode is separate from mock ads in the Unity Editor — it enables real test ads on device)
+
+### Issue: Callbacks not firing
+
+**Possible causes:**
+- Events registered after SDK initialization
+- Missing event subscriptions
+- Script destroyed before callbacks execute
+
+**Solutions:**
+- Register callbacks before calling `Init()`
+- Verify all callbacks are subscribed (check with Debug.Log statements)
+- Use persistent GameObject with DontDestroyOnLoad if needed
+
+### Issue: Platform-specific build errors
+
+**iOS:**
+- Ensure SKAdNetwork IDs are configured in Info.plist
+- Verify ATT is implemented correctly (see `references/ios-setup.md`)
+- Check Xcode build settings for required frameworks
+
+**Android:**
+- Verify Google Play Services is included
+- Check AndroidManifest.xml for required permissions
+- Ensure Gradle dependencies are resolved
+
+## When to Read Detailed References
+
+Read specific references based on what the user is implementing:
+
+- **`references/rewarded-api.md`**: When implementing rewarded ads
+- **`references/interstitial-api.md`**: When implementing interstitial ads
+- **`references/banner-api.md`**: When implementing banner ads
+- **`references/initialization-api.md`**: When user ID tracking, segmentation, consent management, or advanced SDK configuration options are needed
+- **`references/ios-setup.md`**: When targeting iOS builds
+- **`references/best-practices.md`**: When user asks for optimization guidance or troubleshooting
+- **`references/privacy-settings.md`**: When GDPR, CCPA, or COPPA compliance is needed
+- **`references/ilrd-api.md`**: When wiring ILRD to an analytics platform
+
+## Examples
+
+**Note**: These examples show abbreviated workflows for illustration. In practice, follow all steps 1–10 in order to ensure proper integration.
+
+### Example 1: Revenue-Focused Game
+
+**User request:** "I want to maximize ad revenue in my casual puzzle game"
+
+**Response approach:**
+1. Verify Unity project (Step 1)
+2. Ask about optimization goal (Step 2)
+3. Verify SDK installed (Step 3)
+4. Verify dependencies resolved (Step 4)
+5. Verify they have App Key (Step 5)
+6. Verify AdMob config if needed (Step 6)
+7. Verify SDK initialized (Step 7)
+8. Recommend revenue-focused strategy (Step 8)
+9. Ask about code organization preference (Step 9)
+10. Generate appropriate code structure (Step 9)
+11. Guide through testing (Step 10)
+
+### Example 2: UX-Focused Game
+
+**User request:** "I want to add optional rewarded ads for extra lives without annoying players"
+
+**Response approach:**
+1. Verify Unity project (Step 1)
+2. Ask about optimization goal (Step 2)
+3. Verify SDK installed (Step 3)
+4. Verify dependencies resolved (Step 4)
+5. Verify they have App Key (Step 5)
+6. Verify AdMob config if needed (Step 6)
+7. Verify SDK initialized (Step 7)
+8. Recommend UX-focused strategy (Step 8): Rewarded only, user-initiated
+9. Ask about code organization preference (Step 9)
+10. Provide implementation with proper patterns (Step 9)
+11. Guide through testing (Step 10)
+
+### Example 3: Existing Project Integration
+
+**User request:** "I have an existing GameManager script and want to add interstitial ads between levels"
+
+**Response approach:**
+1. Verify Unity project (Step 1)
+2. Ask about optimization goal (Step 2)
+3. Verify SDK installed (Step 3)
+4. Verify dependencies resolved (Step 4)
+5. Verify they have App Key (Step 5)
+6. Verify AdMob config if needed (Step 6)
+7. Verify SDK initialized (Step 7)
+8. Recommend balanced strategy (Step 8)
+9. Ask to see existing GameManager.cs, then provide code snippets (Step 9)
+10. Guide through testing (Step 10)
From c5acac500f1eec6991906582a6a331225e17e6a1 Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 10:14:36 -0400
Subject: [PATCH 02/10] Add files via upload
---
.../levelplay-unity-integration/CHANGELOG.md | 20 +
skills/levelplay-unity-integration/README.md | 70 ++
.../references/banner-api.md | 923 ++++++++++++++++++
.../references/best-practices.md | 536 ++++++++++
.../references/ilrd-api.md | 261 +++++
.../references/initialization-api.md | 630 ++++++++++++
.../references/interstitial-api.md | 887 +++++++++++++++++
.../references/ios-setup.md | 491 ++++++++++
.../references/privacy-settings.md | 606 ++++++++++++
.../references/rewarded-api.md | 882 +++++++++++++++++
10 files changed, 5306 insertions(+)
create mode 100644 skills/levelplay-unity-integration/CHANGELOG.md
create mode 100644 skills/levelplay-unity-integration/README.md
create mode 100644 skills/levelplay-unity-integration/references/banner-api.md
create mode 100644 skills/levelplay-unity-integration/references/best-practices.md
create mode 100644 skills/levelplay-unity-integration/references/ilrd-api.md
create mode 100644 skills/levelplay-unity-integration/references/initialization-api.md
create mode 100644 skills/levelplay-unity-integration/references/interstitial-api.md
create mode 100644 skills/levelplay-unity-integration/references/ios-setup.md
create mode 100644 skills/levelplay-unity-integration/references/privacy-settings.md
create mode 100644 skills/levelplay-unity-integration/references/rewarded-api.md
diff --git a/skills/levelplay-unity-integration/CHANGELOG.md b/skills/levelplay-unity-integration/CHANGELOG.md
new file mode 100644
index 0000000..d64b97c
--- /dev/null
+++ b/skills/levelplay-unity-integration/CHANGELOG.md
@@ -0,0 +1,20 @@
+# Changelog
+
+## v0.7.0 — 2026-06-05 — 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?usp=sharing&ouid=116753073211029919766
diff --git a/skills/levelplay-unity-integration/README.md b/skills/levelplay-unity-integration/README.md
new file mode 100644
index 0000000..511c1d6
--- /dev/null
+++ b/skills/levelplay-unity-integration/README.md
@@ -0,0 +1,70 @@
+# LevelPlay Unity Integration Skill
+
+  
+
+> 🧪 **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?usp=sharing&ouid=116753073211029919766)!
+
+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.
+
+## What's in this folder
+
+```
+levelplay-unity-integration/
+├── SKILL.md # Core skill instructions
+├── references/ # Detailed API guides for each ad format
+│ ├── rewarded-api.md
+│ ├── interstitial-api.md
+│ ├── banner-api.md
+│ ├── ios-setup.md
+│ ├── privacy-settings.md
+│ ├── ilrd-api.md
+│ ├── initialization-api.md
+│ └── best-practices.md
+├── CHANGELOG.md
+└── README.md
+```
+
diff --git a/skills/levelplay-unity-integration/references/banner-api.md b/skills/levelplay-unity-integration/references/banner-api.md
new file mode 100644
index 0000000..ced321e
--- /dev/null
+++ b/skills/levelplay-unity-integration/references/banner-api.md
@@ -0,0 +1,923 @@
+# Banner Ads API Reference
+
+## Contents
+- [Overview](#overview)
+- [Key Characteristics](#key-characteristics)
+- [Implementation Pattern](#implementation-pattern)
+- [API Reference](#api-reference)
+- [Data Types](#data-types)
+- [Best Practices](#best-practices)
+- [Common Issues](#common-issues)
+- [Testing Checklist](#testing-checklist)
+
+## Overview
+
+Banner ads are rectangular ads that occupy a portion of the screen. They can be displayed at the top or bottom of the screen and remain visible while users interact with your app.
+
+## Key Characteristics
+
+- **Persistent**: Remain on screen until explicitly destroyed
+- **Always visible**: Can distract from gameplay if poorly positioned
+- **Best for**: Menu screens, waiting periods, low-attention moments
+
+## Implementation Pattern
+
+### Basic Banner Implementation
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class BannerAdManager : MonoBehaviour
+{
+ private LevelPlayBannerAd bannerAd;
+ private string adUnitId = "YOUR_BANNER_AD_UNIT_ID";
+
+ void Start()
+ {
+ // Create the banner ad object using constructor
+ bannerAd = new LevelPlayBannerAd(adUnitId);
+
+ // Register event listeners
+ bannerAd.OnAdLoaded += OnAdLoaded;
+ bannerAd.OnAdLoadFailed += OnAdLoadFailed;
+ bannerAd.OnAdDisplayed += OnAdDisplayed;
+ bannerAd.OnAdDisplayFailed += OnAdDisplayFailed;
+ bannerAd.OnAdClicked += OnAdClicked;
+ bannerAd.OnAdExpanded += OnAdExpanded;
+ bannerAd.OnAdCollapsed += OnAdCollapsed;
+ bannerAd.OnAdLeftApplication += OnAdLeftApplication;
+
+ // Load and show banner
+ LoadBanner();
+ }
+
+ void OnDestroy()
+ {
+ // Unregister event listeners
+ if (bannerAd != null)
+ {
+ bannerAd.OnAdLoaded -= OnAdLoaded;
+ bannerAd.OnAdLoadFailed -= OnAdLoadFailed;
+ bannerAd.OnAdDisplayed -= OnAdDisplayed;
+ bannerAd.OnAdDisplayFailed -= OnAdDisplayFailed;
+ bannerAd.OnAdClicked -= OnAdClicked;
+ bannerAd.OnAdExpanded -= OnAdExpanded;
+ bannerAd.OnAdCollapsed -= OnAdCollapsed;
+ bannerAd.OnAdLeftApplication -= OnAdLeftApplication;
+ }
+
+ // Destroy banner
+ DestroyBanner();
+ }
+
+ public void LoadBanner()
+ {
+ Debug.Log("Loading banner ad...");
+ bannerAd.LoadAd();
+ }
+
+ public void ShowBanner()
+ {
+ Debug.Log("Showing banner ad");
+ bannerAd.ShowAd();
+ }
+
+ public void HideBanner()
+ {
+ Debug.Log("Hiding banner ad");
+ bannerAd.HideAd();
+ }
+
+ public void DestroyBanner()
+ {
+ if (bannerAd != null)
+ {
+ Debug.Log("Destroying banner ad");
+ bannerAd.DestroyAd();
+ bannerAd = null;
+ }
+ }
+
+ // Event Callbacks
+ private void OnAdLoaded(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Banner ad loaded");
+ // Banner is ready, can call ShowAd() if needed
+ }
+
+ private void OnAdLoadFailed(LevelPlayAdError error)
+ {
+ Debug.LogWarning($"Banner ad load failed: {error.ErrorMessage}");
+ // Retry loading after delay
+ Invoke(nameof(LoadBanner), 60f);
+ }
+
+ private void OnAdDisplayed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Banner ad displayed");
+ }
+
+ private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
+ {
+ Debug.LogError($"Banner ad display failed: {error.ErrorMessage}");
+ // Retry loading
+ Invoke(nameof(LoadBanner), 60f);
+ }
+
+ private void OnAdClicked(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Banner ad clicked");
+ }
+
+ private void OnAdExpanded(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Banner ad expanded");
+ // Optionally pause game if banner expands to full screen
+ }
+
+ private void OnAdCollapsed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Banner ad collapsed");
+ // Resume game if paused
+ }
+
+ private void OnAdLeftApplication(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Banner ad left application");
+ }
+}
+```
+
+### Banner with Custom Configuration
+
+Use the Config Builder pattern to customize banner size, position, and behavior:
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class CustomBannerAdManager : MonoBehaviour
+{
+ private LevelPlayBannerAd bannerAd;
+ private string adUnitId = "YOUR_BANNER_AD_UNIT_ID";
+
+ void Start()
+ {
+ // Create config using Builder pattern
+ var configBuilder = new LevelPlayBannerAd.Config.Builder();
+
+ // Set banner size (UPPERCASE!)
+ configBuilder.SetSize(LevelPlayAdSize.LARGE); // 320x90
+
+ // Set position on screen
+ configBuilder.SetPosition(LevelPlayBannerPosition.BottomCenter);
+
+ // Auto-display when loaded
+ configBuilder.SetDisplayOnLoad(true);
+
+ // Respect safe area (Android only)
+ configBuilder.SetRespectSafeArea(true);
+
+ // Build the config
+ var bannerConfig = configBuilder.Build();
+
+ // Create banner with config
+ bannerAd = new LevelPlayBannerAd(adUnitId, bannerConfig);
+
+ // Register callbacks
+ bannerAd.OnAdLoaded += OnAdLoaded;
+ bannerAd.OnAdLoadFailed += OnAdLoadFailed;
+ bannerAd.OnAdDisplayed += OnAdDisplayed;
+
+ // Load banner
+ bannerAd.LoadAd();
+ }
+
+ void OnDestroy()
+ {
+ if (bannerAd != null)
+ {
+ bannerAd.OnAdLoaded -= OnAdLoaded;
+ bannerAd.OnAdLoadFailed -= OnAdLoadFailed;
+ bannerAd.OnAdDisplayed -= OnAdDisplayed;
+ bannerAd.DestroyAd();
+ }
+ }
+
+ private void OnAdLoaded(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Custom banner loaded");
+ }
+
+ private void OnAdLoadFailed(LevelPlayAdError error)
+ {
+ Debug.LogWarning($"Banner load failed: {error.ErrorMessage}");
+ }
+
+ private void OnAdDisplayed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Custom banner displayed");
+ }
+}
+```
+
+### Advanced: Context-Aware Banner Management
+
+Show banners only in appropriate scenes:
+
+```csharp
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using Unity.Services.LevelPlay;
+
+public class ContextAwareBannerManager : MonoBehaviour
+{
+ private LevelPlayBannerAd bannerAd;
+ private string adUnitId = "YOUR_BANNER_AD_UNIT_ID";
+
+ // Scenes where banner should be shown
+ private string[] bannerEnabledScenes = { "MainMenu", "LevelSelect", "Shop" };
+
+ private bool isBannerLoaded = false;
+
+ void Start()
+ {
+ // Make this manager persist across scenes
+ DontDestroyOnLoad(gameObject);
+
+ // Create banner ad
+ bannerAd = new LevelPlayBannerAd(adUnitId);
+
+ // Register callbacks
+ bannerAd.OnAdLoaded += OnAdLoaded;
+ bannerAd.OnAdLoadFailed += OnAdLoadFailed;
+
+ // Register for scene changes
+ SceneManager.sceneLoaded += OnSceneLoaded;
+
+ // Load banner
+ bannerAd.LoadAd();
+
+ // Check initial scene
+ OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single);
+ }
+
+ void OnDestroy()
+ {
+ // Unregister callbacks
+ if (bannerAd != null)
+ {
+ bannerAd.OnAdLoaded -= OnAdLoaded;
+ bannerAd.OnAdLoadFailed -= OnAdLoadFailed;
+ }
+
+ SceneManager.sceneLoaded -= OnSceneLoaded;
+
+ // Destroy banner
+ if (bannerAd != null)
+ {
+ bannerAd.DestroyAd();
+ }
+ }
+
+ private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
+ {
+ if (ShouldShowBannerInScene(scene.name))
+ {
+ ShowBanner();
+ }
+ else
+ {
+ HideBanner();
+ }
+ }
+
+ private bool ShouldShowBannerInScene(string sceneName)
+ {
+ foreach (string enabledScene in bannerEnabledScenes)
+ {
+ if (sceneName == enabledScene)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void ShowBanner()
+ {
+ if (isBannerLoaded && bannerAd != null)
+ {
+ bannerAd.ShowAd();
+ }
+ }
+
+ private void HideBanner()
+ {
+ if (bannerAd != null)
+ {
+ bannerAd.HideAd();
+ }
+ }
+
+ private void OnAdLoaded(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Banner ad loaded");
+ isBannerLoaded = true;
+
+ // Show banner if we're in an enabled scene
+ if (ShouldShowBannerInScene(SceneManager.GetActiveScene().name))
+ {
+ ShowBanner();
+ }
+ }
+
+ private void OnAdLoadFailed(LevelPlayAdError error)
+ {
+ Debug.LogWarning($"Banner ad load failed: {error.ErrorMessage}");
+ isBannerLoaded = false;
+ Invoke(nameof(LoadBanner), 60f);
+ }
+
+ private void LoadBanner()
+ {
+ if (bannerAd != null)
+ {
+ isBannerLoaded = false; // Reset flag when loading
+ bannerAd.LoadAd();
+ }
+ }
+}
+```
+
+## API Reference
+
+### Constructor
+
+#### `new LevelPlayBannerAd(string adUnitId)`
+Create a banner ad object with default configuration.
+
+**Parameters:**
+- `adUnitId`: The ad unit identifier from your LevelPlay dashboard
+
+**Returns:** `LevelPlayBannerAd` object
+
+**Usage:**
+```csharp
+LevelPlayBannerAd bannerAd = new LevelPlayBannerAd("your_ad_unit_id");
+```
+
+**Default behavior:**
+- Size: Standard banner (320x50)
+- Position: Bottom center
+- DisplayOnLoad: true (banner shows automatically when loaded)
+
+**Important:** Call this only after `LevelPlay.Init()` has completed successfully.
+
+#### `new LevelPlayBannerAd(string adUnitId, Config config)`
+Create a banner ad object with custom configuration.
+
+**Parameters:**
+- `adUnitId`: The ad unit identifier
+- `config`: Configuration object built using Config.Builder()
+
+**Usage:**
+```csharp
+var configBuilder = new LevelPlayBannerAd.Config.Builder();
+configBuilder.SetSize(LevelPlayAdSize.LARGE);
+configBuilder.SetPosition(LevelPlayBannerPosition.TopCenter);
+var config = configBuilder.Build();
+
+LevelPlayBannerAd bannerAd = new LevelPlayBannerAd("your_ad_unit_id", config);
+```
+
+### Configuration Builder
+
+#### `LevelPlayBannerAd.Config.Builder()`
+Create a configuration builder for customizing banner behavior.
+
+**Methods:**
+
+**`SetSize(LevelPlayAdSize size)`**
+Set the banner size.
+
+**Usage:**
+```csharp
+configBuilder.SetSize(LevelPlayAdSize.BANNER); // 320x50
+configBuilder.SetSize(LevelPlayAdSize.LARGE); // 320x90
+configBuilder.SetSize(LevelPlayAdSize.MEDIUM_RECTANGLE); // 300x250
+configBuilder.SetSize(LevelPlayAdSize.LEADERBOARD); // 728x90
+configBuilder.SetSize(LevelPlayAdSize.CreateAdaptiveAdSize()); // Adaptive
+configBuilder.SetSize(LevelPlayAdSize.CreateCustomBannerSize(300, 150)); // Custom size
+```
+
+**`SetPosition(LevelPlayBannerPosition position)`**
+Set the banner position on screen.
+
+**Usage:**
+```csharp
+configBuilder.SetPosition(LevelPlayBannerPosition.TopCenter);
+configBuilder.SetPosition(LevelPlayBannerPosition.BottomCenter);
+// Also: TopLeft, TopRight, BottomLeft, BottomRight
+```
+
+**`SetDisplayOnLoad(bool displayOnLoad)`**
+Whether to automatically show banner when loaded.
+
+**Usage:**
+```csharp
+configBuilder.SetDisplayOnLoad(true); // Auto-show (default)
+configBuilder.SetDisplayOnLoad(false); // Manual show
+```
+
+**`SetRespectSafeArea(bool respectSafeArea)`**
+Whether to respect device safe area (Android only).
+
+**Usage:**
+```csharp
+configBuilder.SetRespectSafeArea(true); // Avoid notches/cutouts
+```
+
+**`SetPlacementName(string placementName)`**
+Set the placement name for analytics tracking.
+
+**Usage:**
+```csharp
+configBuilder.SetPlacementName("main_menu_banner");
+```
+
+**When to use:** When tracking multiple banner placements for analytics and reporting.
+
+**Set minimum bid price:**
+```csharp
+configBuilder.SetBidFloor(1.0); // Minimum bid price in USD ($1.00 CPM)
+```
+
+**When to use:** Set a minimum bid price in USD for ad requests to ensure revenue floor.
+
+**`Build()`**
+Build the configuration object.
+
+**Usage:**
+```csharp
+var config = configBuilder.Build();
+```
+
+### Core Methods
+
+#### `LoadAd()`
+Load a banner ad.
+
+**Usage:**
+```csharp
+bannerAd.LoadAd();
+```
+
+**Note:** After loading succeeds, you can call `ShowAd()` to display it (unless `SetDisplayOnLoad(true)` was used).
+
+#### `ShowAd()`
+Show the banner ad.
+
+**Usage:**
+```csharp
+bannerAd.ShowAd();
+```
+
+**Note:** Banner must be loaded first.
+
+#### `HideAd()`
+Hide the banner ad without destroying it.
+
+**Usage:**
+```csharp
+bannerAd.HideAd();
+```
+
+**When to use:** Temporarily hide banner (e.g., during gameplay) while keeping it loaded for quick re-display.
+
+#### `DestroyAd()`
+Destroy the banner ad and free resources.
+
+**Usage:**
+```csharp
+bannerAd.DestroyAd();
+```
+
+**When to use:** When leaving a scene where banner was shown, or when switching to a different banner size.
+
+**Note on IsAdReady():** Unlike Rewarded and Interstitial ads, Banner ads do NOT have an `IsAdReady()` method. Banners are ready to show immediately after `OnAdLoaded` fires. Simply call `ShowAd()` in the `OnAdLoaded` callback or afterward.
+
+#### `PauseAutoRefresh()`
+Pause automatic banner refresh.
+
+**Usage:**
+```csharp
+bannerAd.PauseAutoRefresh();
+```
+
+**When to use:** When banner is not visible but still loaded (e.g., during gameplay).
+
+#### `ResumeAutoRefresh()`
+Resume automatic banner refresh.
+
+**Usage:**
+```csharp
+bannerAd.ResumeAutoRefresh();
+```
+
+**When to use:** When banner becomes visible again after pausing.
+
+### Banner Sizes
+
+Available banner sizes from `LevelPlayAdSize` (note: **UPPERCASE**):
+
+| Size | Description | Typical Dimensions |
+|------|-------------|-------------------|
+| `LevelPlayAdSize.BANNER` | Standard banner | 320x50 |
+| `LevelPlayAdSize.LARGE` | Large banner | 320x90 |
+| `LevelPlayAdSize.MEDIUM_RECTANGLE` | Medium rectangle | 300x250 |
+| `LevelPlayAdSize.LEADERBOARD` | Leaderboard banner | 728x90 |
+| `LevelPlayAdSize.CreateAdaptiveAdSize()` | Adaptive banner | Adapts to screen width |
+| `LevelPlayAdSize.CreateCustomBannerSize(int width, int height)` | Custom size | Custom dimensions |
+
+**Usage:**
+```csharp
+// Standard banner (most common)
+LevelPlayAdSize.BANNER
+
+// Large banner
+LevelPlayAdSize.LARGE
+
+// Medium rectangle
+LevelPlayAdSize.MEDIUM_RECTANGLE
+
+// Leaderboard
+LevelPlayAdSize.LEADERBOARD
+
+// Adaptive banner (method call)
+LevelPlayAdSize.CreateAdaptiveAdSize()
+
+// Custom size
+LevelPlayAdSize.CreateCustomBannerSize(300, 150)
+```
+
+**Recommendation:** Use `BANNER` (320x50) for most cases. Use `CreateAdaptiveAdSize()` for adaptive sizing across devices.
+
+**LevelPlayAdSize Properties (SDK 8.8.0+):**
+
+After setting a banner size, you can query the actual dimensions:
+
+```csharp
+LevelPlayAdSize adSize = LevelPlayAdSize.BANNER;
+int width = adSize.Width; // e.g., 320
+int height = adSize.Height; // e.g., 50
+```
+
+**Properties:**
+- `Width` (int): Banner width in pixels
+- `Height` (int): Banner height in pixels
+
+**When to use:** When calculating UI layout offsets or positioning other UI elements around the banner.
+
+### Banner Positions
+
+Available positions from `LevelPlayBannerPosition`:
+
+| Position | Description |
+|----------|-------------|
+| `TopLeft` | Top-left corner |
+| `TopCenter` | Top-center |
+| `TopRight` | Top-right corner |
+| `CenterLeft` | Center-left |
+| `Center` | Center |
+| `CenterRight` | Center-right |
+| `BottomLeft` | Bottom-left corner |
+| `BottomCenter` | Bottom-center (most common) |
+| `BottomRight` | Bottom-right corner |
+
+**Usage:**
+```csharp
+LevelPlayBannerPosition.BottomCenter
+LevelPlayBannerPosition.TopCenter
+LevelPlayBannerPosition.Center
+// etc.
+```
+
+**Additional:** Custom positioning can be achieved using a Vector2 constructor.
+
+**Recommendation:** `BottomCenter` is less intrusive and more commonly used.
+
+### Events
+
+All events are properties of the `LevelPlayBannerAd` object.
+
+**Threading:** All ad callbacks run on the Unity main thread, so you can safely call Unity APIs (update UI, access GameObjects, etc.) directly in these callbacks. This is different from `LevelPlay.OnImpressionDataReady` which runs on a background thread.
+
+#### `OnAdLoaded`
+Fired when a banner ad is loaded.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+bannerAd.OnAdLoaded += (adInfo) =>
+{
+ Debug.Log("Banner loaded");
+ bannerAd.ShowAd();
+};
+```
+
+#### `OnAdLoadFailed`
+Fired when a banner ad fails to load.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+bannerAd.OnAdLoadFailed += (error) =>
+{
+ Debug.LogWarning($"Banner load failed: {error.ErrorMessage}");
+};
+```
+
+#### `OnAdDisplayed`
+Fired when banner ad is displayed.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+bannerAd.OnAdDisplayed += (adInfo) =>
+{
+ Debug.Log("Banner displayed");
+};
+```
+
+#### `OnAdDisplayFailed`
+Fired when banner ad fails to display.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+bannerAd.OnAdDisplayFailed += (adInfo, error) =>
+{
+ Debug.LogError($"Banner display failed: {error.ErrorMessage}");
+};
+```
+
+#### `OnAdClicked`
+Fired when user clicks on the banner.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+bannerAd.OnAdClicked += (adInfo) =>
+{
+ Debug.Log("Banner clicked");
+};
+```
+
+#### `OnAdExpanded`
+Fired when banner ad expands to full screen (e.g., after click).
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+bannerAd.OnAdExpanded += (adInfo) =>
+{
+ Debug.Log("Banner expanded");
+ // Pause game if needed
+};
+```
+
+#### `OnAdCollapsed`
+Fired when expanded banner collapses back.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+bannerAd.OnAdCollapsed += (adInfo) =>
+{
+ Debug.Log("Banner collapsed");
+ // Resume game if paused
+};
+```
+
+#### `OnAdLeftApplication`
+Fired when banner click causes user to leave the application.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+bannerAd.OnAdLeftApplication += (adInfo) =>
+{
+ Debug.Log("Banner left application");
+};
+```
+
+## Data Types
+
+### LevelPlayAdInfo
+
+Contains information about the ad.
+
+**Properties:**
+- `AdId` (string): Unique identifier for this specific ad instance
+- `AdUnitId` (string): The ad unit identifier
+- `AdUnitName` (string): The ad unit name
+- `AdSize` (LevelPlayAdSize): The ad size (may be null; banner-relevant)
+- `AdFormat` (string): The ad format (e.g., "BANNER")
+- `PlacementName` (string): Placement name where ad was shown
+- `AuctionId` (string): Unique auction identifier
+- `CreativeId` (string): Creative identifier
+- `Country` (string): User's country code (ISO 3166-1)
+- `Ab` (string): A/B test segment identifier
+- `SegmentName` (string): User segment name
+- `AdNetwork` (string): Ad network that served the ad
+- `InstanceName` (string): Ad network instance name
+- `InstanceId` (string): Ad network instance identifier
+- `Revenue` (double?): Estimated revenue in USD (nullable - check for null)
+- `Precision` (string): Revenue precision level
+- `EncryptedCPM` (string): Encrypted CPM value
+
+### LevelPlayAdError
+
+Contains error information when ad operations fail.
+
+**Properties:**
+- `ErrorCode` (int): Numeric error code
+- `ErrorMessage` (string): Human-readable error description
+- `AdUnitId` (string): The ad unit identifier where the error occurred
+- `AdId` (string): Unique identifier for the specific ad instance
+
+## Best Practices
+
+### Loading
+
+- Load banners when entering scenes where they'll be displayed
+- Destroy banners when leaving those scenes to free memory
+- Only one banner per ad unit can be active at a time
+- If you need to change size/position, destroy and recreate
+
+### Placement
+
+- **Bottom position** is generally less intrusive than top
+- **Avoid during gameplay** - show in menus, waiting screens
+- **Consider UI overlap** - ensure banner doesn't cover important UI
+- **Test on various screen sizes** - especially important for mobile
+
+### Visibility Management
+
+- Use `HideAd()` / `ShowAd()` for temporary visibility changes
+- Use `DestroyAd()` when permanently done with banner (frees memory)
+- Hide banners during gameplay for UX-focused strategy
+- Show banners persistently for revenue-focused strategy
+
+### Auto-Refresh Management
+
+- Use `PauseAutoRefresh()` when banner is hidden
+- Use `ResumeAutoRefresh()` when banner is shown again
+
+**Example:**
+```csharp
+void OnGameplayStart()
+{
+ bannerAd.HideAd();
+ bannerAd.PauseAutoRefresh();
+}
+
+void OnGameplayEnd()
+{
+ bannerAd.ShowAd();
+ bannerAd.ResumeAutoRefresh();
+}
+```
+
+### Memory Management
+
+- Always call `DestroyAd()` in `OnDestroy()` or when leaving scene
+- Don't keep banners loaded in scenes where they're not displayed
+- Unsubscribe from events properly
+
+```csharp
+void OnDestroy()
+{
+ if (bannerAd != null)
+ {
+ bannerAd.DestroyAd();
+ }
+}
+```
+
+### UX Considerations
+
+- Banners can be distracting during gameplay
+- Best practice: Show in menus, hide during gameplay
+- For revenue-focused: Show persistently but position carefully
+- Test with actual users to gauge impact on experience
+
+## Common Issues
+
+### Issue: Repeated LoadAd() calls
+
+**Cause:** Calling `LoadAd()` repeatedly in `Update()` or other high-frequency loops.
+
+**Common mistake:**
+```csharp
+void Update()
+{
+ // WRONG - Don't call LoadAd() repeatedly!
+ if (!bannerAd.IsAdReady())
+ {
+ bannerAd.LoadAd();
+ }
+}
+```
+
+**Why to avoid:** While banner ads don't throw errors for repeated loads like interstitial/rewarded ads do, calling `LoadAd()` multiple times per second wastes resources and can cause unexpected behavior.
+
+**Solution:** Call `LoadAd()` once after creating the banner:
+```csharp
+void Start()
+{
+ bannerAd = new LevelPlayBannerAd(adUnitId);
+ bannerAd.OnAdLoaded += OnAdLoaded;
+ bannerAd.OnAdLoadFailed += OnAdLoadFailed;
+ bannerAd.LoadAd(); // Call once
+}
+
+void OnAdLoadFailed(LevelPlayAdError error)
+{
+ // Optionally retry after delay
+ Invoke(nameof(RetryLoad), 5f);
+}
+
+void RetryLoad()
+{
+ bannerAd.LoadAd();
+}
+```
+
+**Note:** Banner ads auto-refresh based on platform settings, so you typically only need to call `LoadAd()` once at initialization.
+
+### Issue: Banner overlaps UI elements
+
+**Solutions:**
+- Calculate banner height and offset UI elements
+- Use `Canvas.offsetMin` or `RectTransform.anchoredPosition` to create spacing
+- Test on different screen sizes
+- Use `SetRespectSafeArea(true)` to avoid notches
+
+### Issue: Banner not showing
+
+**Possible causes:**
+- SDK not initialized before creating banner
+- Banner not loaded before calling `ShowAd()`
+- Banner is hidden with `HideAd()`
+- `SetDisplayOnLoad(false)` used without manual `ShowAd()`
+
+**Solutions:**
+- Create banner only after `OnInitSuccess` callback
+- Ensure `LoadAd()` is called and `OnAdLoaded` fires before `ShowAd()`
+- Check banner visibility state
+
+### Issue: Banner shows in wrong scenes
+
+**Solutions:**
+- Implement scene-based visibility management
+- Destroy banner when leaving scenes where it shouldn't appear
+- Use `SceneManager.sceneLoaded` event for automatic management
+
+### Issue: Wrong banner size
+
+**Possible causes:**
+- Using lowercase enum values (e.g., `.Banner` instead of `.BANNER`)
+- Not using Config Builder to set size
+
+**Solutions:**
+- Always use UPPERCASE: `LevelPlayAdSize.BANNER`, not `.Banner`
+- Use Config Builder to explicitly set size:
+```csharp
+var config = new LevelPlayBannerAd.Config.Builder()
+ .SetSize(LevelPlayAdSize.LARGE)
+ .Build();
+```
+
+## Testing Checklist
+
+- [ ] Banner loads and displays correctly
+- [ ] Banner appears in correct position (top/bottom)
+- [ ] Banner size is appropriate for screen
+- [ ] Banner doesn't overlap important UI
+- [ ] Banner hides/shows correctly
+- [ ] Banner destroys properly when leaving scene
+- [ ] Banner works on different screen sizes/orientations
+- [ ] Retry logic works if banner fails to load
+- [ ] Auto-refresh pauses/resumes correctly
+- [ ] Safe area respected on devices with notches (Android)
diff --git a/skills/levelplay-unity-integration/references/best-practices.md b/skills/levelplay-unity-integration/references/best-practices.md
new file mode 100644
index 0000000..804a884
--- /dev/null
+++ b/skills/levelplay-unity-integration/references/best-practices.md
@@ -0,0 +1,536 @@
+# LevelPlay Ad Unit Implementation Best Practices
+
+## Contents
+- [Overview](#overview)
+- [Strategic Framework: Revenue vs UX Optimization](#strategic-framework-revenue-vs-ux-optimization)
+- [Ad Format Strategy by Goal](#ad-format-strategy-by-goal)
+- [Placement Strategy](#placement-strategy)
+- [Implementation Patterns](#implementation-patterns)
+- [Frequency Management](#frequency-management)
+- [Error Handling and Graceful Degradation](#error-handling-and-graceful-degradation)
+- [A/B Testing Considerations](#ab-testing-considerations)
+- [Common Mistakes to Avoid](#common-mistakes-to-avoid)
+- [Platform-Specific Considerations](#platform-specific-considerations)
+- [Success Metrics by Strategy](#success-metrics-by-strategy)
+- [Final Recommendations](#final-recommendations)
+
+## Overview
+
+This reference compiles best practices from the LevelPlay enablement playbook, covering strategy selection, placement optimization, and common implementation patterns that drive both revenue and user satisfaction.
+
+## Strategic Framework: Revenue vs UX Optimization
+
+### Understanding the Trade-off
+
+Every ad implementation involves a fundamental trade-off between revenue generation and user experience. Your optimization goal should guide every decision.
+
+| Optimization Goal | Philosophy | Typical Outcome |
+|-------------------|------------|-----------------|
+| **Revenue-Focused** | Maximize impression opportunities | Higher revenue, potential UX impact |
+| **UX-Focused** | Protect user experience at all costs | Better retention, lower short-term revenue |
+| **Balanced** | Optimize for sustainable long-term value | Moderate revenue with good UX |
+
+### Choosing Your Optimization Goal
+
+**Ask yourself:**
+1. What stage is your app in? (Early = UX focus, Mature = revenue optimization)
+2. What's your business model? (Ads-only = revenue, Hybrid = balanced)
+3. What's your competitive landscape? (High competition = UX focus)
+4. What are your user expectations? (Casual games = more ads tolerated)
+
+## Ad Format Strategy by Goal
+
+### Revenue-Focused Implementation
+
+**Goal**: Maximize ad revenue and impression opportunities
+
+**Rewarded Ads** (Primary):
+- Implement 3-5 placements across high-value moments
+- Examples: Hints, extra lives, power-ups, bonus currency, skip levels
+- Always keep a rewarded ad loaded and ready
+- Reload immediately after showing
+
+**Interstitial Ads** (Secondary):
+- Show at every natural transition point
+- Frequency cap: Every 3-5 minutes of active gameplay
+- Placements: Level complete, game over, returning to menu
+- Load proactively before transitions
+
+**Banner Ads** (Tertiary):
+- Show persistently during gameplay
+- Position: Bottom (less intrusive than top)
+- Keep loaded throughout session for continuous impressions
+- Accept some visual clutter for revenue
+
+**Expected Outcomes:** *(Industry benchmarks, not LevelPlay-specific)*
+- ✅ 30-50% higher ad revenue
+- ⚠️ 5-15% increase in early abandonment
+- ⚠️ Lower user satisfaction scores
+- ✅ Strong monetization from engaged users
+
+### UX-Focused Implementation
+
+**Goal**: Maintain excellent user experience while monetizing thoughtfully
+
+**Rewarded Ads** (Only or Primary):
+- Single placement or 2 maximum
+- User-initiated only (player chooses to watch)
+- High-value rewards that feel generous
+- Never force ads
+
+**Interstitial Ads** (Optional, Rare):
+- Only at major session boundaries (returning to menu, closing app)
+- Frequency cap: 10+ minutes or once per session
+- Skip during active gameplay entirely
+
+**Banner Ads** (Avoid or Minimize):
+- Show only in menus, never during gameplay
+- If used, small sizes (standard 320x50, not rectangles)
+- Hide during any active user engagement
+
+**Expected Outcomes:** *(Industry benchmarks, not LevelPlay-specific)*
+- ⚠️ 40-60% lower ad revenue vs revenue-focused
+- ✅ Better user retention and satisfaction
+- ✅ Higher organic growth and word-of-mouth
+- ✅ Premium brand perception
+
+### Balanced Implementation (Recommended for Most Apps)
+
+**Goal**: Sustainable monetization with good user experience
+
+**Rewarded Ads** (Primary):
+- 2-3 well-chosen placements
+- Focus on moments of high user engagement
+- Make rewards feel valuable but not exploitative
+- Examples: Extra attempts, time skips, premium currency
+
+**Interstitial Ads** (Moderate):
+- Natural transition points only (level complete, game over)
+- Frequency cap: Every 5-7 minutes
+- Never interrupt active gameplay
+- Respect user flow
+
+**Banner Ads** (Selective):
+- Show in menus and low-attention moments
+- Hide during intense or immersive gameplay
+- Position at bottom with proper UI offset
+- Rotation: Show/hide based on context
+
+**Expected Outcomes:** *(Industry benchmarks, not LevelPlay-specific)*
+- ✅ Strong ad revenue (70-85% of revenue-focused approach)
+- ✅ Good user retention
+- ✅ Sustainable long-term monetization
+- ✅ Positive user sentiment
+
+## Placement Strategy
+
+### Identifying Strong Placement Moments
+
+**High-value moments** (Best for rewarded ads):
+- Post-victory celebrations
+- Level progression milestones
+- Unlock opportunities
+- User feels positive emotion
+
+**Natural transitions** (Good for interstitials):
+- Level complete
+- Game over
+- Returning to main menu
+- Session boundaries
+
+**Low-attention periods** (Acceptable for banners):
+- Waiting screens
+- Loading periods
+- Menu browsing
+- Non-interactive moments
+
+### Placement Anti-Patterns (Avoid These)
+
+❌ **Mid-gameplay interruptions**: Never show interstitials during active play
+❌ **Bait-and-switch**: Don't offer rewards then show unrewarded interstitials
+❌ **Excessive frequency**: Showing ads every 1-2 minutes frustrates users
+❌ **Punitive ads**: Using ads as punishment for failure feels manipulative
+❌ **Blocking progression**: Making ads mandatory to continue playing
+❌ **Poor timing**: Showing ads right before key moments or cliffhangers
+
+## Implementation Patterns
+
+### Pattern 1: Rewarded Hints System
+
+**Use case**: Puzzle or strategy games where hints add value
+
+```csharp
+public class HintSystem : MonoBehaviour
+{
+ [SerializeField] private RewardedAdManager adManager;
+ [SerializeField] private int hintsAvailable = 3;
+
+ public void RequestHint()
+ {
+ if (hintsAvailable > 0)
+ {
+ // User has free hints remaining
+ UseHint();
+ hintsAvailable--;
+ }
+ else
+ {
+ // Offer ad-based hint
+ if (adManager.IsRewardedAdAvailable())
+ {
+ ShowHintAdOffer();
+ }
+ else
+ {
+ ShowHintNotAvailableMessage();
+ }
+ }
+ }
+
+ private void ShowHintAdOffer()
+ {
+ // Show dialog: "Watch an ad to get a hint?"
+ // If user accepts:
+ adManager.ShowRewardedAd(OnHintAdCompleted);
+ }
+
+ private void OnHintAdCompleted(LevelPlayAdInfo adInfo, LevelPlayReward reward)
+ {
+ if (reward != null && !string.IsNullOrEmpty(reward.Name))
+ {
+ // User completed ad, grant hint
+ UseHint();
+ }
+ }
+
+ private void UseHint()
+ {
+ // Show hint to player
+ Debug.Log("Showing hint");
+ }
+
+ private void ShowHintNotAvailableMessage()
+ {
+ // Inform user that hints aren't available right now
+ Debug.Log("Hints not available at the moment");
+ }
+}
+```
+
+**Why this works:**
+- User has free hints first (generous)
+- Ad is optional, not forced
+- Clear value exchange (ad for hint)
+- Graceful handling when ads unavailable
+
+### Pattern 2: Level Transition Interstitial (Frequency Capped)
+
+**Use case**: Showing interstitials between levels without annoying users
+
+```csharp
+public class LevelTransitionAds : MonoBehaviour
+{
+ [SerializeField] private InterstitialAdManager adManager;
+ private int levelsCompletedSinceAd = 0;
+ private int levelsRequiredBetweenAds = 3; // Show ad every 3 levels
+
+ public void OnLevelComplete()
+ {
+ levelsCompletedSinceAd++;
+
+ if (levelsCompletedSinceAd >= levelsRequiredBetweenAds)
+ {
+ ShowInterstitialOpportunistically();
+ levelsCompletedSinceAd = 0;
+ }
+
+ ProceedToNextLevel();
+ }
+
+ private void ShowInterstitialOpportunistically()
+ {
+ // Try to show, but don't wait if not ready
+ if (adManager.IsInterstitialReady())
+ {
+ adManager.ShowInterstitialAd();
+ }
+ }
+
+ private void ProceedToNextLevel()
+ {
+ // Load next level regardless of ad status
+ // Never block user flow waiting for ads
+ }
+}
+```
+
+**Why this works:**
+- Frequency capping prevents ad fatigue
+- Opportunistic showing (doesn't block if not ready)
+- User flow never interrupted
+- Predictable pattern users can adapt to
+
+### Pattern 3: Context-Aware Banner Management
+
+**Use case**: Showing banners in menus but hiding during gameplay
+
+```csharp
+public class ContextAwareBanners : MonoBehaviour
+{
+ [SerializeField] private BannerAdManager bannerManager;
+
+ public enum AppContext
+ {
+ MainMenu,
+ Playing,
+ Paused,
+ GameOver
+ }
+
+ private AppContext currentContext;
+
+ public void SetContext(AppContext newContext)
+ {
+ currentContext = newContext;
+ UpdateBannerVisibility();
+ }
+
+ private void UpdateBannerVisibility()
+ {
+ switch (currentContext)
+ {
+ case AppContext.MainMenu:
+ case AppContext.GameOver:
+ // Show banners in menu contexts
+ bannerManager.ShowBanner();
+ break;
+
+ case AppContext.Playing:
+ // Hide during active gameplay
+ bannerManager.HideBanner();
+ break;
+
+ case AppContext.Paused:
+ // Optional: Show or hide based on your preference
+ // For UX-focused: Hide
+ // For revenue-focused: Show
+ bannerManager.ShowBanner();
+ break;
+ }
+ }
+}
+```
+
+**Why this works:**
+- Banners shown only in appropriate contexts
+- Gameplay experience uninterrupted
+- Easy to adjust strategy by changing visibility logic
+- Clean separation of concerns
+
+## Frequency Management
+
+### Time-Based Frequency Capping
+
+Implement minimum time intervals between ads:
+
+```csharp
+public class FrequencyManager : MonoBehaviour
+{
+ private float minSecondsBetweenInterstitials = 300f; // 5 minutes
+ private float lastInterstitialTime = -300f; // Allow first ad immediately
+
+ public bool CanShowInterstitial()
+ {
+ float timeSinceLast = Time.realtimeSinceStartup - lastInterstitialTime;
+ return timeSinceLast >= minSecondsBetweenInterstitials;
+ }
+
+ public void RecordInterstitialShown()
+ {
+ lastInterstitialTime = Time.realtimeSinceStartup;
+ }
+}
+```
+
+**Recommended intervals:**
+- Revenue-focused: 3-5 minutes
+- Balanced: 5-7 minutes
+- UX-focused: 10+ minutes
+
+### Count-Based Frequency Capping
+
+Implement minimum actions between ads:
+
+```csharp
+private int actionsRequiredBetweenAds = 3;
+private int actionsSinceLastAd = 0;
+
+public void OnUserAction()
+{
+ actionsSinceLastAd++;
+
+ if (actionsSinceLastAd >= actionsRequiredBetweenAds)
+ {
+ // Eligible to show ad
+ TryShowAd();
+ actionsSinceLastAd = 0;
+ }
+}
+```
+
+**Examples of "actions":**
+- Levels completed
+- Games played
+- Sessions started
+- Feature uses
+
+## Error Handling and Graceful Degradation
+
+### Always Have a Fallback
+
+Never let ad failures block user flow:
+
+```csharp
+public void OnAdLoadFailed()
+{
+ // Log for debugging
+ Debug.LogWarning("Ad failed to load");
+
+ // Continue with user flow
+ ProceedWithoutAd();
+
+ // Schedule retry
+ Invoke(nameof(RetryLoadAd), 30f);
+}
+
+private void ProceedWithoutAd()
+{
+ // Your app continues normally
+ // Never block users because ads aren't available
+}
+```
+
+### Reward Users Even When Ads Fail
+
+For rewarded ads, consider granting rewards even if ad fails:
+
+```csharp
+public void OnRewardedAdShowFailed()
+{
+ // User intended to watch ad, but it failed
+ // Consider granting reward anyway (generous approach)
+ if (shouldBeGenerousOnFailure)
+ {
+ GrantReward();
+ ShowMessage("Reward granted! (Ad unavailable)");
+ }
+ else
+ {
+ ShowMessage("Ad unavailable, please try again later");
+ }
+}
+```
+
+**When to be generous:**
+- UX-focused strategy
+- High-value users
+- Failure is on your end (not user's fault)
+
+## A/B Testing Considerations
+
+### What to Test
+
+**High-impact variables:**
+1. **Frequency caps**: 3min vs 5min vs 7min between interstitials
+2. **Placement timing**: Immediate vs delayed interstitials
+3. **Reward generosity**: 1x vs 2x multiplier for rewarded ads
+4. **Banner visibility**: Always vs context-aware vs never during gameplay
+5. **Interstitial presence**: With vs without interstitials
+
+**How to test:**
+- Split users into cohorts
+- Track both revenue AND retention
+- Run tests for 7-14 days minimum
+- Focus on long-term value (LTV), not just day-1 revenue
+
+### Metrics to Track
+
+**Revenue metrics:**
+- ARPDAU (Average Revenue Per Daily Active User)
+- Impression per DAU
+- eCPM (effective Cost Per Mille)
+
+**UX metrics:**
+- D1, D7, D30 retention
+- Session length
+- Session frequency
+- Organic virality (shares, referrals)
+
+**Balance metrics:**
+- LTV (Lifetime Value)
+- ARPU / Retention ratio
+- User satisfaction scores
+
+## Common Mistakes to Avoid
+
+1. **Over-monetizing early**: Don't show tons of ads before users are engaged
+2. **Ignoring retention**: High revenue means nothing if users quit
+3. **Blocking without ads**: Never require watching ads to progress
+4. **Poor frequency capping**: Ads every minute frustrate users
+5. **Forcing rewarded ads**: Rewarded ads should always be optional
+6. **Ignoring load failures**: Always handle ad failures gracefully
+7. **One-size-fits-all**: Different user segments may need different strategies
+8. **Neglecting testing**: Always A/B test major monetization changes
+
+## Platform-Specific Considerations
+
+### iOS
+- Lower opt-in rates post-ATT (iOS 14.5+)
+- Users who deny tracking see lower-value ads
+- More privacy-conscious user base
+- Consider more UX-focused approach
+
+### Android
+- Higher ad fill rates
+- More permissive user expectations
+- Diverse hardware requires testing
+- Can push frequency slightly higher
+
+## Success Metrics by Strategy
+
+*(Example benchmarks from industry typical ranges, not LevelPlay-specific targets)*
+
+### Revenue-Focused Success
+- ARPDAU > $0.15
+- Impression/DAU > 8
+- D7 retention > 20%
+
+### UX-Focused Success
+- D7 retention > 35%
+- Session length > 15 minutes
+- Low uninstall rate (<5% weekly)
+- ARPDAU > $0.05
+
+### Balanced Success
+- ARPDAU > $0.10
+- D7 retention > 28%
+- Impression/DAU > 5
+- High user satisfaction
+
+## Final Recommendations
+
+1. **Start UX-focused**: Better to under-monetize early than drive users away
+2. **Increase gradually**: Add monetization as users become engaged
+3. **Always test**: Data beats assumptions
+4. **Watch retention closely**: It's easier to add ads than win back users
+5. **Be transparent**: Users appreciate honesty about ad-supported models
+6. **Reward patience**: Give users free options before pushing ads
+7. **Optimize continuously**: Ad strategy should evolve with your product
+
+## Additional Resources
+
+For detailed API implementation, see:
+- `rewarded-api.md`: Rewarded ad implementation
+- `interstitial-api.md`: Interstitial ad implementation
+- `banner-api.md`: Banner ad implementation
+- `initialization-api.md`: SDK setup and configuration
diff --git a/skills/levelplay-unity-integration/references/ilrd-api.md b/skills/levelplay-unity-integration/references/ilrd-api.md
new file mode 100644
index 0000000..0258ad1
--- /dev/null
+++ b/skills/levelplay-unity-integration/references/ilrd-api.md
@@ -0,0 +1,261 @@
+# Impression Level Revenue (ILR) Integration
+
+## Contents
+- [Overview](#overview)
+- [Prerequisites](#prerequisites)
+- [Key Characteristics](#key-characteristics)
+- [Implementation](#implementation)
+- [API Reference](#api-reference)
+- [Thread Safety](#thread-safety)
+- [Integration with Third-Party Analytics](#integration-with-third-party-analytics)
+- [Best Practices](#best-practices)
+- [Common Issues](#common-issues)
+- [Testing Checklist](#testing-checklist)
+
+## Overview
+
+Use the Impression Level Revenue (ILR) solution to track ad revenue at both device and impression levels, integrating with third-party analytics tools for deeper insights.
+
+## Prerequisites
+
+- LevelPlay SDK 7.0.3+ correctly integrated
+- Refer to Unity Package integration guide
+
+For more information about the Impression Level Revenue (ILR) SDK feature and pre-requisites, refer to the Impression-level revenue server-side API documentation.
+
+## Key Characteristics
+
+- **Real-time data**: Fires postbacks to inform you about displayed ads
+- **Background thread**: Callback runs on a background thread, not the Unity main thread
+- **Optional listener**: Provides information about all ad units
+- **All ad formats**: Works with Rewarded, Interstitial, and Banner ads
+
+## Implementation
+
+### Basic ILR Setup
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class ImpressionRevenueManager : MonoBehaviour
+{
+ void Start()
+ {
+ // Register impression data callback BEFORE SDK initialization
+ // This event is triggered on a background thread, not the Unity main thread.
+ LevelPlay.OnImpressionDataReady += ImpressionDataReadyEvent;
+
+ // Register initialization callbacks
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+
+ // Initialize SDK
+ LevelPlay.Init("YOUR_APP_KEY");
+ }
+
+ void OnDestroy()
+ {
+ // Unregister callbacks
+ LevelPlay.OnImpressionDataReady -= ImpressionDataReadyEvent;
+ LevelPlay.OnInitSuccess -= OnInitSuccess;
+ LevelPlay.OnInitFailed -= OnInitFailed;
+ }
+
+ private void ImpressionDataReadyEvent(LevelPlayImpressionData impressionData)
+ {
+ // IMPORTANT: This runs on a background thread, not the main thread
+ // Do not call Unity APIs directly from here
+ Debug.Log($"ILR - Ad Network: {impressionData.AdNetwork}");
+ Debug.Log($"ILR - Revenue: ${impressionData.Revenue}");
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay initialized");
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"Init failed: {error.ErrorMessage}");
+ }
+}
+```
+
+**Key points:**
+- Declare the listener **BEFORE** initializing the LevelPlay SDK to avoid any loss of information
+- Callback runs on a **background thread**
+- Don't call Unity APIs directly in the callback
+
+### Accessing Impression Data
+
+You can refer to each field separately or get all information by using the AllData property:
+
+```csharp
+private void ImpressionDataReadyEvent(LevelPlayImpressionData impressionData)
+{
+ string allData = impressionData.AllData;
+ string adNetwork = impressionData.AdNetwork;
+ double? revenue = impressionData.Revenue;
+}
+```
+
+**Important:** The returned data might include null values. To avoid potential crashes, ensure that you add protections before assigning the data.
+
+## API Reference
+
+### Event
+
+#### `LevelPlay.OnImpressionDataReady`
+
+Fired when an impression occurs and revenue data is available.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+// This event is triggered on a background thread, not the Unity main thread.
+LevelPlay.OnImpressionDataReady += ImpressionDataReadyEvent;
+```
+
+**Important:**
+- Register **BEFORE** `LevelPlay.Init()`
+- Callback runs on **background thread**
+- Fires for all ad formats (Rewarded, Interstitial, Banner)
+
+### Data Type
+
+#### `LevelPlayImpressionData`
+
+Contains detailed information about an ad impression.
+
+**Properties:**
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `AllData` | string/dictionary | All impression data as a structured object |
+| `AuctionId` | string | Unique auction identifier |
+| `MediationAdUnitName` | string | Mediation ad unit name |
+| `MediationAdUnitId` | string | Mediation ad unit identifier |
+| `AdFormat` | string | Ad format (e.g., "REWARDED", "INTERSTITIAL", "BANNER") |
+| `AdNetwork` | string | Ad network that served the ad |
+| `InstanceName` | string | Ad network instance name |
+| `InstanceId` | string | Ad network instance identifier |
+| `Country` | string | User's country code |
+| `Placement` | string | Placement name where ad was shown |
+| `Revenue` | double? | Estimated revenue in USD (nullable - check for null) |
+| `Precision` | string | Revenue precision level |
+| `Ab` | string | A/B test segment identifier |
+| `SegmentName` | string | User segment name |
+| `EncryptedCpm` | string | Encrypted CPM value |
+| `ConversionValue` | number? | iOS SKAdNetwork conversion value (iOS only) |
+| `CreativeId` | string | Creative identifier |
+
+**Example:**
+```csharp
+private void ImpressionDataReadyEvent(LevelPlayImpressionData impressionData)
+{
+ string allData = impressionData.AllData;
+ string adNetwork = impressionData.AdNetwork;
+ double? revenue = impressionData.Revenue;
+}
+```
+
+**Note:** For the full list of available ILR data, including field description and types, refer to Impression-level revenue server-side API documentation.
+
+## Thread Safety
+
+**CRITICAL:** `OnImpressionDataReady` runs on a background thread. This means:
+
+**❌ DO NOT:**
+- Call Unity APIs directly (e.g., `GameObject.Find()`, `transform.position`)
+- Access Unity components or game objects
+- Update UI elements directly
+- Call coroutines
+
+**✅ DO:**
+- Queue data for processing on main thread
+- Use thread-safe operations
+- Check for null values before using data
+
+## Integration with Third-Party Analytics
+
+### Firebase Analytics Example
+
+The following example details how to integrate the Impression Level Revenue SDK API data with Google Analytics for Firebase. You can use it as-is or make any required changes to integrate with third-party reporting tools or your own proprietary optimization tools and databases.
+
+**Important:** Ensure that the inside parameters aren't null.
+
+```csharp
+private void ImpressionDataReadyEvent(LevelPlayImpressionData impressionData)
+{
+ Debug.Log("unity-script: ImpressionDataReadyEvent impressionData = " + impressionData);
+
+ if (impressionData != null)
+ {
+ Firebase.Analytics.Parameter[] AdParameters = {
+ new Firebase.Analytics.Parameter("ad_platform", "LevelPlay"),
+ new Firebase.Analytics.Parameter("ad_source", impressionData.AdNetwork),
+ new Firebase.Analytics.Parameter("ad_format", impressionData.AdFormat),
+ new Firebase.Analytics.Parameter("ad_unit_name", impressionData.InstanceName),
+ new Firebase.Analytics.Parameter("currency", "USD"),
+ new Firebase.Analytics.Parameter("value", impressionData.Revenue ?? 0) // Add protection for null values
+ };
+
+ Firebase.Analytics.FirebaseAnalytics.LogEvent("custom_ad_impression", AdParameters);
+ }
+}
+```
+
+### Integration with Other Tools
+
+After you implement the ImpressionDataListener, you can send the impression data to:
+- Your own proprietary BI tools and data warehouses
+- Third-party analytics platforms
+- Attribution providers
+- Custom backend services
+
+## Best Practices
+
+1. **Register before Init**: Always register `OnImpressionDataReady` before calling `LevelPlay.Init()`
+2. **Handle background thread**: Don't call Unity APIs directly in the callback
+3. **Check for null values**: Revenue and other properties may be null - always check before using
+4. **Protect against crashes**: Add null checks to avoid potential crashes
+5. **Unregister on destroy**: Prevent memory leaks by unregistering in `OnDestroy()`
+
+## Common Issues
+
+### Issue: Callback never fires
+
+**Possible causes:**
+- Not registered before `LevelPlay.Init()`
+- SDK not initialized successfully
+- No ads shown yet
+
+**Solutions:**
+- Register callback before calling `Init()`
+- Verify `OnInitSuccess` fires
+- Show an ad and check if callback fires
+
+### Issue: Unity APIs crash in callback
+
+**Cause:** Calling Unity APIs from background thread
+
+**Solution:** Don't call Unity APIs directly in the callback. Queue data for main thread processing if needed.
+
+### Issue: Revenue is null
+
+**Cause:** Some ad networks don't provide revenue data
+
+**Solution:** Always check `impressionData.Revenue.HasValue` or use `impressionData.Revenue ?? 0` to provide a default value
+
+## Testing Checklist
+
+- [ ] `OnImpressionDataReady` registered before `Init()`
+- [ ] Callback fires when ads are shown
+- [ ] Revenue data is received (check for null)
+- [ ] Null checks added to prevent crashes
+- [ ] Analytics/backend integration works correctly
+- [ ] No crashes from Unity API calls in callback
+- [ ] Callback unregistered in `OnDestroy()`
+- [ ] Tested with all ad formats (Rewarded, Interstitial, Banner)
diff --git a/skills/levelplay-unity-integration/references/initialization-api.md b/skills/levelplay-unity-integration/references/initialization-api.md
new file mode 100644
index 0000000..99cef50
--- /dev/null
+++ b/skills/levelplay-unity-integration/references/initialization-api.md
@@ -0,0 +1,630 @@
+# LevelPlay SDK Initialization API
+
+## Contents
+- [Overview](#overview)
+- [Basic Initialization](#basic-initialization)
+- [Advanced Initialization](#advanced-initialization)
+- [API Reference](#api-reference)
+- [Initialization Timing](#initialization-timing)
+- [Best Practices](#best-practices)
+- [Testing](#testing)
+- [Migration from IronSource.* APIs](#migration-from-ironsource-apis)
+- [Error Code Reference](#error-code-reference)
+
+## Overview
+
+Proper SDK initialization is critical for LevelPlay to function correctly. This reference covers basic initialization, advanced options, and best practices.
+
+## Basic Initialization
+
+### Minimum Required Setup
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class LevelPlayInitializer : MonoBehaviour
+{
+ [SerializeField] private string appKey = "YOUR_APP_KEY";
+
+ void Start()
+ {
+ // Register initialization callbacks
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+
+ // Initialize SDK
+ LevelPlay.Init(appKey);
+ }
+
+ void OnDestroy()
+ {
+ // Unregister callbacks
+ LevelPlay.OnInitSuccess -= OnInitSuccess;
+ LevelPlay.OnInitFailed -= OnInitFailed;
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay SDK initialized successfully");
+ // SDK is ready - you can now create ad objects
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
+ }
+}
+```
+
+**Key points:**
+- Register `OnInitSuccess` and `OnInitFailed` callbacks before calling `Init()`
+- The `appKey` is obtained from your LevelPlay dashboard
+- Call `Init()` as early as possible (ideally in the first scene)
+- Only create ad objects after `OnInitSuccess` fires
+
+## Advanced Initialization
+
+### With User ID
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class AdvancedInitializer : MonoBehaviour
+{
+ [SerializeField] private string appKey = "YOUR_APP_KEY";
+
+ void Start()
+ {
+ // Register callbacks
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+
+ // Initialize SDK with optional user ID
+ string userId = "user_12345"; // Your internal user ID
+ LevelPlay.Init(appKey, userId);
+ }
+
+ void OnDestroy()
+ {
+ LevelPlay.OnInitSuccess -= OnInitSuccess;
+ LevelPlay.OnInitFailed -= OnInitFailed;
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay initialized successfully");
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"Init failed: {error.ErrorMessage}");
+ }
+}
+```
+
+**When to use:** When you need to track users across sessions or implement server-side verification for rewards.
+
+### Complete Production-Ready Example
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class ProductionLevelPlayInitializer : MonoBehaviour
+{
+ [Header("Configuration")]
+ [SerializeField] private string appKey = "YOUR_APP_KEY";
+
+ void Awake()
+ {
+ // Ensure this object persists across scenes
+ DontDestroyOnLoad(gameObject);
+
+ // Initialize as early as possible
+ InitializeLevelPlay();
+ }
+
+ private void InitializeLevelPlay()
+ {
+ // Register initialization callbacks
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+
+ // Initialize SDK with optional user ID
+ if (HasUserId())
+ {
+ string userId = GetUserId();
+ LevelPlay.Init(appKey, userId);
+ Debug.Log($"LevelPlay SDK initialization started with user ID: {userId}");
+ }
+ else
+ {
+ LevelPlay.Init(appKey);
+ Debug.Log("LevelPlay SDK initialization started");
+ }
+ }
+
+ void OnDestroy()
+ {
+ // Unregister callbacks
+ LevelPlay.OnInitSuccess -= OnInitSuccess;
+ LevelPlay.OnInitFailed -= OnInitFailed;
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay SDK initialization completed");
+
+ // SDK is ready - create ad objects here
+ CreateAdObjects();
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
+
+ // Optionally retry initialization after delay
+ Invoke(nameof(RetryInitialization), 5f);
+ }
+
+ private void RetryInitialization()
+ {
+ Debug.Log("Retrying LevelPlay initialization...");
+ LevelPlay.Init(appKey);
+ }
+
+ private bool HasUserId()
+ {
+ // Check if user is logged in and has an ID
+ return !string.IsNullOrEmpty(PlayerPrefs.GetString("UserID", ""));
+ }
+
+ private string GetUserId()
+ {
+ // Get your internal user ID
+ return PlayerPrefs.GetString("UserID", "");
+ }
+
+ private void CreateAdObjects()
+ {
+ // Create your ad objects here after init succeeds
+ // Example:
+ // rewardedAd = new LevelPlayRewardedAd("rewarded_ad_unit_id");
+ // interstitialAd = new LevelPlayInterstitialAd("interstitial_ad_unit_id");
+ Debug.Log("Creating ad objects...");
+ }
+}
+```
+
+## API Reference
+
+### Core Methods
+
+#### `LevelPlay.Init(string appKey, string userId = null)`
+Initialize the LevelPlay SDK.
+
+**Parameters:**
+- `appKey`: Your application key from LevelPlay dashboard
+- `userId` (optional): Your internal user ID for tracking and server-side reward verification
+
+**Usage:**
+```csharp
+// Basic initialization
+LevelPlay.Init("your_app_key");
+
+// With user ID
+LevelPlay.Init("your_app_key", "user_12345");
+```
+
+**Important:** Register `OnInitSuccess` and `OnInitFailed` callbacks before calling `Init()`.
+
+**When to use user ID:**
+- Server-side reward verification
+- User-level analytics
+- Cross-session tracking
+
+#### `LevelPlay.SetSegment(LevelPlaySegment segment)`
+Set user segmentation data for improved ad targeting and reporting.
+
+**Parameters:**
+- `segment`: A `LevelPlaySegment` object containing user segment information
+
+**Usage:**
+```csharp
+// Create a segment with user data
+var segment = new LevelPlaySegment();
+segment.SegmentName = "high_spenders";
+segment.Level = 25;
+segment.UserCreationDate = 1609459200000; // Unix timestamp in milliseconds (Jan 1, 2021)
+segment.IapTotal = 49.99;
+segment.IsPaying = 1; // 0 or 1
+
+// Set custom parameters
+segment.SetCustom("vip_tier", "gold");
+segment.SetCustom("gameplay_hours", "150");
+
+// Apply segment before or after initialization
+LevelPlay.SetSegment(segment);
+```
+
+**When to call:**
+- Can be called before or after SDK initialization
+- Update whenever user segment changes (level up, first purchase, etc.)
+- Helps LevelPlay optimize ad delivery and reporting
+
+**LevelPlaySegment properties:**
+- `SegmentName` (string): Name of the segment (e.g., "high_spenders", "casual_players")
+- `Level` (int): User's current level in the game
+- `UserCreationDate` (long): Unix timestamp of when user created their account
+- `IapTotal` (double): Total amount spent on in-app purchases
+- `IsPaying` (int): Whether the user has made any purchases (0 or 1)
+
+**LevelPlaySegment methods:**
+- `SetCustom(string key, string value)`: Add custom key-value parameters for advanced segmentation
+
+**Custom parameter limits:**
+- Maximum 5 custom parameters per segment
+
+**Best practices:**
+- Update segments when significant user milestones occur
+- Use meaningful segment names that describe user behavior
+- Keep custom parameters descriptive and consistent
+- Stay within the 5 custom parameter limit per segment
+
+#### `LevelPlay.SetPauseGame(bool pause)`
+Pause Unity 3D game activities (except ad callbacks) during ad presentation.
+
+**Platform:** iOS only (SDK 8.5.0+)
+
+**Parameters:**
+- `pause`: Set to `true` to pause game activities during ads
+
+**Usage:**
+```csharp
+LevelPlay.SetPauseGame(true);
+```
+
+**Notes:**
+- iOS only
+- Can be called once per session, before or after SDK initialization
+- Game resumes automatically when ad closes
+- Affects Rewarded and Interstitial ads
+
+#### `LevelPlay.SetMetaData(string key, params string[] values)`
+Set metadata for SDK configuration.
+
+**Parameters:**
+- `key`: The metadata key
+- `values`: Array of values
+
+**Use Cases:**
+
+**1. Server-to-Server Rewarded Callbacks (SDK 8.11.0+)**
+
+Pass custom parameters for rewarded ad callbacks:
+
+```csharp
+LevelPlay.SetMetaData("LevelPlay_Rewarded_Server_Params", new [] { "key1=value1", "key2=value2" });
+```
+
+**2. Test Suite Activation**
+
+Enable the LevelPlay Test Suite:
+
+```csharp
+LevelPlay.SetMetaData("is_test_suite", "enable");
+LevelPlay.Init(appKey);
+```
+
+**Note:** For Test Suite, must be called before `Init()`.
+
+#### `LevelPlay.SetDynamicUserId(string userId)`
+Set a dynamic user ID that can be changed during the session for server-side rewarded ad verification.
+
+**Parameters:**
+- `userId`: The dynamic user ID to set
+
+**Returns:** `bool` - true if successfully set, false otherwise
+
+**Usage:**
+```csharp
+string newUserId = "user_67890";
+bool success = LevelPlay.SetDynamicUserId(newUserId);
+if (success)
+{
+ Debug.Log($"Dynamic user ID set to: {newUserId}");
+}
+```
+
+**When to use:**
+- Server-side reward verification for rewarded ads
+- When user ID changes mid-session (e.g., user logs in after guest play)
+- Must be set before showing rewarded ads that require verification
+
+**Important:** This is different from the userId passed to `Init()`. The dynamic user ID can be changed during the session and is used specifically for server-to-server reward callbacks.
+
+#### `LevelPlay.LaunchTestSuite()`
+Launch the LevelPlay Test Suite for testing ad integrations on device.
+
+**Usage:**
+```csharp
+void OnInitSuccess(LevelPlayConfiguration config)
+{
+ Debug.Log("SDK initialized");
+
+ #if UNITY_EDITOR || DEVELOPMENT_BUILD
+ // Launch Test Suite for testing
+ LevelPlay.LaunchTestSuite();
+ #endif
+}
+```
+
+**Requirements:**
+- SDK must be initialized before calling this method
+- Test Suite must be enabled via metadata before initialization:
+ ```csharp
+ LevelPlay.SetMetaData("is_test_suite", "enable");
+ LevelPlay.Init(appKey);
+ ```
+
+**When to use:**
+- Testing ad networks on real devices
+- Verifying ad fill and display for different networks
+- Debugging ad integration issues
+- QA testing before release
+
+**Notes:**
+- Opens a UI overlay with test controls
+- Allows testing each ad network individually
+- Only available on device builds (not Editor)
+- Should be removed or disabled in production builds
+
+#### `LevelPlay.SetAdaptersDebug(bool enabled)`
+Enable or disable debug logging for ad network adapters.
+
+**Parameters:**
+- `enabled`: Set to `true` to enable adapter debug logs, `false` to disable
+
+**Usage:**
+```csharp
+void Awake()
+{
+ #if UNITY_EDITOR || DEVELOPMENT_BUILD
+ // Enable adapter debug logs for development
+ LevelPlay.SetAdaptersDebug(true);
+ #endif
+
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.Init(appKey);
+}
+```
+
+**When to use:**
+- Debugging ad network integration issues
+- Investigating ad load failures
+- Understanding adapter behavior
+- Development and QA testing
+
+**Notes:**
+- Increases console log verbosity
+- Helps diagnose network-specific issues
+- Should be disabled in production builds
+- Can be called before or after SDK initialization
+
+#### `LevelPlay.SetNetworkData(string networkKey, string networkData)`
+Set custom data for specific ad networks.
+
+**Parameters:**
+- `networkKey`: The ad network identifier (e.g., "UnityAds", "AdMob")
+- `networkData`: Custom data string for the network
+
+**Usage:**
+```csharp
+// Set custom network-specific configuration
+LevelPlay.SetNetworkData("UnityAds", "customValue");
+LevelPlay.SetNetworkData("AdMob", "extraConfig");
+```
+
+**When to use:**
+- Passing custom configuration to specific ad networks
+- Advanced network-specific setup
+- When network adapter requires additional data
+
+**Notes:**
+- Call before SDK initialization
+- Consult individual network adapter documentation for supported data
+- Not commonly needed for standard integrations
+
+### Events
+
+#### `LevelPlay.OnInitSuccess`
+Fired when SDK initialization completes successfully.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+LevelPlay.OnInitSuccess += (config) =>
+{
+ Debug.Log("SDK ready");
+ Debug.Log($"Unity Version: {LevelPlay.UnityVersion}");
+ Debug.Log($"Plugin Version: {LevelPlay.PluginVersion}");
+ // Create ad objects here
+};
+```
+
+**Note:** `UnityVersion` and `PluginVersion` are static properties on the `LevelPlay` class, not on `LevelPlayConfiguration`.
+
+#### `LevelPlay.OnInitFailed`
+Fired when SDK initialization fails.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+LevelPlay.OnInitFailed += (error) =>
+{
+ Debug.LogError($"Init failed: Code {error.ErrorCode}, Message: {error.ErrorMessage}");
+ // Optionally retry
+};
+```
+
+**LevelPlayInitError properties:**
+- `error.ErrorCode`: Numeric error code
+- `error.ErrorMessage`: Description of the initialization error
+
+## Initialization Timing
+
+### Early Initialization (Recommended)
+
+Initialize in your first scene, as early as possible:
+
+```csharp
+void Awake()
+{
+ DontDestroyOnLoad(gameObject);
+
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+ LevelPlay.Init(appKey);
+}
+```
+
+**Why:** Ads start loading immediately, maximizing availability when needed.
+
+### Delayed Initialization (Advanced)
+
+In some cases, you might delay initialization:
+
+```csharp
+void Start()
+{
+ // Wait for user to complete onboarding first
+ StartCoroutine(InitializeAfterOnboarding());
+}
+
+IEnumerator InitializeAfterOnboarding()
+{
+ yield return new WaitUntil(() => HasCompletedOnboarding());
+
+ // Now initialize
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+ LevelPlay.Init(appKey);
+}
+```
+
+**When to use:**
+- Waiting for user consent decisions
+- Completing critical onboarding first
+- Reducing initial app load time
+
+**Trade-off:** Ads won't be ready as quickly.
+
+## Best Practices
+
+1. **Initialize early**: Call `Init()` in your first scene's `Awake()` or `Start()`
+2. **Persist the initializer**: Use `DontDestroyOnLoad()` to prevent re-initialization
+3. **Register callbacks first**: Always register `OnInitSuccess` and `OnInitFailed` before calling `Init()`
+4. **Create ads after init**: Only create ad objects after `OnInitSuccess` fires
+5. **Set user ID if available**: Helps with analytics and server-side verification
+6. **Initialize only once**: Don't re-initialize the SDK across scenes
+7. **Handle failures**: Implement `OnInitFailed` to catch and log initialization errors
+
+## Testing
+
+### Development Checklist
+- [ ] SDK initializes successfully (check logs)
+- [ ] `OnInitSuccess` callback fires
+- [ ] Ads can be created after initialization
+- [ ] User ID is set correctly (if used)
+- [ ] Initialization happens early in app lifecycle
+- [ ] Error handling works (`OnInitFailed` implemented)
+
+### Common Issues
+
+**Issue: Ads not loading after initialization**
+- Check that App Key is correct
+- Verify ad objects are created **after** `OnInitSuccess` fires
+- Check console for initialization errors
+- Enable test mode in LevelPlay dashboard
+
+**Issue: Initialization fails**
+- Verify App Key matches LevelPlay dashboard exactly
+- Check internet connectivity
+- Look at `LevelPlayInitError.ErrorMessage` for specific error
+- Ensure LevelPlay SDK package is properly installed
+
+**Issue: User ID not being tracked**
+- Verify user ID is passed to `Init(appKey, userId)` as the second parameter
+- Check that user ID is non-empty and unique
+- Verify format meets LevelPlay requirements (alphanumeric, max 64 chars)
+
+**Issue: Ads not ready when needed**
+- Initialize earlier in the app lifecycle
+- Create ad objects immediately in `OnInitSuccess`
+- Load ads immediately after creating ad objects
+
+## Migration from IronSource.* APIs
+
+If you're migrating from deprecated IronSource APIs:
+
+**Old (Deprecated):**
+```csharp
+IronSource.Agent.validateIntegration();
+IronSource.Agent.init(appKey);
+```
+
+**New (Current):**
+```csharp
+LevelPlay.OnInitSuccess += OnInitSuccess;
+LevelPlay.OnInitFailed += OnInitFailed;
+LevelPlay.Init(appKey);
+```
+
+**Key differences:**
+- Init errors are reported via `OnInitFailed`; for integration validation use the LevelPlay Test Suite (`LaunchTestSuite()`)
+- Must register callbacks before calling `Init()`
+- Ad objects created explicitly after initialization (not automatic background loading)
+
+## Error Code Reference
+
+When ad operations fail, `LevelPlayAdError` contains an `ErrorCode` property with one of these values:
+
+| Code | Ad Formats | Description |
+|------|-----------|-------------|
+| **508** | N/A | Init failure of mediation/Network, Calling Demand Only API in non Demand Only mode, Calling non Demand Only API in Demand Only mode |
+| **509** | Interstitial, Rewarded | Show Fail: No ads to show |
+| **510** | Interstitial, Rewarded, Banner | Load Fail: Server response failed |
+| **520** | Interstitial, Rewarded | Show Fail: No internet connection; ShouldTrackNetworkState is enabled |
+| **524** | Interstitial, Rewarded | Show Fail: Placement has reached its limit as defined per pace or capping limit |
+| **526** | Interstitial, Rewarded | Show Fail: Ad unit has reached its daily cap per session |
+| **604** | Banner | Can't load because the placement is capped |
+| **605** | Banner | Unexpected exception while loading the banner |
+| **606** | Banner | No banner fill on all the networks on the first load |
+| **1007** | Interstitial, Rewarded | Auction Fail: Auction request did not contain all required information |
+| **1022** | Rewarded | Show Fail: Cannot show a rewarded ad while another rewarded ad is showing |
+| **1023** | Rewarded | Show Fail: Show called when there are no available ads to show |
+| **1035** | Interstitial | Empty Waterfall |
+| **1036** | Interstitial | Show Fail: Cannot show an interstitial while another interstitial is showing |
+| **1037** | Interstitial | Load Fail: Cannot load an interstitial while another interstitial is loading |
+
+**Usage in error callbacks:**
+```csharp
+private void OnAdLoadFailed(LevelPlayAdError error)
+{
+ Debug.LogWarning($"Ad load failed: Code {error.ErrorCode}, Message: {error.ErrorMessage}");
+
+ // Handle specific errors
+ if (error.ErrorCode == 510)
+ {
+ // Server response failed - retry with backoff
+ }
+ else if (error.ErrorCode == 520)
+ {
+ // No internet connection - wait for connectivity
+ }
+}
+```
diff --git a/skills/levelplay-unity-integration/references/interstitial-api.md b/skills/levelplay-unity-integration/references/interstitial-api.md
new file mode 100644
index 0000000..7a99111
--- /dev/null
+++ b/skills/levelplay-unity-integration/references/interstitial-api.md
@@ -0,0 +1,887 @@
+# Interstitial Ads API Reference
+
+## Contents
+- [Overview](#overview)
+- [Key Characteristics](#key-characteristics)
+- [Implementation Pattern](#implementation-pattern)
+- [API Reference](#api-reference)
+- [Data Types](#data-types)
+- [Best Practices](#best-practices)
+- [Common Issues](#common-issues)
+- [Testing Checklist](#testing-checklist)
+
+## Overview
+
+Interstitial ads are full-screen video or static ads shown at natural transition points in your app. They provide good revenue while maintaining acceptable user experience when placed thoughtfully.
+
+## Key Characteristics
+
+- **Full-screen**: Takes over the entire screen
+- **Skippable**: User can close after viewing
+- **Best for**: Level transitions, game over, menu navigation, session breaks
+
+## Implementation Pattern
+
+### Basic Interstitial Implementation
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class InterstitialAdManager : MonoBehaviour
+{
+ private LevelPlayInterstitialAd interstitialAd;
+ private string adUnitId = "YOUR_INTERSTITIAL_AD_UNIT_ID";
+
+ void Start()
+ {
+ // Create the interstitial ad object using constructor
+ interstitialAd = new LevelPlayInterstitialAd(adUnitId);
+
+ // Register event listeners
+ interstitialAd.OnAdLoaded += OnAdLoaded;
+ interstitialAd.OnAdLoadFailed += OnAdLoadFailed;
+ interstitialAd.OnAdDisplayed += OnAdDisplayed;
+ interstitialAd.OnAdDisplayFailed += OnAdDisplayFailed;
+ interstitialAd.OnAdClicked += OnAdClicked;
+ interstitialAd.OnAdClosed += OnAdClosed;
+ interstitialAd.OnAdInfoChanged += OnAdInfoChanged; // Fires when the loaded ad swaps to a higher-paying network
+
+ // Load the ad
+ LoadAd();
+ }
+
+ void OnDestroy()
+ {
+ // Unregister event listeners
+ if (interstitialAd != null)
+ {
+ interstitialAd.OnAdLoaded -= OnAdLoaded;
+ interstitialAd.OnAdLoadFailed -= OnAdLoadFailed;
+ interstitialAd.OnAdDisplayed -= OnAdDisplayed;
+ interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed;
+ interstitialAd.OnAdClicked -= OnAdClicked;
+ interstitialAd.OnAdClosed -= OnAdClosed;
+ interstitialAd.OnAdInfoChanged -= OnAdInfoChanged;
+ }
+ }
+
+ public void LoadAd()
+ {
+ Debug.Log("Loading interstitial ad...");
+ interstitialAd.LoadAd();
+ }
+
+ public void ShowAd()
+ {
+ if (interstitialAd.IsAdReady())
+ {
+ Debug.Log("Showing interstitial ad");
+ interstitialAd.ShowAd();
+ }
+ else
+ {
+ Debug.LogWarning("Interstitial ad is not ready yet");
+ }
+ }
+
+ // Event Callbacks
+ private void OnAdLoaded(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial ad loaded successfully");
+ }
+
+ private void OnAdLoadFailed(LevelPlayAdError error)
+ {
+ Debug.LogWarning($"Interstitial ad failed to load: {error.ErrorMessage}");
+ // Retry loading after a delay
+ Invoke(nameof(LoadAd), 30f);
+ }
+
+ private void OnAdDisplayed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial ad displayed");
+ }
+
+ private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
+ {
+ Debug.LogError($"Interstitial ad failed to display: {error.ErrorMessage}");
+ // Load a new ad
+ LoadAd();
+ }
+
+ private void OnAdClicked(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial ad clicked");
+ }
+
+ private void OnAdClosed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial ad closed");
+ // Load the next ad
+ LoadAd();
+ }
+
+ private void OnAdInfoChanged(LevelPlayAdInfo adInfo)
+ {
+ // Optional: log or update analytics with the latest revenue estimate
+ Debug.Log($"Interstitial ad info changed - network: {adInfo.AdNetwork}, revenue: ${adInfo.Revenue}");
+ }
+}
+```
+
+### Advanced: Frequency Capping
+
+Implement time-based frequency capping to avoid showing ads too frequently:
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class FrequencyCappedInterstitialManager : MonoBehaviour
+{
+ private LevelPlayInterstitialAd interstitialAd;
+ private string adUnitId = "YOUR_INTERSTITIAL_AD_UNIT_ID";
+
+ // Frequency capping settings
+ private float minTimeBetweenAds = 300f; // 5 minutes in seconds
+ private float lastAdShownTime = -999f; // Time when last ad was shown
+
+ void Start()
+ {
+ // Create interstitial ad object
+ interstitialAd = new LevelPlayInterstitialAd(adUnitId);
+
+ // Register callbacks
+ interstitialAd.OnAdLoaded += OnAdLoaded;
+ interstitialAd.OnAdLoadFailed += OnAdLoadFailed;
+ interstitialAd.OnAdDisplayed += OnAdDisplayed;
+ interstitialAd.OnAdClosed += OnAdClosed;
+ interstitialAd.OnAdDisplayFailed += OnAdDisplayFailed;
+
+ // Load first ad
+ interstitialAd.LoadAd();
+ }
+
+ void OnDestroy()
+ {
+ if (interstitialAd != null)
+ {
+ interstitialAd.OnAdLoaded -= OnAdLoaded;
+ interstitialAd.OnAdLoadFailed -= OnAdLoadFailed;
+ interstitialAd.OnAdDisplayed -= OnAdDisplayed;
+ interstitialAd.OnAdClosed -= OnAdClosed;
+ interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed;
+ }
+ }
+
+ public void TryShowInterstitial()
+ {
+ // Check if enough time has passed since last ad
+ float timeSinceLastAd = Time.time - lastAdShownTime;
+
+ if (timeSinceLastAd < minTimeBetweenAds)
+ {
+ Debug.Log($"Frequency cap: {minTimeBetweenAds - timeSinceLastAd:F0}s until next ad");
+ return;
+ }
+
+ // Check if ad is ready
+ if (interstitialAd.IsAdReady())
+ {
+ interstitialAd.ShowAd();
+ }
+ else
+ {
+ Debug.LogWarning("Interstitial not ready, loading...");
+ interstitialAd.LoadAd();
+ }
+ }
+
+ private void OnAdLoaded(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial loaded");
+ }
+
+ private void OnAdLoadFailed(LevelPlayAdError error)
+ {
+ Debug.LogWarning($"Interstitial load failed: {error.ErrorMessage}");
+ Invoke(nameof(LoadAd), 30f);
+ }
+
+ private void OnAdDisplayed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial displayed");
+ lastAdShownTime = Time.time; // Record when ad was shown
+ }
+
+ private void OnAdClosed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial closed");
+ // Load next ad
+ interstitialAd.LoadAd();
+ }
+
+ private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
+ {
+ Debug.LogError($"Interstitial display failed: {error.ErrorMessage}");
+ interstitialAd.LoadAd();
+ }
+
+ private void LoadAd()
+ {
+ if (interstitialAd != null)
+ {
+ interstitialAd.LoadAd();
+ }
+ }
+}
+```
+
+### Advanced: Multiple Placements
+
+Track different interstitial placements for analytics:
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class MultiPlacementInterstitialManager : MonoBehaviour
+{
+ private LevelPlayInterstitialAd interstitialAd;
+ private string adUnitId = "YOUR_INTERSTITIAL_AD_UNIT_ID";
+
+ // Placement names
+ private const string PLACEMENT_LEVEL_COMPLETE = "level_complete";
+ private const string PLACEMENT_GAME_OVER = "game_over";
+ private const string PLACEMENT_MAIN_MENU = "main_menu";
+
+ // Frequency capping per placement
+ private float minTimeBetweenAds = 300f;
+ private float lastAdTime = -999f;
+
+ void Start()
+ {
+ interstitialAd = new LevelPlayInterstitialAd(adUnitId);
+
+ interstitialAd.OnAdLoaded += OnAdLoaded;
+ interstitialAd.OnAdLoadFailed += OnAdLoadFailed;
+ interstitialAd.OnAdDisplayed += OnAdDisplayed;
+ interstitialAd.OnAdClosed += OnAdClosed;
+ interstitialAd.OnAdDisplayFailed += OnAdDisplayFailed;
+
+ interstitialAd.LoadAd();
+ }
+
+ void OnDestroy()
+ {
+ if (interstitialAd != null)
+ {
+ interstitialAd.OnAdLoaded -= OnAdLoaded;
+ interstitialAd.OnAdLoadFailed -= OnAdLoadFailed;
+ interstitialAd.OnAdDisplayed -= OnAdDisplayed;
+ interstitialAd.OnAdClosed -= OnAdClosed;
+ interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed;
+ }
+ }
+
+ // Public methods for different placements
+ public void ShowOnLevelComplete()
+ {
+ TryShowWithPlacement(PLACEMENT_LEVEL_COMPLETE);
+ }
+
+ public void ShowOnGameOver()
+ {
+ TryShowWithPlacement(PLACEMENT_GAME_OVER);
+ }
+
+ public void ShowOnMainMenu()
+ {
+ TryShowWithPlacement(PLACEMENT_MAIN_MENU);
+ }
+
+ private void TryShowWithPlacement(string placementName)
+ {
+ // Check frequency cap
+ if (Time.time - lastAdTime < minTimeBetweenAds)
+ {
+ Debug.Log($"Frequency cap active for {placementName}");
+ return;
+ }
+
+ // Check placement capping
+ if (LevelPlayInterstitialAd.IsPlacementCapped(placementName))
+ {
+ Debug.Log($"Placement {placementName} is capped");
+ return;
+ }
+
+ // Check if ad ready
+ if (interstitialAd.IsAdReady())
+ {
+ Debug.Log($"Showing interstitial with placement: {placementName}");
+ interstitialAd.ShowAd(placementName: placementName);
+ }
+ else
+ {
+ Debug.LogWarning("Interstitial not ready");
+ }
+ }
+
+ private void OnAdLoaded(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial loaded");
+ }
+
+ private void OnAdLoadFailed(LevelPlayAdError error)
+ {
+ Debug.LogWarning($"Interstitial load failed: {error.ErrorMessage}");
+ Invoke(nameof(LoadAd), 30f);
+ }
+
+ private void OnAdDisplayed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial displayed");
+ lastAdTime = Time.time;
+ }
+
+ private void OnAdClosed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Interstitial closed");
+ interstitialAd.LoadAd();
+ }
+
+ private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
+ {
+ Debug.LogError($"Interstitial display failed: {error.ErrorMessage}");
+ interstitialAd.LoadAd();
+ }
+
+ private void LoadAd()
+ {
+ if (interstitialAd != null)
+ {
+ interstitialAd.LoadAd();
+ }
+ }
+}
+```
+
+## API Reference
+
+### Constructor
+
+#### `new LevelPlayInterstitialAd(string adUnitId)`
+Create an interstitial ad object.
+
+**Parameters:**
+- `adUnitId`: The ad unit identifier from your LevelPlay dashboard
+
+**Returns:** `LevelPlayInterstitialAd` object
+
+**Usage:**
+```csharp
+LevelPlayInterstitialAd interstitialAd = new LevelPlayInterstitialAd("your_ad_unit_id");
+```
+
+**Important:** Call this only after `LevelPlay.Init()` has completed successfully (in the `OnInitSuccess` callback).
+
+#### `new LevelPlayInterstitialAd(string adUnitId, Config config)`
+Create an interstitial ad object with custom configuration.
+
+**Parameters:**
+- `adUnitId`: The ad unit identifier
+- `config`: Optional configuration object
+
+**Usage:**
+```csharp
+var configBuilder = new LevelPlayInterstitialAd.Config.Builder();
+configBuilder.SetBidFloor(1.0); // Minimum bid price in USD
+var config = configBuilder.Build();
+LevelPlayInterstitialAd interstitialAd = new LevelPlayInterstitialAd("your_ad_unit_id", config);
+```
+
+### Configuration Builder
+
+#### `LevelPlayInterstitialAd.Config.Builder()`
+Create a configuration builder for interstitial ads.
+
+**Returns:** `LevelPlayInterstitialAd.Config.Builder` object
+
+**Usage:**
+```csharp
+var configBuilder = new LevelPlayInterstitialAd.Config.Builder();
+```
+
+**Set minimum bid price:**
+```csharp
+configBuilder.SetBidFloor(1.0); // Minimum bid price in USD ($1.00 CPM)
+```
+
+**When to use:** Set a minimum bid price in USD for ad requests.
+
+#### `Build()`
+Build the configuration object.
+
+**Returns:** `LevelPlayInterstitialAd.Config` object
+
+**Usage:**
+```csharp
+var config = configBuilder.Build();
+```
+
+### Core Methods
+
+#### `LoadAd()`
+Load an interstitial ad.
+
+**Usage:**
+```csharp
+interstitialAd.LoadAd();
+```
+
+**When to call:**
+- After SDK initialization completes
+- After showing an ad (to load the next one)
+- After a load or display failure
+
+#### `ShowAd()`
+Show the interstitial ad without a placement name.
+
+**Usage:**
+```csharp
+if (interstitialAd.IsAdReady())
+{
+ interstitialAd.ShowAd();
+}
+```
+
+**Important:** Always check `IsAdReady()` before calling `ShowAd()`.
+
+#### `ShowAd(placementName: string)`
+Show the interstitial ad with a named placement for analytics tracking.
+
+**Parameters:**
+- `placementName`: Placement name configured in LevelPlay dashboard
+
+**Usage:**
+```csharp
+interstitialAd.ShowAd(placementName: "level_complete");
+```
+
+**When to use:** When tracking multiple placements within the same ad unit for analytics.
+
+#### `IsAdReady()`
+Check if an interstitial ad is loaded and ready to show.
+
+**Returns:** `bool` - true if ready, false otherwise
+
+**Usage:**
+```csharp
+if (interstitialAd.IsAdReady())
+{
+ // Ad is ready to show
+ interstitialAd.ShowAd();
+}
+else
+{
+ // Ad not ready, load it
+ interstitialAd.LoadAd();
+}
+```
+
+#### `LevelPlayInterstitialAd.IsPlacementCapped(string placementName)` (Static)
+Check if a placement has reached its capping limit.
+
+**Parameters:**
+- `placementName`: The placement name to check
+
+**Returns:** `bool` - true if capped, false otherwise
+
+**Usage:**
+```csharp
+if (LevelPlayInterstitialAd.IsPlacementCapped("level_complete"))
+{
+ Debug.Log("Level complete placement is capped");
+}
+```
+
+**When to use:** Before showing an ad to check if the placement frequency cap is reached.
+
+### Events
+
+All events are properties of the `LevelPlayInterstitialAd` object.
+
+**Threading:** All ad callbacks run on the Unity main thread, so you can safely call Unity APIs (update UI, access GameObjects, etc.) directly in these callbacks. This is different from `LevelPlay.OnImpressionDataReady` which runs on a background thread.
+
+#### `OnAdLoaded`
+Fired when an interstitial ad is successfully loaded.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+interstitialAd.OnAdLoaded += (adInfo) =>
+{
+ Debug.Log("Interstitial ad loaded");
+};
+```
+
+#### `OnAdLoadFailed`
+Fired when an interstitial ad fails to load.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+interstitialAd.OnAdLoadFailed += (error) =>
+{
+ Debug.LogWarning($"Load failed: {error.ErrorMessage}");
+};
+```
+
+**Best practice:** Implement retry logic with exponential backoff.
+
+#### `OnAdDisplayed`
+Fired when an interstitial ad is displayed on screen.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+interstitialAd.OnAdDisplayed += (adInfo) =>
+{
+ Debug.Log("Interstitial ad displayed");
+ // Optional: Track analytics
+};
+```
+
+#### `OnAdDisplayFailed`
+Fired when an interstitial ad fails to display.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+interstitialAd.OnAdDisplayFailed += (adInfo, error) =>
+{
+ Debug.LogError($"Display failed: {error.ErrorMessage}");
+ // Load a new ad
+ interstitialAd.LoadAd();
+};
+```
+
+#### `OnAdClicked`
+Fired when the user clicks on the interstitial ad.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+interstitialAd.OnAdClicked += (adInfo) =>
+{
+ Debug.Log("Interstitial ad clicked");
+ // Optional analytics tracking
+};
+```
+
+#### `OnAdClosed`
+Fired when the interstitial ad is closed.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+interstitialAd.OnAdClosed += (adInfo) =>
+{
+ Debug.Log("Interstitial ad closed");
+ // Resume game, load next ad
+ interstitialAd.LoadAd();
+};
+```
+
+**Best practice:** Load the next ad immediately in this callback.
+
+#### `OnAdInfoChanged`
+Fired when ad information changes, such as when a new highest-paying ad becomes available.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+interstitialAd.OnAdInfoChanged += (adInfo) =>
+{
+ Debug.Log($"Ad info changed - New ad from: {adInfo.AdNetwork}");
+ Debug.Log($"Estimated revenue: ${adInfo.Revenue}");
+};
+```
+
+**Important:** This event indicates that a new ad with potentially different revenue has become available. The SDK automatically selects the highest-paying ad when you call `ShowAd()`, but this event lets you know when the ad info has been updated. This is particularly important for revenue optimization as it signals when a better-paying ad has loaded.
+
+**When it fires:**
+- After a new ad loads with different properties than the previous one
+- When the waterfall selects a different ad network
+- When ad auction results change
+
+**Why it matters:** The updated `LevelPlayAdInfo` contains the latest revenue estimates and network information, which directly impacts your monetization. Always use the most recent `adInfo` when logging or analyzing ad performance.
+
+**If you're using ILRD** (`references/ilrd-api.md`): the `LevelPlayImpressionData` you receive in `OnImpressionDataReady` already contains the final revenue value, so `OnAdInfoChanged` is mostly useful for in-Editor debugging of the waterfall. Most publishers can leave it as a logging hook.
+
+## Data Types
+
+### LevelPlayAdInfo
+
+Contains information about the ad.
+
+**Properties:**
+- `AdId` (string): Unique identifier for this specific ad instance
+- `AdUnitId` (string): The ad unit identifier
+- `AdUnitName` (string): The ad unit name
+- `AdSize` (LevelPlayAdSize): The ad size (may be null; banner-relevant)
+- `AdFormat` (string): The ad format (e.g., "INTERSTITIAL")
+- `PlacementName` (string): Placement name where ad was shown
+- `AuctionId` (string): Unique auction identifier
+- `CreativeId` (string): Creative identifier
+- `Country` (string): User's country code (ISO 3166-1)
+- `Ab` (string): A/B test segment identifier
+- `SegmentName` (string): User segment name
+- `AdNetwork` (string): Ad network that served the ad
+- `InstanceName` (string): Ad network instance name
+- `InstanceId` (string): Ad network instance identifier
+- `Revenue` (double?): Estimated revenue in USD (nullable - check for null)
+- `Precision` (string): Revenue precision level
+- `EncryptedCPM` (string): Encrypted CPM value
+
+### LevelPlayAdError
+
+Contains error information when ad operations fail.
+
+**Properties:**
+- `ErrorCode` (int): Numeric error code
+- `ErrorMessage` (string): Human-readable error description
+- `AdUnitId` (string): The ad unit identifier where the error occurred
+- `AdId` (string): Unique identifier for the specific ad instance
+
+## Best Practices
+
+### Loading Strategy
+
+**Keep an interstitial ready for opportunistic moments:**
+```csharp
+// Load after init
+void OnInitSuccess(LevelPlayConfiguration config)
+{
+ interstitialAd = new LevelPlayInterstitialAd(adUnitId);
+ interstitialAd.OnAdLoaded += OnAdLoaded;
+ interstitialAd.OnAdClosed += OnAdClosed;
+ interstitialAd.LoadAd();
+}
+
+// Reload after showing
+void OnAdClosed(LevelPlayAdInfo adInfo)
+{
+ interstitialAd.LoadAd();
+}
+```
+
+### Placement Strategy
+
+**Show at natural transition points:**
+- ✅ Level complete screen
+- ✅ Game over screen
+- ✅ Returning to main menu
+- ✅ Between sessions
+- ❌ During active gameplay
+- ❌ Mid-level or mid-action
+
+### Frequency Capping
+
+**Implement time-based capping to avoid annoying users:**
+```csharp
+private float minTimeBetweenAds = 300f; // 5 minutes
+private float lastAdTime = -999f;
+
+public void TryShowInterstitial()
+{
+ if (Time.time - lastAdTime < minTimeBetweenAds)
+ {
+ Debug.Log("Too soon to show another ad");
+ return;
+ }
+
+ if (interstitialAd.IsAdReady())
+ {
+ interstitialAd.ShowAd();
+ }
+}
+
+private void OnAdDisplayed(LevelPlayAdInfo adInfo)
+{
+ lastAdTime = Time.time;
+}
+```
+
+**Recommended frequencies:**
+- **Revenue-focused**: 3-5 minutes
+- **Balanced**: 5-7 minutes
+- **UX-focused**: 7-10 minutes or never
+
+### User Flow
+
+**Never block user progress waiting for ads:**
+```csharp
+// ✅ CORRECT - Don't block flow
+public void OnLevelComplete()
+{
+ // Continue to next level regardless of ad
+ LoadNextLevel();
+
+ // Try to show ad, but don't wait for it
+ TryShowInterstitial();
+}
+
+// ❌ WRONG - Blocking user
+public void OnLevelComplete()
+{
+ if (interstitialAd.IsAdReady())
+ {
+ interstitialAd.ShowAd();
+ // Don't wait here to load next level!
+ }
+}
+```
+
+### Error Handling
+
+**Handle ad load failures:**
+```csharp
+private void OnAdLoadFailed(LevelPlayAdError error)
+{
+ Debug.LogWarning($"Ad load failed: {error.ErrorMessage}");
+
+ // Retry loading after a delay
+ Invoke(nameof(RetryLoad), 30f);
+}
+
+private void RetryLoad()
+{
+ interstitialAd.LoadAd();
+}
+```
+
+### Memory Management
+
+**Always unsubscribe from events:**
+```csharp
+void OnDestroy()
+{
+ if (interstitialAd != null)
+ {
+ interstitialAd.OnAdLoaded -= OnAdLoaded;
+ interstitialAd.OnAdLoadFailed -= OnAdLoadFailed;
+ interstitialAd.OnAdDisplayed -= OnAdDisplayed;
+ interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed;
+ interstitialAd.OnAdClicked -= OnAdClicked;
+ interstitialAd.OnAdClosed -= OnAdClosed;
+ interstitialAd.OnAdInfoChanged -= OnAdInfoChanged;
+ }
+}
+```
+
+## Common Issues
+
+### Issue: Error 1037 (Load during load)
+
+**Cause:** Calling `LoadAd()` repeatedly in `Update()` or other high-frequency loops before the previous load completes.
+
+**Common mistake:**
+```csharp
+void Update()
+{
+ // WRONG - This will cause error 1037!
+ if (!interstitialAd.IsAdReady())
+ {
+ interstitialAd.LoadAd();
+ }
+}
+```
+
+**Why this fails:** If the ad is still loading, calling `LoadAd()` again triggers error 1037: "Cannot load an interstitial while another interstitial is loading."
+
+**Solution:** Track load state manually and only call `LoadAd()` once:
+```csharp
+private bool isLoadingAd = false;
+
+void Start()
+{
+ LoadInterstitialAd();
+}
+
+void LoadInterstitialAd()
+{
+ if (isLoadingAd) return; // Prevent duplicate loads
+
+ isLoadingAd = true;
+ interstitialAd.LoadAd();
+}
+
+void OnAdLoaded(LevelPlayAdInfo adInfo)
+{
+ isLoadingAd = false;
+}
+
+void OnAdLoadFailed(LevelPlayAdError error)
+{
+ isLoadingAd = false;
+ // Optionally retry after delay
+}
+```
+
+### Issue: Interstitial showing too frequently
+
+**Solution:** Implement frequency capping based on time or session count. Track when the last ad was shown and enforce a minimum interval.
+
+### Issue: User flow blocked by ad loading
+
+**Solution:** Never wait for ads to load or show before progressing the user. Load ads proactively and show them opportunistically.
+
+### Issue: Ad not loading
+
+**Possible causes:**
+- SDK not initialized before creating ad object
+- No internet connection
+- Ad inventory not available
+- Incorrect ad unit ID
+
+**Solutions:**
+- Create ad object only after `OnInitSuccess` callback
+- Check network connectivity
+- Verify ad unit ID in LevelPlay dashboard
+- Test on real device with good connection
+
+### Issue: Ad loading but not showing
+
+**Possible causes:**
+- Not checking `IsAdReady()` before calling `ShowAd()`
+- Ad expired (loaded too long ago)
+
+**Solutions:**
+- Always check `IsAdReady()` before showing
+- Load ads close to when they'll be shown (within a few minutes)
+
+## Testing Checklist
+
+- [ ] Ad loads successfully after SDK initialization
+- [ ] `IsAdReady()` returns true when ad is loaded
+- [ ] Ad displays correctly when `ShowAd()` is called
+- [ ] Ad shows at appropriate transition points only
+- [ ] Frequency capping works as expected
+- [ ] User can progress regardless of ad status
+- [ ] Ad reloads automatically after being shown
+- [ ] Retry logic works when ad fails to load
+- [ ] Multiple placements track correctly (if using)
+- [ ] Placement capping works (if configured)
+- [ ] Memory leaks prevented (events unsubscribed)
+- [ ] Tested on multiple devices and network conditions
+- [ ] Ad doesn't interrupt active gameplay
diff --git a/skills/levelplay-unity-integration/references/ios-setup.md b/skills/levelplay-unity-integration/references/ios-setup.md
new file mode 100644
index 0000000..20e0a25
--- /dev/null
+++ b/skills/levelplay-unity-integration/references/ios-setup.md
@@ -0,0 +1,491 @@
+# iOS-Specific Setup for LevelPlay
+
+## Contents
+- [Overview](#overview)
+- [Prerequisites](#prerequisites)
+- [SKAdNetwork Configuration](#skadnetwork-configuration)
+- [App Tracking Transparency (ATT)](#app-tracking-transparency-att)
+- [Privacy Manifest](#privacy-manifest)
+- [Xcode Build Settings](#xcode-build-settings)
+- [Testing on iOS](#testing-on-ios)
+- [Common iOS Issues](#common-ios-issues)
+- [iOS Privacy Checklist](#ios-privacy-checklist)
+- [Version-Specific Notes](#version-specific-notes)
+- [Best Practices](#best-practices)
+
+## Overview
+
+LevelPlay SDK on iOS requires additional configuration beyond the basic Unity integration. This guide covers iOS-specific setup, including SKAdNetwork, App Tracking Transparency (ATT), and privacy manifest requirements.
+
+## Prerequisites
+
+For the current supported Unity, iOS deployment target, and Xcode versions, see the [LevelPlay iOS SDK integration guide](https://docs.unity.com/en-us/grow/levelplay/sdk/ios/sdk-integration).
+
+## SKAdNetwork Configuration
+
+### What is SKAdNetwork?
+
+SKAdNetwork is Apple's privacy-preserving ad attribution framework. It allows advertisers to measure campaign effectiveness without accessing user-level data.
+
+### Setup Steps
+
+LevelPlay SDK automatically includes the required SKAdNetwork IDs in your app's Info.plist during the build process. However, you should verify this after building:
+
+1. Build your Unity project for iOS
+2. Open the generated Xcode project
+3. Check `Info.plist` for the `SKAdNetworkItems` array
+4. Verify it contains multiple SKAdNetwork IDs (format: `XXXX1234abc.skadnetwork`)
+
+**If SKAdNetwork IDs are missing:**
+
+The LevelPlay Unity Package should handle this automatically. If you notice missing IDs:
+- Ensure you're using the latest LevelPlay Unity Package version
+- Check the Unity console for any import warnings
+- Manually add the SKAdNetwork IDs if necessary (see Unity documentation)
+
+### Verifying SKAdNetwork Setup
+
+After building to Xcode, you should see entries like this in Info.plist:
+
+```xml
+SKAdNetworkItems
+
+
+ SKAdNetworkIdentifier
+ cstr6suwn9.skadnetwork
+
+
+ SKAdNetworkIdentifier
+ 4fzdc2evr5.skadnetwork
+
+
+
+```
+
+## App Tracking Transparency (ATT)
+
+### What is ATT?
+
+Starting with iOS 14.5, apps must request user permission before tracking them across apps and websites. This is required for personalized advertising. ATT must be requested **before** `LevelPlay.Init()` to maximise ad fill rate.
+
+### Implementation
+
+ATT requires two parts: a post-build script that writes the permission description string into `Info.plist`, and a native plugin that performs the actual permission request.
+
+### Part 1: Permission Description (Post-Build Script)
+
+Apple requires a user-facing description explaining why your app requests tracking permission. Add it via a post-build script — this is the reliable approach across all Unity versions. The **User Tracking Usage Description** field in Player Settings is not present in Unity 6.
+
+Create `Assets/Editor/iOSPostBuild.cs`:
+
+```csharp
+using UnityEngine;
+using UnityEditor;
+using UnityEditor.Callbacks;
+
+#if UNITY_IOS
+using UnityEditor.iOS.Xcode;
+using System.IO;
+#endif
+
+public class iOSPostBuild
+{
+#if UNITY_IOS
+ [PostProcessBuild(999)]
+ public static void OnPostProcessBuild(BuildTarget target, string buildPath)
+ {
+ if (target != BuildTarget.iOS) return;
+
+ var plistPath = Path.Combine(buildPath, "Info.plist");
+ var plist = new PlistDocument();
+ plist.ReadFromFile(plistPath);
+
+ // Required for ATT prompt — shown to users in the system dialog
+ plist.root.SetString(
+ "NSUserTrackingUsageDescription",
+ "We use tracking to show you relevant ads and support free gameplay."
+ );
+
+ plist.WriteToFile(plistPath);
+ Debug.Log("iOSPostBuild: NSUserTrackingUsageDescription added to Info.plist");
+ }
+#endif
+}
+```
+
+**Customise the description** to match your app's purpose. Apple reviews this text and may reject vague descriptions.
+
+### Part 2: Native Plugin for the Permission Request
+
+Create `Assets/Plugins/iOS/ATTRequester.mm`:
+
+```objc
+#import
+
+typedef void (*ATTCallback)(int status);
+
+extern "C"
+{
+ // Returns current ATT status without prompting:
+ // 0 = Not Determined, 1 = Restricted, 2 = Denied, 3 = Authorized
+ int _ATT_GetStatus()
+ {
+ if (@available(iOS 14, *))
+ {
+ return (int)[ATTrackingManager trackingAuthorizationStatus];
+ }
+ return 3; // Pre-iOS 14: treat as Authorized
+ }
+
+ // Requests ATT permission and invokes callback with the resulting status.
+ // On pre-iOS 14 devices, immediately calls back with Authorized (3).
+ void _ATT_RequestPermission(ATTCallback callback)
+ {
+ if (@available(iOS 14, *))
+ {
+ [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status)
+ {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (callback) callback((int)status);
+ });
+ }];
+ }
+ else
+ {
+ if (callback) callback(3);
+ }
+ }
+}
+```
+
+**Note:** `dispatch_async(dispatch_get_main_queue(), ...)` returns the callback to Unity's main thread, which is required for safe IL2CPP interop.
+
+### Part 3: Integrating ATT into LevelPlayInitializer
+
+ATT must complete before `LevelPlay.Init()`. Modify your `LevelPlayInitializer.cs` from Step 7 to use a coroutine `Start()`:
+
+```csharp
+using System.Collections;
+using System.Runtime.InteropServices;
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class LevelPlayInitializer : MonoBehaviour
+{
+ [SerializeField] private string appKey;
+
+#if UNITY_IOS && !UNITY_EDITOR
+ private delegate void ATTCallbackDelegate(int status);
+
+ [DllImport("__Internal")] private static extern int _ATT_GetStatus();
+ [DllImport("__Internal")] private static extern void _ATT_RequestPermission(ATTCallbackDelegate callback);
+
+ private const int ATT_NOT_DETERMINED = 0;
+ private static bool _attDone = false;
+
+ [AOT.MonoPInvokeCallback(typeof(ATTCallbackDelegate))]
+ private static void OnATTCallback(int status)
+ {
+ Debug.Log($"ATT: user responded — status {status}");
+ _attDone = true;
+ }
+
+ private IEnumerator RequestATT()
+ {
+ // Skip prompt if user has already responded (e.g. returning user)
+ if (_ATT_GetStatus() != ATT_NOT_DETERMINED)
+ {
+ Debug.Log($"ATT: already determined (status {_ATT_GetStatus()})");
+ yield break;
+ }
+
+ _attDone = false;
+ _ATT_RequestPermission(OnATTCallback);
+ yield return new WaitUntil(() => _attDone);
+ }
+#endif
+
+ void Awake()
+ {
+ DontDestroyOnLoad(gameObject);
+ }
+
+ IEnumerator Start()
+ {
+#if UNITY_IOS && !UNITY_EDITOR
+ // Request ATT before initializing LevelPlay to maximise ad fill rate
+ yield return RequestATT();
+#else
+ yield return null; // Editor / Android: skip ATT
+#endif
+ InitializeLevelPlay();
+ }
+
+ private void InitializeLevelPlay()
+ {
+ // Add privacy settings here before Init (see Step 6.5)
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+ LevelPlay.Init(appKey);
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay SDK initialized successfully");
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
+ }
+
+ void OnDestroy()
+ {
+ LevelPlay.OnInitSuccess -= OnInitSuccess;
+ LevelPlay.OnInitFailed -= OnInitFailed;
+ }
+}
+```
+
+**Key points:**
+- `[AOT.MonoPInvokeCallback]` is required for IL2CPP builds — without it the native callback will crash on device
+- `[DllImport("__Internal")]` binds to the native plugin functions
+- `_ATT_GetStatus()` prevents re-prompting users who have already responded
+- `IEnumerator Start()` with `WaitUntil(() => _attDone)` pauses until the user responds to the ATT dialog
+- `#if UNITY_IOS && !UNITY_EDITOR` guards ensure ATT code only compiles for iOS device builds
+
+## Privacy Manifest
+
+### iOS 17+ Requirements
+
+Apple requires apps to declare data types collected and the reasons for using certain APIs. LevelPlay SDK includes a privacy manifest, but you should be aware of what it declares.
+
+### What LevelPlay Declares
+
+The LevelPlay SDK's privacy manifest typically declares:
+- **Data Collection**: Device identifiers (IDFA), usage data, location (if used)
+- **Required Reason APIs**: User defaults, file timestamp, system boot time, disk space
+- **Tracking**: Yes (for personalized advertising)
+
+### Your App's Privacy Manifest
+
+Ensure your app's privacy manifest (if you have one) aligns with LevelPlay's declarations:
+
+1. Check for `PrivacyInfo.xcprivacy` in your Xcode project
+2. Verify it includes LevelPlay's data types
+3. Update your App Store privacy declarations accordingly
+
+## Xcode Build Settings
+
+### Required iOS Capabilities
+
+LevelPlay SDK requires certain capabilities to function properly:
+
+1. Open your Xcode project after building from Unity
+2. Select your target > **Signing & Capabilities**
+3. Verify these frameworks are included (LevelPlay should add them automatically):
+ - **AdSupport.framework**: For IDFA access
+ - **AppTrackingTransparency.framework**: For ATT (iOS 14+)
+ - **StoreKit.framework**: For SKAdNetwork
+
+### App Transport Security (ATS) Configuration
+
+To ensure ads load correctly, configure App Transport Security in your Info.plist:
+
+**Option 1: Allow arbitrary loads (easiest, less secure)**
+
+Add this to your Info.plist:
+```xml
+NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
+```
+
+**Option 2: Allow specific domains (more secure)**
+
+If you prefer to only allow specific ad network domains:
+```xml
+NSAppTransportSecurity
+
+ NSExceptionDomains
+
+ ironsrc.com
+
+ NSIncludesSubdomains
+
+ NSExceptionAllowsInsecureHTTPLoads
+
+
+
+
+
+```
+
+**Note:** Most ad networks require HTTP access for legacy ad creatives. Without proper ATS configuration, some ads may fail to load.
+
+**When to configure:** Before building for iOS. This can be done in Unity's PostProcessBuild or manually in Xcode after export.
+
+### Recommended Xcode Build Settings
+
+In Xcode, verify these settings:
+
+- **Deployment Target**: see the [LevelPlay iOS SDK integration guide](https://docs.unity.com/en-us/grow/levelplay/sdk/ios/sdk-integration) for the currently supported minimum
+- **Enable Bitcode**: No
+- **Other Linker Flags**: Should include `-ObjC` (automatically added by Unity)
+
+## Testing on iOS
+
+### Test Device Setup
+
+1. Build and run on a physical iOS device (simulators have limitations)
+2. For ATT testing:
+ - Go to **Settings > Privacy & Security > Tracking**
+ - You can reset ATT prompt status by going to **Settings > General > Transfer or Reset iPhone > Reset > Reset Location & Privacy**
+
+### Integration Validation
+
+After building to iOS:
+
+```csharp
+void Start()
+{
+ #if UNITY_IOS
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+ LevelPlay.Init("YOUR_APP_KEY");
+ #endif
+}
+
+void OnInitSuccess(LevelPlayConfiguration config)
+{
+ Debug.Log("LevelPlay initialized successfully on iOS");
+}
+
+void OnInitFailed(LevelPlayInitError error)
+{
+ Debug.LogError($"LevelPlay init failed on iOS: {error.ErrorMessage}");
+}
+```
+
+Check the Xcode console for initialization output. Look for:
+- ✅ "LevelPlay initialized successfully on iOS"
+- ❌ Any initialization errors or warnings
+
+### Testing Ads on iOS
+
+1. **Test mode**: Enable test mode in your LevelPlay dashboard
+2. **Real ads**: Disable test mode and test with real ads (use low-value test account)
+3. **Different ATT states**: Test with:
+ - ATT granted (tracking allowed)
+ - ATT denied (tracking disallowed)
+ - ATT restricted (device-level restriction)
+
+## Common iOS Issues
+
+### Issue: ATT prompt not showing
+
+**Causes:**
+- Missing `NSUserTrackingUsageDescription` in Info.plist
+- ATT already determined on device
+- Testing on iOS Simulator (use real device)
+
+**Solutions:**
+- Verify `iOSPostBuild.cs` exists in `Assets/Editor/` and ran successfully (check console for "iOSPostBuild: NSUserTrackingUsageDescription added to Info.plist")
+- Reset privacy settings on device: **Settings > General > Transfer or Reset iPhone > Reset > Reset Location & Privacy**
+- Test on physical iOS device
+
+### Issue: Ads not loading after ATT denial
+
+**This is expected behavior:**
+- When users deny tracking, personalized ads are limited
+- Non-personalized ads may still serve
+- Revenue may be lower for users who deny tracking
+
+**What to do:**
+- Continue with LevelPlay initialization regardless of ATT response
+- Don't block features if tracking is denied
+- Comply with Apple's guidelines
+
+### Issue: SKAdNetwork IDs missing
+
+**Causes:**
+- Outdated LevelPlay package
+- Custom build postprocessor conflict
+- Manual Info.plist modifications
+
+**Solutions:**
+- Update to latest LevelPlay Unity Package
+- Check for conflicting build scripts
+- Let LevelPlay handle SKAdNetwork IDs automatically
+
+### Issue: App rejected for privacy reasons
+
+**Common causes:**
+- Missing privacy manifest declarations
+- Incorrect ATT usage description
+- Data collection not properly declared
+
+**Solutions:**
+- Review Apple's App Privacy Details guidelines
+- Ensure ATT description is clear and user-friendly
+- Declare all data types collected in App Store Connect
+- Keep LevelPlay SDK updated (includes privacy manifest updates)
+
+## iOS Privacy Checklist
+
+Before submitting to App Store:
+
+- [ ] ATT prompt implemented: `ATTRequester.mm` in `Assets/Plugins/iOS/`, `iOSPostBuild.cs` in `Assets/Editor/`, `LevelPlayInitializer.cs` updated to `IEnumerator Start()`
+- [ ] SKAdNetwork IDs included in Info.plist
+- [ ] Privacy manifest present and accurate
+- [ ] App Store privacy declarations filled out
+- [ ] Data collection disclosed transparently
+- [ ] Tested on real iOS devices
+- [ ] Verified ads load and show correctly
+- [ ] Tested both ATT granted and denied scenarios
+- [ ] No tracking of users who denied ATT
+- [ ] Compliant with Apple's advertising policies
+
+## Additional Resources
+
+### Apple Documentation
+- [App Tracking Transparency Guide](https://developer.apple.com/documentation/apptrackingtransparency)
+- [SKAdNetwork Documentation](https://developer.apple.com/documentation/storekit/skadnetwork)
+- [User Privacy and Data Use](https://developer.apple.com/app-store/user-privacy-and-data-use/)
+
+### LevelPlay Documentation
+- Refer to LevelPlay's Unity documentation for the latest iOS setup requirements
+- Check for SDK update notes regarding iOS changes
+- Review LevelPlay's privacy manifest documentation
+
+## Version-Specific Notes
+
+### iOS 14.5+
+- ATT required for IDFA access
+- Personalized ads limited without tracking permission
+
+### iOS 15+
+- Custom product pages support
+- App privacy report visible to users
+
+### iOS 16+
+- Additional privacy enhancements
+- More granular location privacy
+
+### iOS 17+
+- Privacy manifest required
+- Increased API usage disclosure requirements
+- SDK transparency requirements
+
+## Best Practices
+
+1. **Request ATT at an appropriate time**: Not immediately on first launch, but before showing ads
+2. **Explain value to users**: Help users understand why you're requesting tracking
+3. **Respect user choice**: Never block app functionality if tracking is denied
+4. **Keep SDK updated**: Apple requirements change frequently
+5. **Test thoroughly**: Test on real devices with different iOS versions
+6. **Monitor metrics**: Track opt-in rates and ad performance post-iOS 14.5
+7. **Stay compliant**: Follow Apple's Human Interface Guidelines for ATT
+8. **Prepare for rejections**: Have documentation ready if Apple requests clarification
diff --git a/skills/levelplay-unity-integration/references/privacy-settings.md b/skills/levelplay-unity-integration/references/privacy-settings.md
new file mode 100644
index 0000000..cdcbbee
--- /dev/null
+++ b/skills/levelplay-unity-integration/references/privacy-settings.md
@@ -0,0 +1,606 @@
+# Privacy and Regulation Settings
+
+## Contents
+- [Overview](#overview)
+- [Quick Reference](#quick-reference)
+- [GDPR Consent Management](#gdpr-consent-management)
+- [CCPA Compliance](#ccpa-compliance)
+- [COPPA Compliance](#coppa-compliance)
+- [Combined Compliance Example](#combined-compliance-example)
+- [Deprecated APIs (SDK 9.3.0 and Lower)](#deprecated-apis-sdk-930-and-lower)
+- [Best Practices](#best-practices)
+- [Testing Privacy Settings](#testing-privacy-settings)
+- [Common Issues](#common-issues)
+
+## Overview
+
+LevelPlay SDK provides APIs to comply with privacy regulations including GDPR, CCPA, and COPPA. These settings must be configured before SDK initialization to ensure compliance with regional privacy laws.
+
+**SDK Version Requirement:** SDK 9.4.0+ for current APIs (older deprecated APIs available in SDK 9.3.0 and lower)
+
+## Quick Reference
+
+| Regulation | SDK 9.5.0+ API | SDK 9.4.x API | When to Use |
+|------------|---------------|---------------|-------------|
+| **GDPR** | `LevelPlayPrivacySettings.SetGDPRConsent(bool)` | `LevelPlayPrivacySettings.SetGDPRConsents(Dictionary)` | EU users, requires explicit consent |
+| **CCPA** | `LevelPlayPrivacySettings.SetCCPA(true)` | `LevelPlayPrivacySettings.SetCCPA(true)` | California users opting out of data sale |
+| **COPPA** | `LevelPlayPrivacySettings.SetCOPPA(true)` | `LevelPlayPrivacySettings.SetCOPPA(true)` | Apps directed at children under 13 |
+
+## GDPR Consent Management
+
+### What is GDPR?
+
+The General Data Protection Regulation (GDPR) is a European Union privacy law requiring explicit user consent for data collection and processing. Apps serving EU users must implement GDPR consent flows.
+
+### Set GDPR Consent
+
+The GDPR consent API changed between SDK versions. Check your installed SDK version in **Ads Mediation > Network Manager**.
+
+**SDK 9.5.0+ — global consent boolean:**
+
+```csharp
+using Unity.Services.LevelPlay;
+
+// true = user has granted consent, false = user has not consented
+// Call BEFORE SDK initialization
+LevelPlayPrivacySettings.SetGDPRConsent(true);
+```
+
+**Parameters:**
+- `true`: User has granted consent for data collection across all networks
+- `false`: User has denied consent (non-personalized ads only)
+
+**SDK 9.4.x — per-network consent dictionary:**
+
+```csharp
+using Unity.Services.LevelPlay;
+using System.Collections.Generic;
+
+// Set consent for each installed ad network
+// Call BEFORE SDK initialization
+LevelPlayPrivacySettings.SetGDPRConsents(new Dictionary {
+ { "UnityAds", true },
+ { "IronSource", true }
+ // Add an entry for each network you have installed — see Supported Network Keys below
+});
+```
+
+**Important:**
+- Call **BEFORE** `LevelPlay.Init()`
+- `SetGDPRConsent(bool)` (singular) was introduced in SDK 9.5.0. If you are on SDK 9.4.x, use `SetGDPRConsents(Dictionary)` instead.
+- `SetGDPRConsents(Dictionary)` is deprecated as of SDK 9.5.0+ — migrate to `SetGDPRConsent(bool)` when you upgrade.
+
+### Supported Network Keys (for SetGDPRConsents)
+
+| Network Key | Ad Network |
+|-------------|------------|
+| `UnityAds` | Unity Ads |
+| `AdMob` | Google AdMob |
+| `AppLovin` | AppLovin |
+| `APS` | Amazon Publisher Services |
+| `BidMachine` | BidMachine |
+| `Bigo` | Bigo Ads |
+| `Chartboost` | Chartboost |
+| `Facebook` | Meta Audience Network |
+| `Fyber` | Digital Turbine (Fyber) |
+| `HyprMx` | HyprMX |
+| `InMobi` | InMobi |
+| `Line` | LINE Ads |
+| `Mintegral` | Mintegral |
+| `MobileFuse` | MobileFuse |
+| `Moloco` | Moloco |
+| `MyTarget` | myTarget |
+| `Ogury` | Ogury |
+| `Pangle` | Pangle (TikTok) |
+| `PubMatic` | PubMatic |
+| `Smaato` | Smaato |
+| `SuperAwesome` | SuperAwesome |
+| `Verve` | Verve |
+| `Voodoo` | Voodoo |
+| `Vungle` | Vungle |
+| `Yandex` | Yandex Ads |
+| `YSO` | YSO |
+
+### Complete GDPR Implementation Example
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class GDPRConsentManager : MonoBehaviour
+{
+ [SerializeField] private string appKey;
+
+ void Start()
+ {
+ // Check if user is in GDPR region (EU)
+ if (IsUserInGDPRRegion())
+ {
+ // Show consent dialog and get user's answer
+ ShowConsentDialog();
+ }
+ else
+ {
+ // Not in GDPR region, initialize normally
+ InitializeLevelPlay();
+ }
+ }
+
+ private bool IsUserInGDPRRegion()
+ {
+ // Implement your region detection logic
+ // Options: IP geolocation, device locale, or third-party consent SDK
+ return false; // Placeholder
+ }
+
+ private void ShowConsentDialog()
+ {
+ // Show your GDPR consent UI
+ // When user makes a choice, call OnConsentReceived()
+ }
+
+ private void OnConsentReceived(bool userConsented)
+ {
+ // Store consent for future sessions
+ PlayerPrefs.SetInt("GDPR_Consent", userConsented ? 1 : 0);
+ PlayerPrefs.Save();
+
+ // Apply consent to LevelPlay (BEFORE Init)
+ LevelPlayPrivacySettings.SetGDPRConsent(userConsented);
+
+ // Initialize SDK
+ InitializeLevelPlay();
+ }
+
+ private void InitializeLevelPlay()
+ {
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+ LevelPlay.Init(appKey);
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay initialized with GDPR consent applied");
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"LevelPlay init failed: {error.ErrorMessage}");
+ }
+}
+```
+
+### Updating Consent
+
+If user changes consent preferences after initialization:
+
+```csharp
+// User updates consent — apply immediately
+LevelPlayPrivacySettings.SetGDPRConsent(false); // User revoked consent
+
+// Note: Changes apply to future ad requests
+// Currently loaded ads are not affected
+```
+
+---
+
+## CCPA Compliance
+
+### What is CCPA?
+
+The California Consumer Privacy Act (CCPA) gives California residents the right to opt out of the "sale" of their personal information. Apps must provide a "Do Not Sell My Personal Information" option.
+
+### Set CCPA Opt-Out (SDK 9.4.0+)
+
+Indicate that the user has opted out of data sale:
+
+```csharp
+using Unity.Services.LevelPlay;
+
+// User has opted out of data sale (call BEFORE SDK initialization)
+LevelPlayPrivacySettings.SetCCPA(true);
+```
+
+**Parameters:**
+- `true`: User has opted out of data sale (restrict data collection)
+- `false`: User has not opted out (default behavior)
+
+**When to call:** BEFORE `LevelPlay.Init()`
+
+### Complete CCPA Implementation Example
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class CCPAComplianceManager : MonoBehaviour
+{
+ [SerializeField] private string appKey;
+ private bool userOptedOut = false;
+
+ void Start()
+ {
+ // Check if user is in California
+ if (IsUserInCalifornia())
+ {
+ // Load previously saved opt-out preference
+ userOptedOut = PlayerPrefs.GetInt("CCPA_OptOut", 0) == 1;
+
+ // Show "Do Not Sell My Info" option in settings
+ // If user hasn't made a choice, you may show a prompt
+ }
+
+ // Apply CCPA setting before initialization
+ if (userOptedOut)
+ {
+ LevelPlayPrivacySettings.SetCCPA(true);
+ }
+
+ // Initialize SDK
+ InitializeLevelPlay();
+ }
+
+ private bool IsUserInCalifornia()
+ {
+ // Implement your region detection logic
+ // Options: IP geolocation, device locale
+ return false; // Placeholder
+ }
+
+ // Call this when user toggles "Do Not Sell" in settings
+ public void OnUserToggleCCPAOptOut(bool optOut)
+ {
+ userOptedOut = optOut;
+
+ // Save preference
+ PlayerPrefs.SetInt("CCPA_OptOut", optOut ? 1 : 0);
+ PlayerPrefs.Save();
+
+ // Apply to SDK
+ LevelPlayPrivacySettings.SetCCPA(optOut);
+
+ Debug.Log($"CCPA opt-out set to: {optOut}");
+ }
+
+ private void InitializeLevelPlay()
+ {
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+ LevelPlay.Init(appKey);
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay initialized with CCPA settings applied");
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"LevelPlay init failed: {error.ErrorMessage}");
+ }
+}
+```
+
+---
+
+## COPPA Compliance
+
+### What is COPPA?
+
+The Children's Online Privacy Protection Act (COPPA) is a US federal law protecting children under 13. Apps directed at children must not collect personal information without parental consent.
+
+### Set Child-Directed Treatment (SDK 9.4.0+)
+
+Indicate that your app is directed at children:
+
+```csharp
+using Unity.Services.LevelPlay;
+
+// App is directed at children under 13 (call BEFORE SDK initialization)
+LevelPlayPrivacySettings.SetCOPPA(true);
+```
+
+**Parameters:**
+- `true`: App is child-directed, apply COPPA restrictions
+- `false`: App is not child-directed (default)
+
+**When to call:** BEFORE `LevelPlay.Init()`
+
+### COPPA Implementation Example
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class COPPACompliantInitializer : MonoBehaviour
+{
+ [SerializeField] private string appKey;
+ [SerializeField] private bool isChildDirectedApp = true; // Set this based on your app
+
+ void Start()
+ {
+ // Set COPPA compliance BEFORE initialization
+ if (isChildDirectedApp)
+ {
+ LevelPlayPrivacySettings.SetCOPPA(true);
+ Debug.Log("COPPA child-directed treatment enabled");
+ }
+
+ // Initialize SDK
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+ LevelPlay.Init(appKey);
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay initialized with COPPA compliance");
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"LevelPlay init failed: {error.ErrorMessage}");
+ }
+}
+```
+
+### Additional COPPA Considerations
+
+If your app is child-directed:
+
+1. **Disable personalized ads**: COPPA requires non-personalized ads only
+2. **Google Play Families Policy**: If targeting children on Google Play:
+ - Set COPPA flag: `LevelPlayPrivacySettings.SetCOPPA(true)`
+ - Ensure ad networks comply with Google Play Families requirements
+ - Test thoroughly before submission
+3. **App Store age ratings**: Set appropriate age ratings in both app stores
+4. **Privacy policy**: Clearly state data collection practices for children
+
+---
+
+## Combined Compliance Example
+
+If you need to support multiple regulations:
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+using System.Collections.Generic;
+
+public class PrivacyComplianceManager : MonoBehaviour
+{
+ [SerializeField] private string appKey;
+ [SerializeField] private bool isChildDirectedApp = false;
+
+ void Start()
+ {
+ // Apply all applicable privacy settings BEFORE initialization
+
+ // 1. COPPA (if child-directed app)
+ if (isChildDirectedApp)
+ {
+ LevelPlayPrivacySettings.SetCOPPA(true);
+ }
+
+ // 2. GDPR (if user in EU)
+ if (IsUserInGDPRRegion())
+ {
+ if (HasStoredGDPRConsent())
+ {
+ LevelPlayPrivacySettings.SetGDPRConsent(LoadGDPRConsent());
+ }
+ else
+ {
+ // Show consent dialog first, then initialize
+ ShowGDPRConsentDialog();
+ return; // Don't initialize yet
+ }
+ }
+
+ // 3. CCPA (if user in California)
+ if (IsUserInCalifornia())
+ {
+ bool ccpaOptOut = LoadCCPAOptOut();
+ if (ccpaOptOut)
+ {
+ LevelPlayPrivacySettings.SetCCPA(true);
+ }
+ }
+
+ // Initialize after applying all privacy settings
+ InitializeLevelPlay();
+ }
+
+ private bool HasStoredGDPRConsent()
+ {
+ return PlayerPrefs.HasKey("GDPR_Consent");
+ }
+
+ private bool LoadGDPRConsent()
+ {
+ return PlayerPrefs.GetInt("GDPR_Consent", 0) == 1;
+ }
+
+ private bool LoadCCPAOptOut()
+ {
+ return PlayerPrefs.GetInt("CCPA_OptOut", 0) == 1;
+ }
+
+ private bool IsUserInGDPRRegion()
+ {
+ // Implement region detection
+ return false;
+ }
+
+ private bool IsUserInCalifornia()
+ {
+ // Implement region detection
+ return false;
+ }
+
+ private void ShowGDPRConsentDialog()
+ {
+ // Show your consent UI, then call OnGDPRConsentsReceived()
+ }
+
+ private void OnGDPRConsentReceived(bool userConsented)
+ {
+ PlayerPrefs.SetInt("GDPR_Consent", userConsented ? 1 : 0);
+ PlayerPrefs.Save();
+ LevelPlayPrivacySettings.SetGDPRConsent(userConsented);
+ InitializeLevelPlay();
+ }
+
+ private void InitializeLevelPlay()
+ {
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.OnInitFailed += OnInitFailed;
+ LevelPlay.Init(appKey);
+ }
+
+ private void OnInitSuccess(LevelPlayConfiguration config)
+ {
+ Debug.Log("LevelPlay initialized with privacy compliance");
+ }
+
+ private void OnInitFailed(LevelPlayInitError error)
+ {
+ Debug.LogError($"LevelPlay init failed: {error.ErrorMessage}");
+ }
+}
+```
+
+---
+
+## Deprecated APIs (SDK 9.3.0 and Lower)
+
+**⚠️ These APIs are deprecated. Use the current `LevelPlayPrivacySettings` APIs instead.**
+
+### Deprecated GDPR API — LevelPlay.SetConsent
+
+```csharp
+// DEPRECATED - Do not use
+LevelPlay.SetConsent(true); // Grants consent for all networks
+LevelPlay.SetConsent(false); // Denies consent for all networks
+```
+
+**Status:** Marked as `[Obsolete]` in SDK code.
+
+**Migration:** Use `LevelPlayPrivacySettings.SetGDPRConsents(Dictionary)` (SDK 9.4.x) or `LevelPlayPrivacySettings.SetGDPRConsent(bool)` (SDK 9.5.0+).
+
+### Deprecated GDPR API — SetGDPRConsents (per-network dictionary, SDK 9.5.0+)
+
+```csharp
+// DEPRECATED as of SDK 9.5.0+ — generates compiler warning
+// NOTE: This is the CORRECT API for SDK 9.4.x. Only deprecated from 9.5.0 onwards.
+Dictionary consents = new Dictionary
+{
+ { "UnityAds", true },
+ { "AdMob", true }
+};
+LevelPlayPrivacySettings.SetGDPRConsents(consents);
+```
+
+**Note:** `SetGDPRConsents(Dictionary)` is the correct API for SDK 9.4.x. It becomes deprecated only in SDK 9.5.0+, where it is replaced by `SetGDPRConsent(bool)`. If you are on 9.5.0+, migrate to the boolean API.
+
+**Migration (9.5.0+ only):** Use `LevelPlayPrivacySettings.SetGDPRConsent(true/false)` instead.
+
+### Deprecated CCPA API
+
+```csharp
+// DEPRECATED - Do not use
+LevelPlay.SetMetaData("do_not_sell", "true");
+LevelPlay.SetMetaData("do_not_sell", "false");
+```
+
+**Migration:** Use `LevelPlayPrivacySettings.SetCCPA(true)` instead.
+
+### Deprecated COPPA API
+
+```csharp
+// DEPRECATED - Do not use
+LevelPlay.SetMetaData("is_child_directed", "true");
+LevelPlay.SetMetaData("is_child_directed", "false");
+```
+
+**Migration:** Use `LevelPlayPrivacySettings.SetCOPPA(true)` instead.
+
+---
+
+## Best Practices
+
+1. **Call before initialization**: All privacy settings must be applied before `LevelPlay.Init()`
+2. **Persist user choices**: Store consent preferences and reapply on each app launch
+3. **Detect user region**: Use geolocation or device locale to determine applicable regulations
+4. **Provide UI controls**: Give users easy access to privacy settings
+5. **Respect user choices**: Honor opt-outs and consent denials
+6. **Update promptly**: When user changes preferences, apply immediately via privacy APIs
+7. **Test thoroughly**: Verify compliance in all supported regions
+8. **Document clearly**: Include privacy policy links in your app
+9. **Use consent management platforms**: Consider third-party CMPs for complex consent requirements
+10. **Stay updated**: Privacy regulations evolve, monitor SDK updates for compliance changes
+
+---
+
+## Testing Privacy Settings
+
+### Verify Settings Are Applied
+
+```csharp
+void Start()
+{
+ // Set privacy settings
+ LevelPlayPrivacySettings.SetCOPPA(true);
+ LevelPlayPrivacySettings.SetGDPRConsent(true); // true = user consented
+ LevelPlayPrivacySettings.SetCCPA(true);
+
+ // Initialize
+ LevelPlay.OnInitSuccess += OnInitSuccess;
+ LevelPlay.Init(appKey);
+}
+
+private void OnInitSuccess(LevelPlayConfiguration config)
+{
+ Debug.Log("Privacy settings applied successfully");
+ // Check console logs from SDK for confirmation
+}
+```
+
+### Test Scenarios
+
+1. **GDPR - All consents granted**: Set all network consents to `true`, verify ads load
+2. **GDPR - All consents denied**: Set all network consents to `false`, verify limited ads
+3. **CCPA opt-out**: Set `SetCCPA(true)`, verify data collection restricted
+4. **COPPA enabled**: Set `SetCOPPA(true)`, verify child-safe ad delivery
+5. **Combined**: Apply multiple regulations, verify all honored
+
+---
+
+## Common Issues
+
+### Issue: Privacy settings not taking effect
+
+**Causes:**
+- Privacy APIs called after SDK initialization
+- Settings not persisted across app launches
+
+**Solutions:**
+- Always call privacy APIs BEFORE `LevelPlay.Init()`
+- Store user preferences and reapply on every launch
+
+### Issue: Using deprecated APIs
+
+**Cause:** Following outdated documentation or examples using `LevelPlay.SetConsent()` or `SetGDPRConsents(Dictionary)`
+
+**Solution:** Migrate to current APIs — use `LevelPlayPrivacySettings.SetGDPRConsent(bool)` for GDPR
+
+---
+
+## Additional Resources
+
+- [LevelPlay Privacy Documentation](https://docs.unity.com/en-us/grow/levelplay)
+- [GDPR Overview](https://gdpr.eu/)
+- [CCPA Information](https://oag.ca.gov/privacy/ccpa)
+- [COPPA Requirements](https://www.ftc.gov/legal-library/browse/rules/childrens-online-privacy-protection-rule-coppa)
+- [Google Play Families Policy](https://support.google.com/googleplay/android-developer/answer/9893335)
diff --git a/skills/levelplay-unity-integration/references/rewarded-api.md b/skills/levelplay-unity-integration/references/rewarded-api.md
new file mode 100644
index 0000000..0e38c19
--- /dev/null
+++ b/skills/levelplay-unity-integration/references/rewarded-api.md
@@ -0,0 +1,882 @@
+# Rewarded Ads API Reference
+
+## Contents
+- [Overview](#overview)
+- [Key Characteristics](#key-characteristics)
+- [Implementation Pattern](#implementation-pattern)
+- [API Reference](#api-reference)
+- [Data Types](#data-types)
+- [Best Practices](#best-practices)
+- [Common Issues](#common-issues)
+- [Testing Checklist](#testing-checklist)
+
+## Overview
+
+Rewarded ads are video ads that users choose to watch in exchange for in-app rewards. They're the highest-earning ad format and provide excellent user experience when implemented correctly.
+
+## Key Characteristics
+
+- **User-initiated**: Users explicitly choose to watch
+- **Win-win**: Users get valuable rewards, developers get revenue
+- **Best for**: Extra lives, hints, currency, power-ups, skipping wait times
+
+## Implementation Pattern
+
+### Basic Rewarded Ad Implementation
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+
+public class RewardedAdManager : MonoBehaviour
+{
+ private LevelPlayRewardedAd rewardedAd;
+ private string adUnitId = "YOUR_REWARDED_AD_UNIT_ID";
+
+ void Start()
+ {
+ // Create the rewarded ad object using constructor
+ rewardedAd = new LevelPlayRewardedAd(adUnitId);
+
+ // Register event listeners
+ rewardedAd.OnAdLoaded += OnAdLoaded;
+ rewardedAd.OnAdLoadFailed += OnAdLoadFailed;
+ rewardedAd.OnAdDisplayed += OnAdDisplayed;
+ rewardedAd.OnAdDisplayFailed += OnAdDisplayFailed;
+ rewardedAd.OnAdRewarded += OnAdRewarded;
+ rewardedAd.OnAdClosed += OnAdClosed;
+ rewardedAd.OnAdClicked += OnAdClicked;
+ rewardedAd.OnAdInfoChanged += OnAdInfoChanged; // Fires when the loaded ad swaps to a higher-paying network
+
+ // Load the ad
+ LoadAd();
+ }
+
+ void OnDestroy()
+ {
+ // Unregister event listeners
+ if (rewardedAd != null)
+ {
+ rewardedAd.OnAdLoaded -= OnAdLoaded;
+ rewardedAd.OnAdLoadFailed -= OnAdLoadFailed;
+ rewardedAd.OnAdDisplayed -= OnAdDisplayed;
+ rewardedAd.OnAdDisplayFailed -= OnAdDisplayFailed;
+ rewardedAd.OnAdRewarded -= OnAdRewarded;
+ rewardedAd.OnAdClosed -= OnAdClosed;
+ rewardedAd.OnAdClicked -= OnAdClicked;
+ rewardedAd.OnAdInfoChanged -= OnAdInfoChanged;
+ }
+ }
+
+ public void LoadAd()
+ {
+ Debug.Log("Loading rewarded ad...");
+ rewardedAd.LoadAd();
+ }
+
+ public void ShowAd()
+ {
+ if (rewardedAd.IsAdReady())
+ {
+ Debug.Log("Showing rewarded ad");
+ rewardedAd.ShowAd();
+ }
+ else
+ {
+ Debug.LogWarning("Rewarded ad is not ready yet");
+ }
+ }
+
+ // Event Callbacks
+ private void OnAdLoaded(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Rewarded ad loaded successfully");
+ }
+
+ private void OnAdLoadFailed(LevelPlayAdError error)
+ {
+ Debug.LogWarning($"Rewarded ad failed to load: {error.ErrorMessage}");
+ // Retry loading after a delay
+ Invoke(nameof(LoadAd), 30f);
+ }
+
+ private void OnAdDisplayed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Rewarded ad displayed");
+ }
+
+ private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
+ {
+ Debug.LogError($"Rewarded ad failed to display: {error.ErrorMessage}");
+ // Load a new ad
+ LoadAd();
+ }
+
+ private void OnAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
+ {
+ Debug.Log($"User earned reward: {reward.Amount} {reward.Name}");
+ // Grant the reward to the user
+ GrantReward(reward);
+ }
+
+ private void OnAdClosed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Rewarded ad closed");
+ // Load the next ad
+ LoadAd();
+ }
+
+ private void OnAdClicked(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Rewarded ad clicked");
+ }
+
+ private void OnAdInfoChanged(LevelPlayAdInfo adInfo)
+ {
+ // Optional: log or update analytics with the latest revenue estimate
+ Debug.Log($"Rewarded ad info changed - network: {adInfo.AdNetwork}, revenue: ${adInfo.Revenue}");
+ }
+
+ private void GrantReward(LevelPlayReward reward)
+ {
+ // Implement your reward logic here
+ Debug.Log($"Granting {reward.Amount} {reward.Name} to the user");
+ }
+}
+```
+
+### Advanced: Multiple Rewarded Ad Placements
+
+```csharp
+using UnityEngine;
+using Unity.Services.LevelPlay;
+using System.Collections.Generic;
+
+public class MultiPlacementRewardedAdManager : MonoBehaviour
+{
+ // Single ad unit ID, multiple placements
+ private string adUnitId = "YOUR_REWARDED_AD_UNIT_ID";
+ private LevelPlayRewardedAd rewardedAd;
+
+ // Track which placement is currently being shown
+ private string currentPlacement;
+
+ void Start()
+ {
+ // Create single rewarded ad object
+ rewardedAd = new LevelPlayRewardedAd(adUnitId);
+
+ // Register callbacks
+ rewardedAd.OnAdLoaded += OnAdLoaded;
+ rewardedAd.OnAdLoadFailed += OnAdLoadFailed;
+ rewardedAd.OnAdRewarded += OnAdRewarded;
+ rewardedAd.OnAdClosed += OnAdClosed;
+ rewardedAd.OnAdDisplayFailed += OnAdDisplayFailed;
+
+ // Load ad
+ rewardedAd.LoadAd();
+ }
+
+ void OnDestroy()
+ {
+ if (rewardedAd != null)
+ {
+ rewardedAd.OnAdLoaded -= OnAdLoaded;
+ rewardedAd.OnAdLoadFailed -= OnAdLoadFailed;
+ rewardedAd.OnAdRewarded -= OnAdRewarded;
+ rewardedAd.OnAdClosed -= OnAdClosed;
+ rewardedAd.OnAdDisplayFailed -= OnAdDisplayFailed;
+ }
+ }
+
+ // Public methods for different placements
+ public void ShowAdForHints()
+ {
+ ShowAdWithPlacement("hints");
+ }
+
+ public void ShowAdForExtraLives()
+ {
+ ShowAdWithPlacement("extra_lives");
+ }
+
+ public void ShowAdForCoins()
+ {
+ ShowAdWithPlacement("bonus_coins");
+ }
+
+ private void ShowAdWithPlacement(string placementName)
+ {
+ if (rewardedAd.IsAdReady())
+ {
+ // Check if placement is capped
+ if (LevelPlayRewardedAd.IsPlacementCapped(placementName))
+ {
+ Debug.LogWarning($"Placement '{placementName}' is capped");
+ return;
+ }
+
+ currentPlacement = placementName;
+ Debug.Log($"Showing rewarded ad with placement: {placementName}");
+ rewardedAd.ShowAd(placementName: placementName);
+ }
+ else
+ {
+ Debug.LogWarning("Rewarded ad is not ready");
+ }
+ }
+
+ private void OnAdLoaded(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Rewarded ad loaded");
+ }
+
+ private void OnAdLoadFailed(LevelPlayAdError error)
+ {
+ Debug.LogWarning($"Rewarded ad load failed: {error.ErrorMessage}");
+ Invoke(nameof(LoadAd), 30f);
+ }
+
+ private void OnAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
+ {
+ Debug.Log($"User rewarded: {reward.Amount} {reward.Name} for placement: {currentPlacement}");
+
+ // Grant placement-specific rewards
+ switch (currentPlacement)
+ {
+ case "hints":
+ GrantHints(reward);
+ break;
+ case "extra_lives":
+ GrantExtraLives(reward);
+ break;
+ case "bonus_coins":
+ GrantCoins(reward);
+ break;
+ }
+ }
+
+ private void OnAdClosed(LevelPlayAdInfo adInfo)
+ {
+ Debug.Log("Rewarded ad closed");
+ currentPlacement = null;
+ LoadAd();
+ }
+
+ private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
+ {
+ Debug.LogError($"Rewarded ad display failed: {error.ErrorMessage}");
+ currentPlacement = null;
+ LoadAd();
+ }
+
+ private void LoadAd()
+ {
+ if (rewardedAd != null)
+ {
+ rewardedAd.LoadAd();
+ }
+ }
+
+ // Reward granting methods
+ private void GrantHints(LevelPlayReward reward)
+ {
+ Debug.Log("Granting hints to player");
+ // Your hint logic here
+ }
+
+ private void GrantExtraLives(LevelPlayReward reward)
+ {
+ Debug.Log("Granting extra lives to player");
+ // Your lives logic here
+ }
+
+ private void GrantCoins(LevelPlayReward reward)
+ {
+ Debug.Log("Granting bonus coins to player");
+ // Your coins logic here
+ }
+}
+```
+
+## API Reference
+
+### Constructor
+
+#### `new LevelPlayRewardedAd(string adUnitId)`
+Create a rewarded ad object.
+
+**Parameters:**
+- `adUnitId`: The ad unit identifier from your LevelPlay dashboard
+
+**Returns:** `LevelPlayRewardedAd` object
+
+**Usage:**
+```csharp
+LevelPlayRewardedAd rewardedAd = new LevelPlayRewardedAd("your_ad_unit_id");
+```
+
+**Important:** Call this only after `LevelPlay.Init()` has completed successfully (in the `OnInitSuccess` callback).
+
+#### `new LevelPlayRewardedAd(string adUnitId, Config config)`
+Create a rewarded ad object with custom configuration.
+
+**Parameters:**
+- `adUnitId`: The ad unit identifier
+- `config`: Optional configuration object
+
+**Usage:**
+```csharp
+var configBuilder = new LevelPlayRewardedAd.Config.Builder();
+configBuilder.SetBidFloor(1.0); // Minimum bid price in USD
+var config = configBuilder.Build();
+LevelPlayRewardedAd rewardedAd = new LevelPlayRewardedAd("your_ad_unit_id", config);
+```
+
+### Configuration Builder
+
+#### `LevelPlayRewardedAd.Config.Builder()`
+Create a configuration builder for rewarded ads.
+
+**Returns:** `LevelPlayRewardedAd.Config.Builder` object
+
+**Usage:**
+```csharp
+var configBuilder = new LevelPlayRewardedAd.Config.Builder();
+```
+
+**Set minimum bid price:**
+```csharp
+configBuilder.SetBidFloor(1.0); // Minimum bid price in USD ($1.00 CPM)
+```
+
+**When to use:** Set a minimum bid price in USD for ad requests.
+
+#### `Build()`
+Build the configuration object.
+
+**Returns:** `LevelPlayRewardedAd.Config` object
+
+**Usage:**
+```csharp
+var config = configBuilder.Build();
+```
+
+### Core Methods
+
+#### `LoadAd()`
+Load a rewarded ad.
+
+**Usage:**
+```csharp
+rewardedAd.LoadAd();
+```
+
+**When to call:**
+- After SDK initialization completes
+- After showing an ad (to load the next one)
+- After a load or display failure
+
+#### `ShowAd()`
+Show the rewarded ad without a placement name.
+
+**Usage:**
+```csharp
+if (rewardedAd.IsAdReady())
+{
+ rewardedAd.ShowAd();
+}
+```
+
+**Important:** Always check `IsAdReady()` before calling `ShowAd()`.
+
+#### `ShowAd(placementName: string)`
+Show the rewarded ad with a named placement for analytics tracking.
+
+**Parameters:**
+- `placementName`: Placement name configured in LevelPlay dashboard
+
+**Usage:**
+```csharp
+rewardedAd.ShowAd(placementName: "extra_lives");
+```
+
+**When to use:** When tracking multiple placements within the same ad unit for analytics.
+
+#### `GetReward(string placementName = null)`
+Get the reward configuration for a placement.
+
+**Parameters:**
+- `placementName` (optional): The placement name to query. If null, returns default reward.
+
+**Returns:** `LevelPlayReward` object with Name and Amount
+
+**Usage:**
+```csharp
+// Get default reward
+LevelPlayReward defaultReward = rewardedAd.GetReward();
+Debug.Log($"Default reward: {defaultReward.Amount} {defaultReward.Name}");
+
+// Get placement-specific reward
+LevelPlayReward placementReward = rewardedAd.GetReward("extra_lives");
+Debug.Log($"Extra lives reward: {placementReward.Amount} {placementReward.Name}");
+```
+
+**When to use:** To display reward information to users before they watch the ad (e.g., "Watch ad to earn 5 coins").
+
+#### `IsAdReady()`
+Check if a rewarded ad is loaded and ready to show.
+
+**Returns:** `bool` - true if ready, false otherwise
+
+**Usage:**
+```csharp
+if (rewardedAd.IsAdReady())
+{
+ // Ad is ready to show
+ rewardedAd.ShowAd();
+}
+else
+{
+ // Ad not ready, maybe disable the button
+}
+```
+
+**Best practice:** Check this before showing ad UI buttons to users.
+
+#### `LevelPlayRewardedAd.IsPlacementCapped(string placementName)` (Static)
+Check if a placement has reached its capping limit.
+
+**Parameters:**
+- `placementName`: The placement name to check
+
+**Returns:** `bool` - true if capped, false otherwise
+
+**Usage:**
+```csharp
+if (LevelPlayRewardedAd.IsPlacementCapped("extra_lives"))
+{
+ Debug.Log("Extra lives placement is capped");
+}
+```
+
+**When to use:** Before showing an ad to check if the placement frequency cap is reached.
+
+### Events
+
+All events are properties of the `LevelPlayRewardedAd` object.
+
+**Threading:** All ad callbacks run on the Unity main thread, so you can safely call Unity APIs (update UI, access GameObjects, etc.) directly in these callbacks. This is different from `LevelPlay.OnImpressionDataReady` which runs on a background thread.
+
+#### `OnAdLoaded`
+Fired when a rewarded ad is successfully loaded.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+rewardedAd.OnAdLoaded += (adInfo) =>
+{
+ Debug.Log("Rewarded ad loaded");
+ // Enable UI button, etc.
+};
+```
+
+#### `OnAdLoadFailed`
+Fired when a rewarded ad fails to load.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+rewardedAd.OnAdLoadFailed += (error) =>
+{
+ Debug.LogWarning($"Load failed: {error.ErrorMessage}");
+ // Retry loading
+};
+```
+
+**Best practice:** Implement retry logic with exponential backoff.
+
+#### `OnAdDisplayed`
+Fired when a rewarded ad is displayed on screen.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+rewardedAd.OnAdDisplayed += (adInfo) =>
+{
+ Debug.Log("Rewarded ad displayed");
+ // Optional: Pause game
+};
+```
+
+#### `OnAdDisplayFailed`
+Fired when a rewarded ad fails to display.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+rewardedAd.OnAdDisplayFailed += (adInfo, error) =>
+{
+ Debug.LogError($"Display failed: {error.ErrorMessage}");
+ // Load a new ad
+ rewardedAd.LoadAd();
+};
+```
+
+#### `OnAdRewarded`
+Fired when the user has earned the reward.
+
+**Signature:** `event Action`
+
+**Parameters:**
+- `adInfo`: Information about the ad
+- `reward`: The reward object containing `Name` and `Amount`
+
+**Usage:**
+```csharp
+rewardedAd.OnAdRewarded += (adInfo, reward) =>
+{
+ Debug.Log($"User earned: {reward.Amount} {reward.Name}");
+ // Grant the reward to the user
+ GrantReward(reward);
+};
+```
+
+**Critical:** This is where you grant rewards to the user. Don't wait for `OnAdClosed` - users may close before the ad finishes.
+
+#### `OnAdClosed`
+Fired when the rewarded ad is closed.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+rewardedAd.OnAdClosed += (adInfo) =>
+{
+ Debug.Log("Rewarded ad closed");
+ // Resume game, load next ad
+ rewardedAd.LoadAd();
+};
+```
+
+**Best practice:** Load the next ad immediately in this callback.
+
+#### `OnAdClicked`
+Fired when the user clicks on the rewarded ad.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+rewardedAd.OnAdClicked += (adInfo) =>
+{
+ Debug.Log("Rewarded ad clicked");
+ // Optional analytics tracking
+};
+```
+
+#### `OnAdInfoChanged`
+Fired when ad information changes, such as when a new highest-paying ad becomes available.
+
+**Signature:** `event Action`
+
+**Usage:**
+```csharp
+rewardedAd.OnAdInfoChanged += (adInfo) =>
+{
+ Debug.Log($"Ad info changed - New ad from: {adInfo.AdNetwork}");
+ Debug.Log($"Estimated revenue: ${adInfo.Revenue}");
+};
+```
+
+**Important:** This event indicates that a new ad with potentially different revenue has become available. The SDK automatically selects the highest-paying ad when you call `ShowAd()`, but this event lets you know when the ad info has been updated. This is particularly important for revenue optimization as it signals when a better-paying ad has loaded.
+
+**When it fires:**
+- After a new ad loads with different properties than the previous one
+- When the waterfall selects a different ad network
+- When ad auction results change
+
+**Why it matters:** The updated `LevelPlayAdInfo` contains the latest revenue estimates and network information, which directly impacts your monetization. Always use the most recent `adInfo` when logging or analyzing ad performance.
+
+**If you're using ILRD** (`references/ilrd-api.md`): the `LevelPlayImpressionData` you receive in `OnImpressionDataReady` already contains the final revenue value, so `OnAdInfoChanged` is mostly useful for in-Editor debugging of the waterfall. Most publishers can leave it as a logging hook.
+
+## Data Types
+
+### LevelPlayReward
+
+Represents the reward earned by the user.
+
+**Properties:**
+- `Name` (string): The reward name (e.g., "coins", "life")
+- `Amount` (int): The reward amount (e.g., 10, 1)
+
+**Usage:**
+```csharp
+private void OnAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
+{
+ Debug.Log($"Reward: {reward.Amount} {reward.Name}");
+
+ if (reward.Name == "coins")
+ {
+ playerCoins += reward.Amount;
+ }
+}
+```
+
+### LevelPlayAdInfo
+
+Contains information about the ad.
+
+**Properties:**
+- `AdId` (string): Unique identifier for this specific ad instance
+- `AdUnitId` (string): The ad unit identifier
+- `AdUnitName` (string): The ad unit name
+- `AdSize` (LevelPlayAdSize): The ad size (may be null; banner-relevant)
+- `AdFormat` (string): The ad format (e.g., "REWARDED")
+- `PlacementName` (string): Placement name where ad was shown
+- `AuctionId` (string): Unique auction identifier
+- `CreativeId` (string): Creative identifier
+- `Country` (string): User's country code (ISO 3166-1)
+- `Ab` (string): A/B test segment identifier
+- `SegmentName` (string): User segment name
+- `AdNetwork` (string): Ad network that served the ad
+- `InstanceName` (string): Ad network instance name
+- `InstanceId` (string): Ad network instance identifier
+- `Revenue` (double?): Estimated revenue in USD (nullable - check for null)
+- `Precision` (string): Revenue precision level
+- `EncryptedCPM` (string): Encrypted CPM value
+
+### LevelPlayAdError
+
+Contains error information when ad operations fail.
+
+**Properties:**
+- `ErrorCode` (int): Numeric error code
+- `ErrorMessage` (string): Human-readable error description
+- `AdUnitId` (string): The ad unit identifier where the error occurred
+- `AdId` (string): Unique identifier for the specific ad instance
+
+## Best Practices
+
+### Loading Strategy
+
+**Always keep a rewarded ad ready:**
+```csharp
+// Load immediately after init
+void OnInitSuccess(LevelPlayConfiguration config)
+{
+ rewardedAd = new LevelPlayRewardedAd(adUnitId);
+ rewardedAd.OnAdLoaded += OnAdLoaded;
+ rewardedAd.OnAdClosed += OnAdClosed;
+ rewardedAd.LoadAd();
+}
+
+// Reload immediately after showing
+void OnAdClosed(LevelPlayAdInfo adInfo)
+{
+ rewardedAd.LoadAd();
+}
+```
+
+### UI Integration
+
+**Show/hide buttons based on ad availability:**
+```csharp
+public Button watchAdButton;
+
+void Update()
+{
+ // Enable button only if ad is ready
+ watchAdButton.interactable = rewardedAd != null && rewardedAd.IsAdReady();
+}
+
+void OnAdLoaded(LevelPlayAdInfo adInfo)
+{
+ watchAdButton.interactable = true;
+}
+
+void OnAdLoadFailed(LevelPlayAdError error)
+{
+ watchAdButton.interactable = false;
+}
+```
+
+### Reward Granting
+
+**Grant rewards in `OnAdRewarded`, not `OnAdClosed`:**
+```csharp
+// ✅ CORRECT
+private void OnAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
+{
+ GrantReward(reward); // Grant immediately
+}
+
+// ❌ WRONG - user may close ad early
+private void OnAdClosed(LevelPlayAdInfo adInfo)
+{
+ // Don't grant reward here!
+}
+```
+
+**Note:** `OnAdRewarded` and `OnAdClosed` are asynchronous — `OnAdRewarded` may fire after `OnAdClosed`. Do not write logic in `OnAdClosed` that assumes the reward has already been granted.
+
+### Error Handling
+
+**Handle ad load failures:**
+```csharp
+private void OnAdLoadFailed(LevelPlayAdError error)
+{
+ Debug.LogWarning($"Ad load failed: {error.ErrorMessage}");
+
+ // Retry loading after a delay
+ Invoke(nameof(RetryLoad), 30f);
+}
+
+private void RetryLoad()
+{
+ rewardedAd.LoadAd();
+}
+```
+
+### Multiple Placements
+
+**Use placements to track different reward contexts:**
+- Create placements in LevelPlay dashboard (e.g., "hints", "extra_lives")
+- Use `ShowAd(placementName: "name")` to track separately
+- Check `IsPlacementCapped()` before showing
+- Handle rewards differently based on placement in `OnAdRewarded`
+
+### Memory Management
+
+**Always unsubscribe from events:**
+```csharp
+void OnDestroy()
+{
+ if (rewardedAd != null)
+ {
+ rewardedAd.OnAdLoaded -= OnAdLoaded;
+ rewardedAd.OnAdLoadFailed -= OnAdLoadFailed;
+ rewardedAd.OnAdDisplayed -= OnAdDisplayed;
+ rewardedAd.OnAdDisplayFailed -= OnAdDisplayFailed;
+ rewardedAd.OnAdRewarded -= OnAdRewarded;
+ rewardedAd.OnAdClosed -= OnAdClosed;
+ rewardedAd.OnAdClicked -= OnAdClicked;
+ rewardedAd.OnAdInfoChanged -= OnAdInfoChanged;
+ }
+}
+```
+
+## Common Issues
+
+### Issue: Error 1037 (Load during load)
+
+**Cause:** Calling `LoadAd()` repeatedly in `Update()` or other high-frequency loops before the previous load completes.
+
+**Common mistake:**
+```csharp
+void Update()
+{
+ // WRONG - This will cause error 1037!
+ if (!rewardedAd.IsAdReady())
+ {
+ rewardedAd.LoadAd();
+ }
+}
+```
+
+**Why this fails:** If the ad is still loading, calling `LoadAd()` again triggers error 1037: "Cannot load while another load is in progress."
+
+**Solution:** Track load state manually and only call `LoadAd()` once:
+```csharp
+private bool isLoadingAd = false;
+
+void Start()
+{
+ LoadRewardedAd();
+}
+
+void LoadRewardedAd()
+{
+ if (isLoadingAd) return; // Prevent duplicate loads
+
+ isLoadingAd = true;
+ rewardedAd.LoadAd();
+}
+
+void OnAdLoaded(LevelPlayAdInfo adInfo)
+{
+ isLoadingAd = false;
+}
+
+void OnAdLoadFailed(LevelPlayAdError error)
+{
+ isLoadingAd = false;
+ // Optionally retry after delay
+}
+```
+
+### Issue: Reward not granted to user
+
+**Possible causes:**
+- Granting reward in `OnAdClosed` instead of `OnAdRewarded`
+- User closes ad before completion
+- Reward logic has bugs
+
+**Solution:**
+Always grant rewards in `OnAdRewarded`, which fires when the user has earned the reward (typically after watching enough of the ad).
+
+### Issue: Ad not loading
+
+**Possible causes:**
+- SDK not initialized before creating ad object
+- No internet connection
+- Ad inventory not available in test region
+- Incorrect ad unit ID
+
+**Solutions:**
+- Create ad object only after `OnInitSuccess` callback
+- Check network connectivity
+- Verify ad unit ID in LevelPlay dashboard
+- Test on real device with good connection
+
+### Issue: Button always disabled
+
+**Possible causes:**
+- Not checking `IsAdReady()` correctly
+- Ad failed to load and no retry logic
+- Event subscriptions not working
+
+**Solutions:**
+- Implement `OnAdLoaded` callback to enable button
+- Add retry logic in `OnAdLoadFailed`
+- Debug log all callbacks to verify they fire
+
+### Issue: Multiple reward grants
+
+**Possible causes:**
+- `OnAdRewarded` callback subscribed multiple times
+- Not tracking if reward already granted
+
+**Solutions:**
+- Unsubscribe in `OnDestroy()`
+- Only subscribe once in initialization
+- Use flag to track if reward granted for this ad
+
+## Testing Checklist
+
+- [ ] Ad loads successfully after SDK initialization
+- [ ] `IsAdReady()` returns true when ad is loaded
+- [ ] Ad displays correctly when `ShowAd()` is called
+- [ ] `OnAdRewarded` fires and reward is granted
+- [ ] UI button enables when ad is ready
+- [ ] UI button disables when ad is not ready
+- [ ] Ad reloads automatically after being shown
+- [ ] Retry logic works when ad fails to load
+- [ ] Multiple placements track correctly (if using)
+- [ ] Placement capping works (if configured)
+- [ ] Memory leaks prevented (events unsubscribed)
+- [ ] Tested on multiple devices and network conditions
From 49b9278814dd07a167235eafcfed8410bb0367e6 Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 15:38:34 -0400
Subject: [PATCH 03/10] Update SKILL.md: legal and language changes
---
skills/levelplay-unity-integration/SKILL.md | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/skills/levelplay-unity-integration/SKILL.md b/skills/levelplay-unity-integration/SKILL.md
index 2801e55..46f9160 100644
--- a/skills/levelplay-unity-integration/SKILL.md
+++ b/skills/levelplay-unity-integration/SKILL.md
@@ -5,11 +5,11 @@ description: Use when integrating LevelPlay (IronSource) ads into a Unity projec
# LevelPlay Unity package/SDK Integration
-Use Unity.RunCommand for editor-side checks and operations. The C# scripts generated in this skill are MonoBehaviour files for the user to save to their project, not for inline execution.
+The C# scripts generated in this skill are MonoBehaviour files for the user to save to their project, not for inline execution.
Follow the integration workflow sequentially, one step at a time. Ask only the questions for the current step — do not gather information for future steps in advance. Wait for the user's response at each checkpoint before proceeding.
-LevelPlay is Unity's ad mediation platform: it connects your game to multiple ad networks simultaneously and automatically shows the highest-paying ad available. This guide walks you through the full integration: installing the SDK, configuring dependencies for Android and iOS, initializing LevelPlay in your project, and implementing rewarded, interstitial, and banner ads. If you already have part of this set up, you can skip ahead to the relevant step.
+LevelPlay is Unity's ad mediation platform: it connects your game to multiple ad networks simultaneously and runs a unified auction across multiple ad networks and bidders to maximize competition for each impression. This guide walks you through the full integration: installing the SDK, configuring dependencies for Android and iOS, initializing LevelPlay in your project, and implementing rewarded, interstitial, and banner ads. If you already have part of this set up, you can skip ahead to the relevant step.
## Integration Workflow
@@ -164,14 +164,9 @@ This configuration is required for AdMob to work as a mediation network in Level
### 6.5. Privacy & Regulation Settings (If Required)
-**When to use**: If your app serves users in the EU (GDPR), California (CCPA), or is directed at children (COPPA).
+> **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.
-Ask the user: "Does your app need to comply with any privacy regulations?"
-- **GDPR** (EU users): Requires user consent for data collection
-- **CCPA** (California users): Requires "Do Not Sell" opt-out option
-- **COPPA** (Child-directed apps): Restricts data collection for children under 13
-
-If you're unsure whether your app reaches EU users, implement GDPR handling as a precaution — it's better to request consent you don't strictly need than to miss it where you do.
+Ask the user: "Do you need to configure privacy settings for GDPR, CCPA/CPRA (or certain state privacy consumer acts), or for child-directed apps?"
**If YES to any:**
@@ -224,7 +219,7 @@ Call these BEFORE `LevelPlay.Init()` in Step 7.
For complete implementation with UI, consent management, and combined regulations, see `references/privacy-settings.md`.
-**For iOS builds — required regardless of privacy regulations above:** Also implement App Tracking Transparency (ATT) before proceeding to Step 7. ATT must be called before `LevelPlay.Init()` to maximize ad fill rate on iOS 14+. See `references/ios-setup.md` for the ATT implementation code.
+**For iOS builds — required regardless of privacy regulations above:** Also implement App Tracking Transparency (ATT) before proceeding to Step 7. Apple requires ATT authorization before your app tracks users or accesses the device's advertising identifier on iOS 14.5+. Request ATT authorization before calling `LevelPlay.Init()` — this is both an Apple platform requirement and necessary for personalized ads (which also affects fill rate). See `references/ios-setup.md` for the ATT implementation code.
**If NO privacy regulations and not targeting iOS:** Skip this step and proceed to Step 7.
@@ -1109,3 +1104,4 @@ Read specific references based on what the user is implementing:
8. Recommend balanced strategy (Step 8)
9. Ask to see existing GameManager.cs, then provide code snippets (Step 9)
10. Guide through testing (Step 10)
+
From 7d90c44c0850bbbe1ea01b9b67193e78dc62c5bf Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 15:39:55 -0400
Subject: [PATCH 04/10] Update README with privacy and legal note
Added privacy and legal disclaimer regarding LevelPlay integration guidance.
---
skills/levelplay-unity-integration/README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/skills/levelplay-unity-integration/README.md b/skills/levelplay-unity-integration/README.md
index 511c1d6..c61c004 100644
--- a/skills/levelplay-unity-integration/README.md
+++ b/skills/levelplay-unity-integration/README.md
@@ -2,7 +2,7 @@
  
-> 🧪 **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?usp=sharing&ouid=116753073211029919766)!
+> 🧪 **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.
@@ -50,6 +50,10 @@ Type `/levelplay-unity-integration` to activate the skill, then describe what yo
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
```
From 9a94df53cfd91095647416bbd0e590f29ccbcdb2 Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 15:44:31 -0400
Subject: [PATCH 05/10] Updated date and feedback form URL
---
skills/levelplay-unity-integration/CHANGELOG.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/skills/levelplay-unity-integration/CHANGELOG.md b/skills/levelplay-unity-integration/CHANGELOG.md
index d64b97c..580a05f 100644
--- a/skills/levelplay-unity-integration/CHANGELOG.md
+++ b/skills/levelplay-unity-integration/CHANGELOG.md
@@ -1,6 +1,6 @@
# Changelog
-## v0.7.0 — 2026-06-05 — Initial public beta release
+## v0.7.0 — 2026-06-12 — Initial public beta release
First release of the LevelPlay Unity integration skill, released as public beta.
@@ -17,4 +17,4 @@ First release of the LevelPlay Unity integration skill, released as public beta.
## Feedback
-This skill is currently in beta. Share your feedback here: https://docs.google.com/forms/d/e/1FAIpQLSe7WvWozJ67KjgOLglSBvLug8JdgEYk895nn_BHZs0HS_bWJA/viewform?usp=sharing&ouid=116753073211029919766
+This skill is currently in beta. [Share your feedback here](https://docs.google.com/forms/d/e/1FAIpQLSe7WvWozJ67KjgOLglSBvLug8JdgEYk895nn_BHZs0HS_bWJA/viewform).
From 322ee879b18f12f06939b8324a753a151fad0efd Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 15:46:21 -0400
Subject: [PATCH 06/10] Improve clarity in privacy settings documentation
Reworded privacy settings overview and guidelines for clarity, as per legal team feedback.
---
.../references/privacy-settings.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/skills/levelplay-unity-integration/references/privacy-settings.md b/skills/levelplay-unity-integration/references/privacy-settings.md
index cdcbbee..474ee3f 100644
--- a/skills/levelplay-unity-integration/references/privacy-settings.md
+++ b/skills/levelplay-unity-integration/references/privacy-settings.md
@@ -14,7 +14,7 @@
## Overview
-LevelPlay SDK provides APIs to comply with privacy regulations including GDPR, CCPA, and COPPA. These settings must be configured before SDK initialization to ensure compliance with regional privacy laws.
+LevelPlay SDK provides APIs to help facilitate privacy regulation requirements including GDPR, CCPA, and COPPA. These settings must be configured before SDK initialization.
**SDK Version Requirement:** SDK 9.4.0+ for current APIs (older deprecated APIs available in SDK 9.3.0 and lower)
@@ -352,7 +352,7 @@ If your app is child-directed:
1. **Disable personalized ads**: COPPA requires non-personalized ads only
2. **Google Play Families Policy**: If targeting children on Google Play:
- Set COPPA flag: `LevelPlayPrivacySettings.SetCOPPA(true)`
- - Ensure ad networks comply with Google Play Families requirements
+ - Review Google Play Families requirements for child-directed apps
- Test thoroughly before submission
3. **App Store age ratings**: Set appropriate age ratings in both app stores
4. **Privacy policy**: Clearly state data collection practices for children
@@ -536,7 +536,7 @@ LevelPlay.SetMetaData("is_child_directed", "false");
4. **Provide UI controls**: Give users easy access to privacy settings
5. **Respect user choices**: Honor opt-outs and consent denials
6. **Update promptly**: When user changes preferences, apply immediately via privacy APIs
-7. **Test thoroughly**: Verify compliance in all supported regions
+7. **Test thoroughly**: Test your privacy settings in all supported regions
8. **Document clearly**: Include privacy policy links in your app
9. **Use consent management platforms**: Consider third-party CMPs for complex consent requirements
10. **Stay updated**: Privacy regulations evolve, monitor SDK updates for compliance changes
From 148e94636120cf6bd5c4b1b9c78822c765cee0c2 Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 15:48:34 -0400
Subject: [PATCH 07/10] Clarify OnAdInfoChanged event language
Updated comments and descriptions for clarity regarding ad information changes and auction results, based on legal team feedback.
---
.../levelplay-unity-integration/references/rewarded-api.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/skills/levelplay-unity-integration/references/rewarded-api.md b/skills/levelplay-unity-integration/references/rewarded-api.md
index 0e38c19..26bdd89 100644
--- a/skills/levelplay-unity-integration/references/rewarded-api.md
+++ b/skills/levelplay-unity-integration/references/rewarded-api.md
@@ -46,7 +46,7 @@ public class RewardedAdManager : MonoBehaviour
rewardedAd.OnAdRewarded += OnAdRewarded;
rewardedAd.OnAdClosed += OnAdClosed;
rewardedAd.OnAdClicked += OnAdClicked;
- rewardedAd.OnAdInfoChanged += OnAdInfoChanged; // Fires when the loaded ad swaps to a higher-paying network
+ rewardedAd.OnAdInfoChanged += OnAdInfoChanged; // Fires when the loaded ad updates after a new auction result
// Load the ad
LoadAd();
@@ -580,7 +580,7 @@ rewardedAd.OnAdClicked += (adInfo) =>
```
#### `OnAdInfoChanged`
-Fired when ad information changes, such as when a new highest-paying ad becomes available.
+Fired when ad information changes, such as when a new winning ad becomes available after auction.
**Signature:** `event Action`
@@ -593,7 +593,7 @@ rewardedAd.OnAdInfoChanged += (adInfo) =>
};
```
-**Important:** This event indicates that a new ad with potentially different revenue has become available. The SDK automatically selects the highest-paying ad when you call `ShowAd()`, but this event lets you know when the ad info has been updated. This is particularly important for revenue optimization as it signals when a better-paying ad has loaded.
+**Important:** This event indicates that a new ad with potentially different revenue has become available. The SDK automatically selects the winning ad from the auction when you call `ShowAd()`, but this event lets you know when the ad info has been updated. This is particularly important for revenue optimization as it signals when a new auction winner has loaded.
**When it fires:**
- After a new ad loads with different properties than the previous one
From af3f2a3150a73b3ba6e49a7a4ba7058b2813f809 Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 15:50:13 -0400
Subject: [PATCH 08/10] Clarify OnAdInfoChanged event language
Updated descriptions for OnAdInfoChanged event and its importance in revenue optimization, based on legal team feedback.
---
.../references/interstitial-api.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/skills/levelplay-unity-integration/references/interstitial-api.md b/skills/levelplay-unity-integration/references/interstitial-api.md
index 7a99111..3aceffa 100644
--- a/skills/levelplay-unity-integration/references/interstitial-api.md
+++ b/skills/levelplay-unity-integration/references/interstitial-api.md
@@ -45,7 +45,7 @@ public class InterstitialAdManager : MonoBehaviour
interstitialAd.OnAdDisplayFailed += OnAdDisplayFailed;
interstitialAd.OnAdClicked += OnAdClicked;
interstitialAd.OnAdClosed += OnAdClosed;
- interstitialAd.OnAdInfoChanged += OnAdInfoChanged; // Fires when the loaded ad swaps to a higher-paying network
+ interstitialAd.OnAdInfoChanged += OnAdInfoChanged; // Fires when the loaded ad updates after a new auction result
// Load the ad
LoadAd();
@@ -604,7 +604,7 @@ interstitialAd.OnAdClosed += (adInfo) =>
**Best practice:** Load the next ad immediately in this callback.
#### `OnAdInfoChanged`
-Fired when ad information changes, such as when a new highest-paying ad becomes available.
+Fired when ad information changes, such as when a new winning ad becomes available after auction.
**Signature:** `event Action`
@@ -617,7 +617,7 @@ interstitialAd.OnAdInfoChanged += (adInfo) =>
};
```
-**Important:** This event indicates that a new ad with potentially different revenue has become available. The SDK automatically selects the highest-paying ad when you call `ShowAd()`, but this event lets you know when the ad info has been updated. This is particularly important for revenue optimization as it signals when a better-paying ad has loaded.
+**Important:** This event indicates that a new ad with potentially different revenue has become available. The SDK automatically selects the winning ad from the auction when you call `ShowAd()`, but this event lets you know when the ad info has been updated. This is particularly important for revenue optimization as it signals when a new auction winner has loaded.
**When it fires:**
- After a new ad loads with different properties than the previous one
From d3537ba1dcfd4a74360b7fb430381442533d2038 Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 15:51:33 -0400
Subject: [PATCH 09/10] Revise iOS setup documentation for LevelPlay
integration
Updated iOS setup guide for LevelPlay with detailed instructions on SKAdNetwork, App Tracking Transparency, and privacy manifest requirements, based on legal feedback.
---
skills/levelplay-unity-integration/references/ios-setup.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/skills/levelplay-unity-integration/references/ios-setup.md b/skills/levelplay-unity-integration/references/ios-setup.md
index 20e0a25..8bff456 100644
--- a/skills/levelplay-unity-integration/references/ios-setup.md
+++ b/skills/levelplay-unity-integration/references/ios-setup.md
@@ -66,7 +66,7 @@ After building to Xcode, you should see entries like this in Info.plist:
### What is ATT?
-Starting with iOS 14.5, apps must request user permission before tracking them across apps and websites. This is required for personalized advertising. ATT must be requested **before** `LevelPlay.Init()` to maximise ad fill rate.
+Apple requires ATT authorization before your app tracks users or accesses the device's advertising identifier on iOS 14.5+. Request ATT authorization before calling `LevelPlay.Init()` — this is both an Apple platform requirement and necessary for personalized ads (which also affects fill rate).
### Implementation
From e975e6f5a4705edb629c8aed613bbb82096c8b79 Mon Sep 17 00:00:00 2001
From: kimberleymday <112126541+kimberleymday@users.noreply.github.com>
Date: Fri, 12 Jun 2026 15:54:41 -0400
Subject: [PATCH 10/10] Clarify legal advice disclaimer in privacy settings
Added a note clarifying that the guidance does not constitute legal advice and emphasized the importance of consulting legal counsel.
---
.../levelplay-unity-integration/references/privacy-settings.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/skills/levelplay-unity-integration/references/privacy-settings.md b/skills/levelplay-unity-integration/references/privacy-settings.md
index 474ee3f..3cb426a 100644
--- a/skills/levelplay-unity-integration/references/privacy-settings.md
+++ b/skills/levelplay-unity-integration/references/privacy-settings.md
@@ -14,6 +14,8 @@
## Overview
+> **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.
+
LevelPlay SDK provides APIs to help facilitate privacy regulation requirements including GDPR, CCPA, and COPPA. These settings must be configured before SDK initialization.
**SDK Version Requirement:** SDK 9.4.0+ for current APIs (older deprecated APIs available in SDK 9.3.0 and lower)