Scope and hard boundaries
This Skill only handles Banner, interstitial, and rewarded video ad capabilities for three.js + TypeScript/Vite + Capacitor mobile projects, with the aggregation layer fixed to AppLovin MAX and the integration implementation fixed to the officially maintained AppLovin cordova-plugin-applovin-max, loaded by Capacitor's Cordova compatibility layer. The project only establishes a business facade and an official plugin API adapter on the TypeScript side; it does not build its own Android/iOS ad plugin, nor does it directly integrate the MAX native SDK. All configured ad placements maintain state independently; interstitials and rewarded videos are only silently preloaded in the background, and ad objects or views are not actively created during the initialization phase, with the plugin managing loading resources on demand internally. Successful preloading of interstitials and rewarded videos is determined by the readiness property of the current ad instance object; the loaded callback and business phase cannot alone prove success. Full-screen display reads the instance readiness property and existing business state and returns immediately; Banner is not preloaded and is only created and loaded when business requests display; when Banner is supported, the ad placement is enabled, and the page allows display, the ad area is reserved before the page's first frame and UI layout is completed, without waiting for ad loading or display. Each ad placement independently maintains a load failure count, retry deadline, and at most one retry timer, but uses the same classification, backoff, pause, and resume rules. Before starting any design, implementation, or review, the sole detailed contract must first be read AppLovin MAX integration contract, and the state, timing, failure, and compliance rules in it shall prevail.
- ThreeJS Web, browser preview, and mini-games do not directly call MAX, nor do they load the native ad SDK; on these runtime targets, ad capabilities must be explicitly
unsupported/no-op, returning immediately after the call and continuing the game. - iOS and Android only use the official AppLovin
cordova-plugin-applovin-max. Before implementation, it is mandatory to check against theofficial Cordova integration documentationandofficial repositoryto verify the plugin version, Capacitor Cordova compatibility layer, Android Gradle, and iOS package manager; if incompatible, block and report, and do not fall back to community plugins, self-built Capacitor native plugins, or business code directly connecting to the MAX SDK. - This Skill does not automatically configure the MAX console, ad network accounts, store metadata, or release channels for the project. SDK keys and ad unit IDs are injected into the official plugin adapter layer from controlled build configuration per environment, and are not hardcoded into business source code, examples, logs, or commit records; ad network account credentials and Ad Review keys must not enter Web code or public artifacts.
- Ads are only attempted at natural interruption points; the caller never waits for ad loading, network requests, or display completion. When an ad cannot be displayed, the result must be returned immediately according to the current state of the ad placement, and must not block scene transitions, input, the main loop, or settlement flow. Background loading, load failures, and retries remain silent, with no Toast.
- When a video ad placement is triggered for display by the user but is currently unavailable, or after the official plugin accepts the display and calls back
displayFailed, a single “Video ad temporarily unavailable, please try again later” Toast is shown through the unified UI layer; the plugin adapter layer must not directly manipulate ThreeJS UI, and repeated callbacks must not cause repeated prompts. - The privacy popup must always use MAX's built-in Terms and Privacy Policy Flow and its integrated Google UMP; before initialization, enable the flow and configure the privacy policy URL, then call MAX initialize to start the built-in flow; on the user side, first complete the MAX privacy popup, and whether consent is given or denied, continue to complete initialization, with the SDK initialization completion callback as the standard. During the popup, remain in initializing and do not initiate a second initialization or retry. Do not create a separate ThreeJS/Web privacy popup or an independent CMP flow; iOS ATT is handled by this flow according to the official configuration to avoid duplicate requests. The specific plugin configuration and initialization callback semantics are subject to the detailed contract.
- Banner does not participate in initialization preloading; the native view is created and loaded only on the first request for display, and the call returns immediately. It is shown when loaded is received and the layout is safe and the business still requests visibility; if creation or loading fails, it remains hidden and continues retrying with the same independent backoff; when leaving the display scenario, clear the visibility request and cancel retries, and old callbacks must not restore creation or display. Do not show a Toast, do not occupy error height, and do not wait for loading.
Global control access
$threejs-game-workflow-control is the only global state, risk gate, task scope, and evidence control plane. This domain may propose solutions, review the current state, and implement within the current Work Item, the frozen Implementation Package, and the A-level and path scope; all plans, changes, test evidence, and blockers must be registered and reviewed back at the control plane. When adjusting interfaces, paths, or verification scope within a task, update the Work Item accordingly and do not create a separate parallel state machine.
For A4-A6 actions involving external writes, payments, real devices, store submissions, releases, or irreversible operations, explicit approval from the control plane must be obtained per object; this Skill does not approve, execute, or push these actions on its behalf, nor does it automatically initiate real-device acceptance runs. Ordinary local documents, bridge code, and unit tests are executed under the current task authorization.
Delivery process
- Read the current Work Item, the project TDD/dependency capability profile, the Capacitor platform status, and the official plugin installation status; according to the AppLovin MAX integration contractregister the scope, non-goals, public interfaces, state ownership, lifecycle, and failure boundaries. Advertising is an optional commercial capability and must not be used as a prerequisite for the operation of the ThreeJS Web core loop.
- Submit a minimal module proposal: Web/mini-game no-op, the official AppLovin Cordova plugin, a shared TypeScript business facade and plugin API adapter, and test doubles. Verify the official plugin release version, license, Android/iOS support, Capacitor compatibility approach, callback semantics, and privacy capabilities; do not propose community plugins or a self-built native bridge as fallback implementations.
- Freeze the plugin adapter contract:
initialize、preload(slotId)、tryShow(context)、setBannerVisibility(slotId, visible)、isReady(slotId)are all non-blocking calls and return structured results; the adapter normalizes the official plugin's callback/event API into this contract. Initialization failure entersfailed: Explicit transient failures are continuously backed off by the service's sole initialization retry task at 2/4/8/16/32/64 seconds; paused when background, offline, or host conditions are not met, and resumed after recovery; explicitly retried after configuration or dependency errors are corrected. Initialization retries are isolated from ad slot loading retries, and do not share timers or counters. Service initialization is idempotent; when the platform is supported, the service is enabled, and the plugin configuration is valid, MAX initialization must be initiated, without privacy consent, CMP completion, or ATT authorization as a precondition for the call. Each ad slot has only one business state owner, and native format objects are managed internally by the official plugin. After successful initialization, immediately deliver the preload intents for all enabled interstitial and rewarded video ads; actual loading passes through the ad request privacy gate; all initiated load failures are continuously scheduled by each ad slot's own retry timer, without terminating by error category and without a retry count limit, until success; disabling, removal, or service destruction terminates retries, and runtime gates closing only pauses them. Each ad slot independently stores its count and deadline, and applies the same set of2/4/8/16/32/64 秒backoff, jitter, deduplication, background/offline pause, and resume rules. Canceling, reordering, or triggering a retry for one ad slot must not change the timers of other ad slots. - Register separate owners for the MAX console, the official plugin and its native dependencies, the Capacitor Cordova compatibility configuration, MAX's built-in privacy flow/Google UMP/ATT, ad slot configuration, and telemetry. The fixed aggregation channels are AppLovin, Google AdMob, Mintegral, Pangle, Unity Ads, DT Exchange (Fyber), and Verve (PubNative/HyBid); the version combinations of the plugin, MAX SDK, and adapters are subject to the compatibility ranges currently declared by the official plugin and official channel documentation, and version numbers must not be hardcoded in the Skill or business code, nor may the MAX SDK be repeatedly introduced bypassing the plugin.
- Call at a natural interruption point
tryShow; the call reads the readiness property and business snapshot of the currently valid instance of the specified ad slot, and never triggers foreground loading. After the matching rewarded videohiddencallback arrives, the ad service uses a monotonic clock to record a 30-second interstitial protection deadline; during the protection period, the interstitialtryShowmust immediately returnrewarded-interstitial-delay, and must not call MAX show, create a cooldown timer, or pop up an unavailable Toast. After the protection period ends, as long as the target ad slot is ready, all gates are open, and the natural interruption conditions hold, a display may be initiated. If not ready, the same ad slot is currently displaying, privacy is undecided, the network is unavailable, or the platform is unsupported, immediately return and continue the game; when a video ad is unavailable or display fails, the unified UI layer consumes the structured result/event and displays an unavailable Toast once. Rewards are granted asynchronously only by the matchingrewardedcallback and remain idempotent; after closing or display failure, immediately silently preload the corresponding full-screen ad slot. - When entering a page that can display a Banner, first determine the reserved size and content inset according to the current platform format, available width, and safe area, and complete the UI layout before the page's first frame; layout planning does not create an ad view or request an ad. When the Banner is displayed, place it only in the reserved area and do not adjust the game UI; if the actual size cannot be safely accommodated, keep it hidden and handle it in the next page or viewport layout cycle. The Banner uses a native ad view; the business only controls show/hide and does not create its own refresh timer. Pause refresh when hidden, switched to background, or the page does not allow ads, and resume according to the contract when display resumes; must not cover game buttons, gesture areas, system safe areas, or treat a blank placeholder as a loaded ad.
- First complete static review, TypeScript/native compilation, and state machine unit tests, then run platform integration tests in an authorized target environment. Use MAX Test Mode and Mediation Debugger to verify separately by channel and format, checking
app-ads.txt、iOS SKAdNetwork、Google CMP/TCF and ATT; do not pass off success on a single network or in a single format as all passing. - Submit the candidate diff, interface contract, platform logs (redacted), failed retry traces, and verification results to
$threejs-game-workflow-control; list unexecuted external configuration, real devices, live ads, and release steps as explicitly uncovered items.
Minimum public contract
The shared facade must provide at least the following semantics; specific parameter naming may follow the target project's conventions, but the return timing must not be changed:
initialize(config): Promise<InitResult> // 只确认桥接请求受理/初始化状态,不等待广告加载
retryInitialize(): Promise<InitResult> // 手动入口复用初始化恢复任务,不绕过退避或配置阻断
preload(slotId): Promise<PreloadResult> // 仅插页/激励后台加载;Banner 返回 format-mismatch
isReady(slotId): ReadyResult // 全屏读取实例就绪属性;Banner 读取加载状态,不创建或加载
tryShow(context): Promise<ShowResult> // 复核实例就绪属性和业务门,通过后才展示
setBannerVisibility(slotId, visible): Promise<BannerVisibilityResult> // 展示时按需创建/加载,立即返回
The result must contain at least a stable ok、code、platform、phase、gates and, where necessary, a retryScheduled field; when an interstitial is skipped due to the 30-second protection period after a rewarded video, return rewarded-interstitial-delay and interstitialDelayRemainingMs. Business callers pass only the environment, capability switches, and business slotId; the official plugin adapter resolves the SDK key and MAX ad unit ID by environment, and uses them only at the plugin API boundary. Plugin raw exceptions, device IDs, ad network account credentials, or full waterfall credentials must not be passed through to the business layer.tryShow Returning "show requested" does not mean the show succeeded.
InitResult、PreloadResult、ReadyResult、ShowResult and NativeAdEvent 's minimum fields, privacy gate/result codes,instanceGeneration/showRequestId association, and duplicate-event idempotency rules are uniformly governed byintegration contract.
Delivery review checklist
- Platform boundary: Web/mini-game calls are no-ops; Android/iOS invoke MAX only through the official AppLovin Cordova plugin, with no community plugins, self-built native ad plugins, or direct SDK integration introduced; host page lifecycle, Activity/ViewController, and foreground/background transitions are made explicit.
- Lifecycle: initialization is idempotent; each configured ad placement holds a business phase, load count, retry deadline, and at most one load retry timer, and format-matching native objects are managed by the official plugin; the ad service additionally holds the 30-second interstitial protection deadline after a rewarded video ends, but does not create a timer for it. All ad placements share service-level gates such as this cross-format protection state, do not share load retry timers, and do not set a cross-placement fullscreen mutex; instance recreation increments
instanceGeneration, and a show request generates a uniqueshowRequestId; after successful initialization, all enabled interstitial and rewarded video preload intents are immediately dispatched, actual loading passes through the ad request privacy gate, and repeated calls do not concurrently request. - Native gates: the Activity/ViewController provider, foreground state, network connectivity, and privacy readiness are native service dependencies;
phaseindicates only the ad asset/show phase, gate state is calculated independently, and ThreeJS scene state is not mixed in. - Pacing: interstitials are shown only at natural interruption points; a matching rewarded video
hiddenevent blocks interstitials for 30 seconds from its monotonic timestamp; the protection period blocks only interstitials, not banners, rewarded videos, background preloading, or reward settlement. During the protection period, the remaining milliseconds are returned immediately and silently; after expiration, the decision resumes without needing a timer wake-up;hiddenanddisplayFailedboth trigger background preloading for the corresponding ad placement. - Readiness determination: the ready property of a fullscreen ad instance is the sole basis for successful preloading; the adapter reads the plugin's internal instance state through the official isInterstitialReady/isRewardedAdReady and does not separately store an independent ready boolean. loaded only triggers re-checking; before showing and retrying, the state is re-read, and if the instance does not exist, is invalid, or the read fails, it is treated as not ready; banners are still managed by post-creation load events and do not participate in preloading.
- Initialization recovery: the service has at most one initialization request and one pending retry timer; transient failures keep backing off, a closed host/foreground/network gate preserves the deadline, privacy state does not pause initialization retries, and recovery deduplicates scheduling; configuration errors are not retried automatically. Success, disablement, or destruction cleans up the initialization recovery task, and after success the full-screen preload intent is delivered only once; ad requests still check the privacy gate, and when blocked the intent is preserved for recovery.
- Failure recovery: each ad placement uses its own load retry timer and follows the same backoff formula to keep retrying until success, with no cap on the number of attempts; counters, deadlines, timer references, and in-flight markers are all isolated per ad placement, and the same ad placement allows only one pending timer. When in the background or offline, retries pause respectively, and after recovery each due ad placement is recovered at most once; display failures are recorded independently and preloaded again, and do not enter another retry logic.
- User feedback: background loading, retries, and state changes remain silent; video ad display triggers immediately determine unavailability or asynchronously
displayFailedonly displays one localized unavailable Toast, and duplicate/late callbacks do not repeat the prompt;rewarded-interstitial-delayis expected pacing control and does not display an unavailable Toast. - Banner: no preloading and no default creation; the first display request creates and loads it, creation failure and load failure share one persistent backoff retry task; leaving the display scene immediately cancels the task and invalidates the old request, and re-entering requires requesting display again; when ready and the layout is safe, it displays according to the current visible request, failure remains hidden with no Toast; before the page's first frame, space is reserved according to the platform Banner size rules and safe area, and loading, failure, retry, and show/hide callbacks do not change the content inset; actual size is used only to verify whether it can be safely placed in the reserved area, and the layout does not block game interactions; refresh has only one owner, and JS/ThreeJS are prohibited from creating their own refresh timer.
- Compliance and security: MAX's built-in privacy flow and its Google UMP/TCF configuration, MAX privacy flags, iOS ATT, SKAdNetwork, Google requirements, and target region rules all have owners and evidence; SDK key and ad placements come from controlled per-environment build configuration and are used only by the official plugin adapter, account credentials and Ad Review key do not enter the Web layer, and telemetry is desensitized.
- Verification: each fixed network is individually confirmed in test mode or the Mediation Debugger for adapter, load, display, and official plugin failure callbacks; check the MAX waterfall,
app-ads.txtand the pre-release platform checklist. Unit tests must also cover that when official plugin ad events never return, settlement still continues via fire-and-forget, and that adapter Promises only wait for local acceptance/rejection and do not wait for ad events.
This repository this time only delivers the Skill guidance document, and adds no runtime code, scripts, SDK dependencies, or external configuration.