@maccesar/titools 5.2.0 → 5.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CONTRIBUTING.md +69 -0
  2. package/README.md +80 -72
  3. package/agents/ti-pro.md +1 -1
  4. package/commands/ti-audit.md +2 -2
  5. package/docs/examples.md +91 -0
  6. package/docs/knowledge-index.md +57 -0
  7. package/lib/downloader.js +1 -1
  8. package/package.json +4 -2
  9. package/skills/purgetss/references/multi-density-images.md +1 -1
  10. package/skills/ti-admob-unified/SKILL.md +21 -7
  11. package/skills/ti-admob-unified/assets/example/app.js +229 -25
  12. package/skills/ti-admob-unified/assets/example/lib/ads.js +30 -2
  13. package/skills/ti-admob-unified/assets/example/library-usage.js +33 -1
  14. package/skills/ti-admob-unified/references/api-reference.md +39 -16
  15. package/skills/ti-admob-unified/references/banners.md +9 -9
  16. package/skills/ti-admob-unified/references/contributing.md +14 -9
  17. package/skills/ti-admob-unified/references/dependencies.md +31 -25
  18. package/skills/ti-admob-unified/references/fullscreen.md +43 -28
  19. package/skills/ti-admob-unified/references/game-monetization.md +138 -7
  20. package/skills/ti-admob-unified/references/getting-started.md +75 -31
  21. package/skills/ti-admob-unified/references/index.md +5 -2
  22. package/skills/ti-admob-unified/references/integration.md +8 -6
  23. package/skills/ti-admob-unified/references/library.md +41 -10
  24. package/skills/ti-admob-unified/references/mediation-parity.md +17 -0
  25. package/skills/ti-admob-unified/references/mediation.md +22 -9
  26. package/skills/ti-admob-unified/references/migration.md +11 -7
  27. package/skills/ti-admob-unified/references/module-changelog.md +41 -11
  28. package/skills/ti-admob-unified/references/privacy.md +35 -8
  29. package/skills/ti-admob-unified/references/production.md +67 -0
  30. package/skills/ti-admob-unified/references/rewarded-interstitial.md +101 -0
  31. package/skills/ti-admob-unified/references/roadmap.md +28 -31
  32. package/skills/ti-admob-unified/references/source-notes.md +51 -4
  33. package/skills/ti-admob-unified/references/source-snapshot.json +249 -45
  34. package/skills/ti-admob-unified/references/third-party-notices.md +4 -4
  35. package/skills/ti-game/SKILL.md +23 -6
  36. package/skills/ti-game/references/api.md +34 -30
  37. package/skills/ti-game/references/debugging-performance.md +18 -9
  38. package/skills/ti-game/references/examples.md +103 -20
  39. package/skills/ti-game/references/input.md +101 -0
  40. package/skills/ti-game/references/patterns.md +39 -15
  41. package/skills/ti-game/references/physics.md +80 -52
  42. package/skills/ti-game/references/project-setup.md +118 -56
  43. package/skills/ti-game/references/recipes-action.md +107 -8
  44. package/skills/ti-game/references/recipes-adventure-effects.md +41 -9
  45. package/skills/ti-game/references/roadmap.md +10 -3
  46. package/skills/ti-game/references/source-notes.md +81 -0
  47. package/skills/ti-game/references/tilemaps.md +12 -4
  48. package/skills/ti-synthengine/SKILL.md +81 -95
  49. package/skills/ti-synthengine/assets/example/README.md +84 -0
  50. package/skills/ti-synthengine/assets/example/app.js +1178 -0
  51. package/skills/ti-synthengine/assets/example/lib/sequenceOwner.js +83 -0
  52. package/skills/ti-synthengine/assets/example/lib/sequenceScore.js +25 -0
  53. package/skills/ti-synthengine/references/advanced-examples.md +174 -0
  54. package/skills/ti-synthengine/references/android-sim80-correction-evidence.zip +0 -0
  55. package/skills/ti-synthengine/references/android-sim80-correction.json +162 -0
  56. package/skills/ti-synthengine/references/api.md +434 -380
  57. package/skills/ti-synthengine/references/audio-engine.md +157 -230
  58. package/skills/ti-synthengine/references/bypass-physical-recheck-evidence.zip +0 -0
  59. package/skills/ti-synthengine/references/examples.md +123 -253
  60. package/skills/ti-synthengine/references/frontier-android-evidence.zip +0 -0
  61. package/skills/ti-synthengine/references/frontier-ipad-evidence.zip +0 -0
  62. package/skills/ti-synthengine/references/frontier-physical-validation.json +1918 -0
  63. package/skills/ti-synthengine/references/guide.md +188 -156
  64. package/skills/ti-synthengine/references/performance-review-evidence.zip +0 -0
  65. package/skills/ti-synthengine/references/performance-review.json +1146 -0
  66. package/skills/ti-synthengine/references/physical-validation.json +1457 -0
  67. package/skills/ti-synthengine/references/recipes-interaction.md +549 -0
  68. package/skills/ti-synthengine/references/recipes.md +423 -599
  69. package/skills/ti-synthengine/references/recommendations.md +63 -113
  70. package/skills/ti-synthengine/references/sound-design.md +134 -258
  71. package/skills/ti-synthengine/references/source-notes.md +93 -105
  72. package/skills/ti-synthengine/references/source-snapshot.json +379 -32
  73. package/skills/ti-synthengine/references/stats-performance.md +64 -20
  74. package/skills/ti-synthengine/references/studio-validation.json +6202 -0
  75. package/skills/ti-synthengine/references/troubleshooting.md +109 -121
  76. package/skills/ti-synthengine/references/unified-bypass-performance.json +22425 -0
  77. package/skills/ti-synthengine/references/unified-validation.json +314 -0
  78. package/AGENTS-VERCEL-RESEARCH.md +0 -175
@@ -1,14 +1,14 @@
1
- > Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
1
+ > Source snapshot: official ti.admob.unified 1.0.0 documentation at release source commit `034843299b2984253988e0ffccb9f6a1c91682c9` (copied 2026-10-24). Relative links rebased for this skill.
2
2
 
3
3
  # Fullscreen ads
4
4
 
5
- Interstitial, rewarded and app open proxies share one public lifecycle and event contract on iOS and Android.
5
+ Interstitial, rewarded, rewarded interstitial and app open proxies share one public lifecycle and event contract on iOS and Android.
6
6
 
7
7
  ## When to show a fullscreen ad
8
8
 
9
9
  A fullscreen ad interrupts the current screen, so choose its presentation point before requesting it.
10
10
 
11
- Load early, show later: `load()` is a network request and can take seconds. Start it before the intended break, for example when a level opens, an article starts or a form appears. Call `show()` when the application reaches that break. Presenting inside the `loaded` listener lets network timing determine when the user is interrupted.
11
+ Load early, show later: `load()` is a network request and can take seconds. Start it before the intended break, for example when a level opens, an article starts or a form appears. Call `show()` when the application reaches that break. Do not present unconditionally from the `loaded` listener: that lets network timing determine when the user is interrupted. A screen may instead keep an explicit Show request pending during loading, provided it cancels that request when the user leaves or the app loses the foreground.
12
12
 
13
13
  Show fullscreen ads at a natural break, such as between levels or after a task or flow ends. Avoid interrupting work in progress.
14
14
 
@@ -17,10 +17,10 @@ Google's guidance includes these restrictions:
17
17
  - Do not show an interstitial as the app opens. That moment belongs to the App Open format, documented below.
18
18
  - Do not show one on the way out, when the user is already leaving.
19
19
  - Do not interrupt an action in progress, or place the ad where a button or a control the user was about to touch used to be.
20
- - Use `createRewarded()` to reward users for watching ads. Interstitials must not offer rewards.
21
- - Avoid consecutive interstitials and excessive frequency. Configure frequency capping on the ad unit in the AdMob console; JavaScript does not control it.
20
+ - Use `createRewarded()` or the dedicated `createRewardedInterstitial()` format for rewards. Ordinary interstitials must not offer rewards.
21
+ - Avoid consecutive interstitials and excessive frequency. Configure the ad unit's frequency cap in the AdMob console; it is not a JavaScript setting.
22
22
 
23
- The full lists are in Google's [Android interstitial guide](https://developers.google.com/admob/android/interstitial) and [iOS interstitial guide](https://developers.google.com/admob/ios/interstitial).
23
+ See the full restrictions in Google's [Android interstitial guide](https://developers.google.com/admob/android/interstitial) and [iOS interstitial guide](https://developers.google.com/admob/ios/interstitial).
24
24
 
25
25
  ## Privacy gate
26
26
 
@@ -69,7 +69,7 @@ function onLevelCompleted() {
69
69
 
70
70
  If the ad is not ready, let the application continue and try at the next break.
71
71
 
72
- The executable [example app](../assets/example/app.js) loads automatically after initialization. Each format has one button that reports its state and enables Show when ready.
72
+ The executable [example app](../assets/example/app.js) loads automatically after initialization. Each format has one button that reports its state and enables Show when ready. One Show or Retry tap is enough: if an ad needs loading or replacement after a size change, the example waits and presents when it arrives. The rewarded interstitial card explains the reward and AdMob’s introduction requirements, links to the official policy and opens a Watch test ad/Skip introduction after Show. Watch permits presentation; Skip cancels it. Watch also remains valid through a required size replacement, so loading does not require another Show tap. Closing an ad preloads another without presenting it. Leaving the foreground, closing the window, changing privacy eligibility or requesting a different fullscreen cancels the pending presentation.
73
73
 
74
74
  ## Rewarded
75
75
 
@@ -92,7 +92,13 @@ rewarded.addEventListener(Admob.AD_FAILED_TO_SHOW, event => {
92
92
  rewarded.load()
93
93
  ```
94
94
 
95
- Closing a rewarded ad is not proof that the reward was earned.
95
+ Present it only after the user accepts a clear reward offer, using `isLoaded()` before `show()`. Closing a rewarded ad is not proof that the reward was earned. Keep the reward handler alive until the SDK delivers the result; mediated callback ordering may differ. Version `1.0.0` exposes client-side rewards only; it has no server-side verification configuration.
96
+
97
+ The application must coordinate fullscreen proxies so only one presentation is active at a time. Keep it separate from UMP, ATT and Ad Inspector.
98
+
99
+ ## Rewarded interstitial
100
+
101
+ `createRewardedInterstitial()` shares Rewarded's configuration, methods and reward event. Before presentation, explain the reward and offer a way to skip. The proxy does not show that introduction itself. See [Rewarded interstitial ads](rewarded-interstitial.md) for the complete flow.
96
102
 
97
103
  ## App open
98
104
 
@@ -112,16 +118,20 @@ appOpen.load()
112
118
  ```
113
119
 
114
120
  ```js
115
- function onApplicationResumed() {
116
- if (appOpen && appOpen.isLoaded()) {
121
+ function onForegroundLoadingScreen() {
122
+ if (appOpen.isLoaded()) {
117
123
  appOpen.show()
124
+ } else {
125
+ appOpen.load()
118
126
  }
119
127
  }
120
128
  ```
121
129
 
130
+ Call this only while your loading screen is visible and no consent form, other fullscreen ad or external flow is active. Do not wire an unconditional show to every activity resume: closing an ad or returning from its click can also resume the host. If loading finishes before the ad is ready, continue into the app and save the ad for a later opportunity. Remove any application lifecycle listener when its owner is disposed.
131
+
122
132
  ### The four hour window
123
133
 
124
- Google documents that an app open ad expires four hours after it was requested. An ad rendered later is no longer valid and may not earn revenue, so the proxy enforces the window itself, with no timer and no extra event:
134
+ Google documents a four-hour validity window and its sample checks the time since the ad loaded. Both module proxies record successful load completion and check that age, with no timer and no extra event. See Google's [Android](https://developers.google.com/admob/android/app-open#consider_ad_expiration) and [iOS](https://developers.google.com/admob/ios/app-open) guides:
125
135
 
126
136
  - The load time is recorded when the ad arrives.
127
137
  - `isLoaded()` returns `false` once four hours have passed.
@@ -132,7 +142,7 @@ Google documents that an app open ad expires four hours after it was requested.
132
142
 
133
143
  ## Creation properties
134
144
 
135
- All three factories accept:
145
+ All four factories accept:
136
146
 
137
147
  | Property | Type | Default | Notes |
138
148
  | -------------- | -------- | ------- | ------------------------------------------------------------------ |
@@ -146,7 +156,7 @@ Do not use `extras` to bypass UMP or synthesize personalization state.
146
156
 
147
157
  Both platforms preserve primitive values in `extras`. Use the types required by the receiving adapter; for example, pass `{ npa: '1' }` when it expects a string.
148
158
 
149
- Pass initial configuration to the factory. Later assignments to the five configuration properties affect the next request on both platforms. An already loaded ad keeps the configuration of its original request.
159
+ Pass initial configuration to the factory. Later assignments to `adUnitId`, `keywords`, `requestAgent` and `extras` affect the next request on both platforms; an already loaded ad keeps its original request configuration. `autoReload` is a live close policy: both native ports read its current value when the shown ad closes, so changing it while an ad is loaded or presented changes whether that close starts another load. It does not present another ad or retry a load/show failure.
150
160
 
151
161
  ## Shared methods
152
162
 
@@ -157,7 +167,7 @@ Pass initial configuration to the factory. Later assignments to the five configu
157
167
  | `isLoaded()` | Returns `true` only while an unused ad is ready. For app open, an ad older than four hours is not ready. |
158
168
  | `destroy()` | Terminal, idempotent cleanup; emits `destroyed` once. |
159
169
 
160
- Google fullscreen ads are single-use. After a presentation or a show failure the proxy holds no ad: its native reference is cleared and `isLoaded()` turns `false`. Call `load()` again before the next `show()`.
170
+ Google fullscreen ads are single-use. Once presentation starts, `isLoaded()` is `false`. Native dismissal or presentation-failure callbacks discard the consumed reference. A local precondition failure, such as a missing host before presentation, can retain the unused ad; recheck readiness after correcting the condition. Call `load()` again before the next `show()`.
161
171
 
162
172
  Late native callbacks are ignored after `destroy()`. A dismissed or failed-to-show ad reference is cleared before the terminal event is emitted.
163
173
 
@@ -183,21 +193,27 @@ Reuse the proxy across loads. Create a new one only after `destroy()`.
183
193
 
184
194
  ### Common events
185
195
 
186
- | Event | Interstitial | Rewarded | App open | Common payload |
187
- | --------------------------- | -----------: | -------: | -------: | -------------------------------------------------- |
188
- | `loaded` | Yes | Yes | Yes | `adUnitId`, `adType` |
189
- | `failed_to_load` | Yes | Yes | Yes | `adUnitId`, `adType`, `error`, `message`, `code` |
190
- | `opened` | Yes | Yes | Yes | `adUnitId`, `adType` |
191
- | `showed_fullscreen_content` | Yes | Yes | Yes | `adUnitId`, `adType` |
192
- | `closed` | Yes | Yes | Yes | `adUnitId`, `adType` |
193
- | `clicked` | Yes | Yes | Yes | `adUnitId`, `adType` |
194
- | `impression` | Yes | Yes | Yes | `adUnitId`, `adType` |
195
- | `failed_to_show` | Yes | Yes | Yes | `adUnitId`, `adType`, `error`, `message`, `code` |
196
- | `destroyed` | Yes | Yes | Yes | `adUnitId`, `adType` |
197
- | `rewarded` | No | Yes | No | `adUnitId`, `adType`, `rewardType`, `rewardAmount` |
196
+ | Event | Interstitial | Rewarded | App open | Rewarded interstitial | Common payload |
197
+ | --------------------------- | -----------: | -------: | -------: | -------: | -------------------------------------------------- |
198
+ | `loaded` | Yes | Yes | Yes | Yes | `adUnitId`, `adType` |
199
+ | `failed_to_load` | Yes | Yes | Yes | Yes | `adUnitId`, `adType`, `error`, `message`, `code` |
200
+ | `opened` | Yes | Yes | Yes | Yes | `adUnitId`, `adType` |
201
+ | `showed_fullscreen_content` | Yes | Yes | Yes | Yes | `adUnitId`, `adType` |
202
+ | `closed` | Yes | Yes | Yes | Yes | `adUnitId`, `adType` |
203
+ | `clicked` | Yes | Yes | Yes | Yes | `adUnitId`, `adType` |
204
+ | `impression` | Yes | Yes | Yes | Yes | `adUnitId`, `adType` |
205
+ | `failed_to_show` | Yes | Yes | Yes | Yes | `adUnitId`, `adType`, `error`, `message`, `code` |
206
+ | `destroyed` | Yes | Yes | Yes | Yes | `adUnitId`, `adType` |
207
+ | `rewarded` | No | Yes | No | Yes | `adUnitId`, `adType`, `rewardType`, `rewardAmount` |
198
208
 
199
209
  `message` duplicates `error` for compatibility. New code may use `error`; both keys are portable.
200
210
 
211
+ ## iPad collapsed-SVG workaround
212
+
213
+ With the pinned GMA iOS 13.11.0 SDK, an Interstitial image creative can contain an SVG whose automatic width collapses in WebKit on iPad. The wrapper checks the presented Google WebView during a bounded first-second window. It repairs only `svg.advertiser-provided-image` with zero width, positive height, a valid SVG viewBox, a Google `simgad` background image and a column-flex parent. The parent receives the viewBox aspect ratio; visible images, intentionally hidden content and other layouts remain unchanged. Pending attempts stop on dismissal, presentation failure or destruction.
214
+
215
+ The check applies to Interstitials on iPad with both demo and publisher-owned unit IDs. It uses internal creative markup and is not an official Google SDK setting. Other templates, nested content or later layout failures may remain outside its scope. Rewarded and Rewarded Interstitial are not modified. Device tests established the original repair with Google's demo creative; extending eligibility to publisher IDs does not certify every production creative. Validate your units with [test devices enabled](https://developers.google.com/admob/ios/test-ads).
216
+
201
217
  ## Cleanup
202
218
 
203
219
  Use named callbacks and remove them before disposing the proxy:
@@ -217,7 +233,6 @@ Create a new proxy after disposal; a destroyed proxy cannot load again.
217
233
 
218
234
  The module exports format-name and test-ID constants for future compatibility, but it does not expose working factories for:
219
235
 
220
- - Rewarded interstitial
221
236
  - Native
222
237
 
223
- `TEST_REWARDED_INTERSTITIAL` and `TEST_NATIVE` name units no factory accepts.
238
+ `TEST_NATIVE` names a unit no factory accepts.
@@ -1,17 +1,148 @@
1
1
  # Game monetization and module integration
2
2
 
3
- Use the official `fullscreen.md`, `privacy.md` and `library.md` contract for calls. This reference adds product/lifecycle criteria, not a second module API. The detailed asset-to-game coordination guide is `ti-game-assets/references/responsible-ads.md` when that skill is available; this skill works independently of it.
3
+ Use [fullscreen.md](fullscreen.md), [privacy.md](privacy.md) and
4
+ [library.md](library.md) for calls. This reference adds game-specific product
5
+ and lifecycle criteria, not a second module API. Reviewed against
6
+ `ti.admob.unified@034843299b2984253988e0ffccb9f6a1c91682c9` (1.0.0).
7
+ Read [production.md](production.md) before shipping an application. When
8
+ available, `ti-game-assets/references/responsible-ads.md` adds asset/game
9
+ coordination; this skill remains usable independently of it.
10
+
11
+ ## Current formats and their boundaries
12
+
13
+ | Placement | Integration boundary |
14
+ | --- | --- |
15
+ | Interstitial | Preload; show at a natural break without offering a reward. Avoid app entry, exit, active gameplay and repeated/consecutive interruptions. |
16
+ | Rewarded | Offer a clear voluntary exchange; present after acceptance; grant only from `rewarded` / `onRewarded`. |
17
+ | Rewarded interstitial | Implemented by `createRewardedInterstitial()` and its library manager. At a natural transition, explain the configured reward and show a clear, unobstructed Skip option before presentation, with enough time to understand and decline. The proxy/manager does not build this introduction. See [rewarded-interstitial.md](rewarded-interstitial.md). |
18
+ | App open | Implemented, but belongs only to an application's own foreground loading experience. Skip when content is already usable; do not show unconditionally from every resume. An expired ad needs a new load. |
19
+ | Banner/adaptive banner | Reserve a safe, measured slot away from controls; preserve that slot across ordinary screen pauses. See [banners.md](banners.md). |
20
+ | Native | No factory in 1.0.0. Format/test-ID constants alone do not implement it. |
21
+
22
+ Adding another implemented format still requires an intentional product decision.
23
+ Do not add forced rewarded continues or replace every retry with an ad. For a
24
+ rewarded-interstitial introduction, use the reward configured for that unit;
25
+ there is no pre-presentation reward getter. A visible Watch/Skip AlertDialog
26
+ works on both platforms; an iPad OptionDialog's hidden cancel item is not an
27
+ equivalent visible Skip action. Cancel a pending introduction when its screen
28
+ closes, the foreground or eligibility is lost, or another fullscreen is chosen.
29
+
30
+ Only the SDK's `rewarded` event grants value; `closed`, `impression`, clicks
31
+ and a successful Show attempt do not. The rewarded-interstitial native proxies
32
+ deduplicate reward callbacks per presentation and preserve the presented unit's
33
+ SDK reward while another request may load. Keep reward handling alive for the
34
+ SDK result: mediated reward and dismissal callback ordering can differ.
35
+ `destroy()` is terminal and suppresses late callbacks; it is not a dismissal API.
36
+ Client-side rewards do not provide server-side verification in this release.
37
+
38
+ ## Privacy and integration ownership
39
+
40
+ Run the consent-first flow on every app launch: register listeners, request
41
+ UMP, wait for `consent_complete`, evaluate `canRequestAds()`, initialize, then
42
+ load. A failed UMP update may retain permission from a previous choice; an
43
+ error alone is neither approval nor denial. Continue the game without ads
44
+ when unavailable. Raw fullscreen proxies do not enforce this gate.
45
+
46
+ ATT is separate. If the app uses it, resolve the foreground prompt after UMP
47
+ and before initialization; keep the required description/translations even if
48
+ an AdMob UMP IDFA message presents ATT. Query
49
+ `getTrackingAuthorizationStatus()` to inspect state without prompting, including
50
+ after Settings. Android's `applicable: false` / `authorized: true` convention
51
+ is not general tracking permission. ATT denial does not override UMP's request
52
+ gate. If explicitly requesting ATT, wait for `tracking_authorization_completed`
53
+ even when permission was resolved in an earlier session. See [privacy.md](privacy.md).
54
+
55
+ The copy-ready library owns sequencing, deferred loads, dimensions and placement
56
+ cleanup. It does **not** choose game breaks, impose frequency limits, pause
57
+ native gameplay/audio/input, or serialize unrelated application modal flows.
58
+ Its `start()` promise is cached even after an unavailable result; repeating it
59
+ is not a startup retry. Use the documented raw consent/init recovery flow if
60
+ recoverable startup is part of the application. Never reset production consent
61
+ to force another attempt.
62
+
63
+ Provide an interactive privacy action when required. Serialize it with ads,
64
+ ATT and Inspector; suspend new ad opportunities until its result is evaluated.
65
+ The library reauthorizes/replaces requests after dismissal. Raw consumers must
66
+ replace requests affected by changed choices themselves. Do not destroy an
67
+ onscreen fullscreen ad to dismiss it or treat `destroyAll()` as cancelling
68
+ UMP/Inspector: it releases placements only. See [library.md](library.md).
4
69
 
5
70
  ## Community-Discovered Patterns
6
71
 
7
- These are author guidance and integration lessons from Lander/Guardian, not additional module API or a verified optimal monetization policy. Preserve this section during official-doc refreshes and re-evaluate its lifecycle claims against current builds.
72
+ These are preserved author guidance and integration lessons from Lander/Guardian,
73
+ not additional module API or a verified optimal monetization policy. Their
74
+ frequency/director separation remains useful alongside the official library;
75
+ their application configuration and numeric thresholds are not defaults for
76
+ other games.
77
+
78
+ Choose deliberate natural breaks, such as settled end-of-run results, rather
79
+ than interrupting flight/combat or attaching an ad to every retry. Preserve
80
+ pause/tutorial/practice and voluntary-exit choices. Load early, but let the
81
+ application choose Show. A time threshold makes a later break eligible; it
82
+ never authorizes a timer-driven interruption. Google's
83
+ [interstitial placement guidance](https://support.google.com/admob/answer/6201362?hl=en)
84
+ disallows unexpected and excessive placements; re-check it before a live change.
85
+
86
+ Before presentation, capture the intended scene state, suspend native motion
87
+ and relevant timers, silence app-owned audio as appropriate, and release held
88
+ input. A stopped JS tick does not stop native gravity. Restore only the scene,
89
+ clock scale and audio that should still be active after dismissal or confirmed
90
+ failure, provided the owner is alive and the app is foregrounded. Do not blindly
91
+ resume a previously paused game, undo a newer navigation choice, or restore held
92
+ throttle/fire inputs. Returning from an ad is a lifecycle event, not a new ad
93
+ opportunity.
94
+
95
+ One application director owns presentation and rejects simultaneous attempts,
96
+ including UMP, ATT, Inspector and other fullscreen formats. Keep a once-only
97
+ continuation and an ownership/generation token so callbacks from an abandoned
98
+ screen cannot advance its successor. A missing-opening watchdog may recover an
99
+ attempt that never opens; after a confirmed `opened`, clear that watchdog and
100
+ wait for actual dismissal or presentation failure. An opening/impression does
101
+ not prove the creative was visibly rendered. Associate completion with the
102
+ active attempt: a rejected duplicate Show is not proof that an already open
103
+ ad has ended.
104
+
105
+ Distinguish the APIs at the boundary: raw `load()` / `show()` return no success
106
+ value. Library-manager `show()` returns `false` when not ready or when the
107
+ size guard requests a replacement; continue without blocking. A `true` result
108
+ means presentation was attempted and may still fail through `onFailedToShow`.
109
+ Supply the owning window. The manager does not queue a future Show after
110
+ returning `false`; a late load or background/resume must not launch a missed
111
+ interstitial. An explicit user-requested pending flow, such as the published
112
+ rewarded-interstitial demo, needs its own cancellation and introduction state.
8
113
 
9
- Choose deliberate natural breaks, such as settled end-of-run results, rather than interrupting flight/combat or attaching an ad to every retry. Preserve pause/tutorial/practice and voluntary-exit choices. Load early, but only the application decides to show; reaching a time threshold makes a later boundary eligible, never a timer-based interruption. Current [Google guidance](https://support.google.com/admob/answer/6201362?hl=en) disallows unexpected and excessive interstitial placements; re-check it before a live change.
114
+ Keep grace period, active-play accumulation, cooldown after dismissal,
115
+ hourly/daily caps and permitted boundaries distinct. Count actual openings
116
+ once and close separately; do not count both `opened` and
117
+ `showed_fullscreen_content` as two exposures. Preserve persistence and
118
+ conservative handling of clock rollback. Caps are ceilings, not exposure
119
+ targets, and application eligibility is separate from AdMob's ad-unit cap.
120
+ Use backoff after load/show failures; `autoReload` requests a replacement after
121
+ `closed` only and never presents it automatically.
10
122
 
11
- The ad boundary must suspend/release native motion and sound as appropriate. A stopped JS tick does not stop native gravity. Resume only the intended scene after an actual dismissal or confirmed show failure. One director owns presentation, protects against simultaneous attempts, and discards continuations after the screen/session closes. Timeouts may handle missing opening; they must not resume gameplay behind a confirmed open ad. A background/resume or late load callback does not start a missed ad.
123
+ A banner host must stay away from touch controls and reserve the native loaded
124
+ height inside safe areas. Choose `collapseOnFailure: false` on a library banner
125
+ when retrying should preserve its slot. Use screen-level `pause()` / `resume()`
126
+ only when navigation hides it while the same host lifecycle remains active;
127
+ background/activity lifecycle is already native. Pausing preserves the creative,
128
+ visibility and geometry; no JS refresh timer or destroy/recreate loop is needed.
129
+ On Android, ignore `_closeFromActivityForcedToDestroy` for placement teardown:
130
+ that close can be activity recreation rather than a destroyed screen. Remove
131
+ named listeners and destroy placements on actual owner disposal. Preserve reward
132
+ listeners until their result; destruction must not resume an abandoned game.
12
133
 
13
- Keep grace period, active-play accumulation, cooldown after dismissal, hourly/daily caps and permitted boundaries distinct. Count actual openings once and track close separately. Preserve persistence and conservative handling of clock rollback. Lander and Guardian implement this separation, but their numeric thresholds are initial settings without evidence of an optimum; do not transplant them into every game. Caps are ceilings, not exposure targets. Application eligibility is separate from the network's AdMob frequency cap.
134
+ ## Acceptance in the consuming game
14
135
 
15
- Use test IDs and the official consent-first flow. Banners need layout away from controls; rewarded ads need an explicit voluntary offer, a real reward and one grant from the documented rewarded event. Interstitials are not rewarded ads. Do not add forced rewarded continues, App Open or a new format without product scope. The module's implemented formats and roadmap determine what is possible.
136
+ Use test units and test devices. Exercise unavailable module, no fill,
137
+ load/show failures, repeated taps, late/duplicate callbacks, reward once,
138
+ Skip, real dismissal, background/resume, privacy changes, rotation between
139
+ load/introduction/Show, owner destruction and phone/tablet layouts on both
140
+ platforms. Check native clock, input and audio restoration on each path.
16
141
 
17
- Test unavailable module, no fill, load/show failures, repeated taps, late/duplicate callbacks, real close, background/resume, destruction and phone/tablet layout on both platforms. An SDK opening/impression does not prove the creative was visible: physical visual acceptance is separate. Measure confirmed exposure per active-play minute, abandonment at ad boundaries and retention/retries where telemetry is authorized; unit tests do not prove players enjoy the frequency.
142
+ A source check, wrapper test, SDK opening or impression does not prove visible
143
+ creative rendering, provider delivery or acceptable game frequency. Record
144
+ physical visual acceptance separately. Where telemetry is authorized, measure
145
+ confirmed exposure per active-play minute, abandonment at boundaries and
146
+ retention/retries. Unit tests do not prove players enjoy the frequency.
147
+ Production units, app verification, app-ads.txt, provider setup and store/privacy
148
+ disclosures belong to [production.md](production.md), not game frequency logic.
@@ -1,9 +1,27 @@
1
- > Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
1
+ > Source snapshot: official ti.admob.unified 1.0.0 documentation at release source commit `034843299b2984253988e0ffccb9f6a1c91682c9` (copied 2026-10-24). Relative links rebased for this skill.
2
2
 
3
3
  # Getting started
4
4
 
5
+ <!-- TOC-START -->
6
+ ## Contents
7
+
8
+ - [1. Install the modules](#1-install-the-modules)
9
+ - [2. Configure the AdMob application IDs](#2-configure-the-admob-application-ids)
10
+ - [3. Configure mediation if needed](#3-configure-mediation-if-needed)
11
+ - [4. Configure Privacy & messaging](#4-configure-privacy--messaging)
12
+ - [5. Use official test units](#5-use-official-test-units)
13
+ - [6. Request consent, then initialize](#6-request-consent-then-initialize)
14
+ - [7. Create an ad only after the gate opens](#7-create-an-ad-only-after-the-gate-opens)
15
+ - [Common setup mistakes](#common-setup-mistakes)
16
+ - [Before shipping](#before-shipping)
17
+ - [Example appearance](#example-appearance)
18
+
19
+ <!-- TOC-END -->
20
+
5
21
  ## 1. Install the modules
6
22
 
23
+ Download the core archives from [GitHub Releases](https://github.com/macCesar/ti.admob.unified/releases). Use `ti.admob.unified-iphone-1.0.0.zip` for iOS and `ti.admob.unified-android-1.0.0.zip` for Android. Extract each ZIP at your Titanium application's root, keeping the archive's `modules/` directory. The resulting manifests must be at `modules/iphone/ti.admob.unified/1.0.0/manifest` and `modules/android/ti.admob.unified/1.0.0/manifest`. Do not add another nested `modules/` directory.
24
+
7
25
  Install the `1.0.0` ZIP of `ti.admob.unified` for every platform you build. All artifacts of a given module must have the same module ID and version:
8
26
 
9
27
  ```xml
@@ -12,7 +30,7 @@ Install the `1.0.0` ZIP of `ti.admob.unified` for every platform you build. All
12
30
  </modules>
13
31
  ```
14
32
 
15
- An application that serves AdMob demand needs only the core: about 14 MB on iOS and 325 KB on Android, with no third-party network SDKs in the build.
33
+ An application that serves AdMob demand needs only the core. Android resolves its Maven dependencies during the consumer build, so the module ZIP size is not the installed app-size cost. CocoaPods and the framework installer scripts are for building this repository, not for applications installing its release ZIPs.
16
34
 
17
35
  If the application mediates, install the selected per-network complements at the same version. For AppLovin:
18
36
 
@@ -25,7 +43,7 @@ If the application mediates, install the selected per-network complements at the
25
43
 
26
44
  Each complement carries one adapter and its dependencies, with no JavaScript API. Add a module line for each intended network; leave unneeded networks out. See [AdMob mediation](mediation.md).
27
45
 
28
- The current baselines are Titanium `13.2.0.GA`, iOS `15.0+`, and Android API `24+`.
46
+ The minimum versions are Titanium `13.4.1.GA`, iOS `15.0+`, and Android API `24+`.
29
47
 
30
48
  For an existing AdMob integration, see [Migrating to ti.admob.unified](migration.md).
31
49
 
@@ -43,7 +61,7 @@ An **ad unit ID** contains a slash:
43
61
  ca-app-pub-0000000000000000/0000000000
44
62
  ```
45
63
 
46
- They are not interchangeable. The application ID belongs in `tiapp.xml`; ad unit IDs are supplied to ad factories such as `createAdaptiveBanner()`.
64
+ Put the application ID in `tiapp.xml` and pass ad unit IDs to factories such as `createAdaptiveBanner()`. Each field requires its own ID type.
47
65
 
48
66
  ### iOS
49
67
 
@@ -60,7 +78,7 @@ Add the iOS application ID to the app plist section:
60
78
  </ios>
61
79
  ```
62
80
 
63
- App Tracking Transparency is optional and separate from UMP. Most banner applications do not need it: serving ads does not require tracking users across other companies' apps and websites. Add the usage description only when the application uses tracking, and describe that use accurately. Apple reviews the prompt against the app's behavior.
81
+ App Tracking Transparency is optional and separate from UMP. Serving ads does not require tracking users across other companies' apps and websites. Add the usage description only when the application uses tracking, and describe that use accurately. Apple reviews the prompt against the app's behavior.
64
82
 
65
83
  ```xml
66
84
  <ios>
@@ -73,7 +91,24 @@ App Tracking Transparency is optional and separate from UMP. Most banner applica
73
91
  </ios>
74
92
  ```
75
93
 
76
- Omit that key entirely if the application never calls `requestTrackingAuthorization()`. UMP still runs and still shows the consent form where the region requires one; the two mechanisms are unrelated.
94
+ Include this key whenever the app or an included SDK requests ATT, including an IDFA message configured in AdMob UMP. Omit it only when the app and its SDK configuration never request ATT and do not perform tracking. UMP regional consent can run without ATT.
95
+
96
+ The usage description is the explanation shown in Apple's ATT prompt. Localize it with `i18n/<language>/app.xml` in the consumer app, using the `NSUserTrackingUsageDescription` string name. Provide an English value and a translation for each supported language, including Spanish when the app supports it. This applies to Classic and Alloy projects; verify that the built app contains `<language>.lproj/InfoPlist.strings` with those values.
97
+
98
+ ```xml
99
+ <resources>
100
+ <string name="NSUserTrackingUsageDescription">This identifier helps us measure advertising performance.</string>
101
+ </resources>
102
+ ```
103
+
104
+ These settings serve separate purposes:
105
+
106
+ - `NSUserTrackingUsageDescription` and its translations explain the ATT request. They do not request permission by themselves.
107
+ - `GADApplicationIdentifier` identifies the iOS app to AdMob. It is required even when ATT is disabled.
108
+ - `SKAdNetworkItems` configures attribution identifiers. It does not grant ATT permission.
109
+ - App Store Connect privacy disclosures describe the app's actual collection and tracking, including its SDKs. They must match the integration; a plist entry does not complete those disclosures. See Apple's [privacy and data use guidance](https://developer.apple.com/app-store/user-privacy-and-data-use/).
110
+
111
+ A successful App Store review of another integration is a reference, not a promise that Apple will approve this app. Review depends on its behavior, configuration and disclosures.
77
112
 
78
113
  Keep any required `SKAdNetworkItems` in the consuming app. Follow Google's [iOS setup guide](https://developers.google.com/admob/ios/quick-start) for the current list and App Store requirements.
79
114
 
@@ -129,7 +164,7 @@ const interstitialId = Admob.TEST_INTERSTITIAL
129
164
  const rewardedId = Admob.TEST_REWARDED
130
165
  ```
131
166
 
132
- The module exports the full set of test constants for compatibility. Two formats have no implemented factory:
167
+ The module exports the full set of test constants for compatibility. Native remains reserved without a factory:
133
168
 
134
169
  | Constant | Factory that accepts it |
135
170
  | ---------------------------- | ------------------------------------------------- |
@@ -138,7 +173,7 @@ The module exports the full set of test constants for compatibility. Two formats
138
173
  | `TEST_INTERSTITIAL` | `createInterstitial()` |
139
174
  | `TEST_INTERSTITIAL_VIDEO` | `createInterstitial()` (same API, video creative) |
140
175
  | `TEST_REWARDED` | `createRewarded()` |
141
- | `TEST_REWARDED_INTERSTITIAL` | None. Reserved; the format is not implemented |
176
+ | `TEST_REWARDED_INTERSTITIAL` | `createRewardedInterstitial()` |
142
177
  | `TEST_NATIVE` | None. Reserved; the format is not implemented |
143
178
  | `TEST_APP_OPEN` | `createAppOpen()` |
144
179
 
@@ -172,7 +207,9 @@ function initializeOnce() {
172
207
  Admob.addEventListener('module_initialized', event => {
173
208
  if (event.success) {
174
209
  Ti.API.info('Google Mobile Ads initialized')
210
+ // Create/load placements here, after initialization succeeds.
175
211
  } else {
212
+ initialized = false // allow a later app-controlled retry
176
213
  Ti.API.warn(`Google Mobile Ads initialization failed: ${event.error}`)
177
214
  }
178
215
  })
@@ -192,7 +229,7 @@ Admob.requestConsent({
192
229
 
193
230
  Skip this section unless step 2 added `NSUserTrackingUsageDescription`. ATT is not a prerequisite for serving ads, and requesting it without a real tracking case invites an App Store rejection.
194
231
 
195
- An application that does request it waits for the Apple prompt to settle before initializing, so that the SDK reads a final tracking status:
232
+ Replace the `consent_complete` listener from step 6 with the listener below; do not register both. An application that requests ATT waits for the Apple prompt to settle before initializing, so that the SDK reads a final tracking status. Present it while the app is active and no other system prompt or consent form is onscreen:
196
233
 
197
234
  ```js
198
235
  Admob.addEventListener('consent_complete', event => {
@@ -217,38 +254,41 @@ Admob.addEventListener('consent_complete', event => {
217
254
  })
218
255
  ```
219
256
 
220
- Google Mobile Ads reads `trackingAuthorizationStatus` on its own on iOS. That read is not a permission request and does not open Apple's prompt: only `requestTrackingAuthorization()` does.
257
+ Google Mobile Ads reads `trackingAuthorizationStatus` on its own on iOS; that read does not open Apple's prompt. An IDFA message configured in AdMob can let UMP present ATT during `requestConsent()`. In that integration, keep the usage description and translations, let UMP finish, and use `getTrackingAuthorizationStatus()` to inspect the result. A separate explicit ATT request is unnecessary when UMP already handles it. See Google's [UMP IDFA message guide](https://developers.google.com/admob/ios/privacy/idfa).
221
258
 
222
259
  `initialize()` and `requestConsent()` are idempotent native operations. Application-level guards are still useful because cached terminal events may be emitted again when a caller invokes an already-completed operation.
223
260
 
224
261
  ## 7. Create an ad only after the gate opens
225
262
 
226
- A banner needs a host view that is already in the hierarchy, so the native view can measure its real width.
263
+ For a complete first banner, use the integration library below **instead of the raw startup listeners in step 6**. It runs the same consent/initialization sequence and waits for a real layout. Keep `const Admob = require('ti.admob.unified')` from step 5. Choose one startup owner for the app.
227
264
 
228
- Reserve the host height before the ad arrives. Growing it from zero when `loaded` fires moves surrounding content after the screen appears. Give the host its expected height and a background colour that matches the screen, so the empty slot looks like a bottom margin:
265
+ Reserve the host height before the ad arrives. Growing it from zero when `loaded` fires moves surrounding content after the screen appears. Give the host its expected height and a background color that matches the screen. The empty slot then looks like a bottom margin:
229
266
 
230
267
  ```js
231
- const bannerHost = Ti.UI.createView({
232
- left: 0,
233
- right: 0,
234
- bottom: 0,
235
- height: '50dp'
268
+ // Classic Titanium; copy example/lib/ads.js into Resources/lib/ first.
269
+ // Alloy: copy to app/lib/ads.js and use require('ads').
270
+ const ads = require('lib/ads')
271
+ const win = Ti.UI.createWindow({ backgroundColor: '#ffffff' })
272
+ const content = Ti.UI.createView({ top: 0, left: 0, right: 0, bottom: 0 })
273
+ const bannerHost = Ti.UI.createView({ left: 0, right: 0, bottom: 0, height: '50dp' })
274
+ win.add(content)
275
+ win.add(bannerHost)
276
+
277
+ const banner = ads.createAdaptiveBanner({
278
+ host: bannerHost,
279
+ content,
280
+ reservedHeight: 50,
281
+ adUnitId: Admob.TEST_ADAPTIVE_BANNER,
282
+ adaptiveType: Admob.ADAPTIVE_ANCHORED
236
283
  })
237
284
 
238
- if (Admob.canRequestAds()) {
239
- const banner = Admob.createAdaptiveBanner({
240
- adUnitId: Admob.TEST_ADAPTIVE_BANNER,
241
- adaptiveType: Admob.ADAPTIVE_ANCHORED,
242
- width: Ti.UI.FILL
243
- })
244
-
245
- banner.addEventListener(Admob.AD_LOADED, event => {
246
- bannerHost.height = `${event.height}dp`
247
- })
248
-
249
- bannerHost.add(banner)
250
- banner.load()
251
- }
285
+ win.addEventListener('close', event => {
286
+ if (!event._closeFromActivityForcedToDestroy) banner.destroy()
287
+ })
288
+ win.open()
289
+ ads.start({ consent: { tagForUnderAgeOfConsent: false } }).catch(error => {
290
+ Ti.API.warn(`Ads startup failed: ${error.message || error}`)
291
+ })
252
292
  ```
253
293
 
254
294
  A fixed `BANNER` is `50dp` and `FULLBANNER` is `60dp`, so a fixed-size placement can reserve its exact height. An adaptive banner reports its final height after loading. Reserve a suitable minimum, then adjust it in the loaded event to avoid growing the host from zero.
@@ -270,6 +310,10 @@ The module does not enforce the privacy gate inside ad proxies. The consuming ap
270
310
  - Installing mediation complements without configured ad sources adds SDK size and may delay startup while unused networks time out.
271
311
  - Publishing an app that is supposed to mediate without the selected complements installed, which must be checked through package inventories and Ad Inspector; an empty diagnostic map alone is not an installation check.
272
312
 
313
+ ## Before shipping
314
+
315
+ Follow [Shipping an application](production.md) for production configuration, app-ads.txt, store disclosures, validation and troubleshooting.
316
+
273
317
  ## Example appearance
274
318
 
275
319
  For the classic playground, copy `example/app.js` and `example/semantic.colors.json` into `Resources/`, and `example/lib/ads.js` into `Resources/lib/`. The named colors provide light and dark variants for the content and window title. Keep the default DayNight Android theme so system icons follow the selected appearance. The sample keeps the title and status bars visible. Changing appearance does not require rebuilding the ad proxies or resetting the scroll position.
@@ -1,4 +1,4 @@
1
- > Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
1
+ > Source snapshot: official ti.admob.unified 1.0.0 documentation at release source commit `034843299b2984253988e0ffccb9f6a1c91682c9` (copied 2026-10-24). Relative links rebased for this skill.
2
2
 
3
3
  # ti.admob.unified documentation
4
4
 
@@ -12,7 +12,10 @@ This documentation describes the public JavaScript contract implemented by `ti.a
12
12
  4. [Privacy and initialization](privacy.md): implement UMP, ATT, the `canRequestAds()` gate, and privacy options correctly.
13
13
  5. [AdMob mediation](mediation.md): install selected complements, and configure and validate the five networks without platform-specific JavaScript.
14
14
  6. [Banner ads](banners.md): integrate standard or adaptive banners with real layout space and deterministic cleanup.
15
- 7. [Fullscreen ads](fullscreen.md): interstitial, rewarded, and app open ads on one lifecycle, and when it is acceptable to interrupt the user.
15
+ 7. [Fullscreen ads](fullscreen.md): interstitial, rewarded, rewarded interstitial, and app open ads on one lifecycle, and when it is acceptable to interrupt the user.
16
+
17
+ 8. [Rewarded interstitial ads](rewarded-interstitial.md): introduce the reward, allow skipping, and handle actual earned rewards.
18
+ 9. [Shipping an application](production.md): production configuration, app verification, store disclosures, troubleshooting and validation.
16
19
 
17
20
  ## Reference
18
21
 
@@ -1,12 +1,12 @@
1
- > Source snapshot: official ti.admob.unified 1.0.0 documentation at commit `5b4cbf9` (copied 2026-10-11). Relative links rebased for this skill.
1
+ > Source snapshot: official ti.admob.unified 1.0.0 documentation at release source commit `034843299b2984253988e0ffccb9f6a1c91682c9` (copied 2026-10-24). Relative links rebased for this skill.
2
2
 
3
3
  # Integrating the module
4
4
 
5
- The native module manages Google Mobile Ads, UMP, portable proxy behavior and host lifecycle. The consuming application controls navigation and layout, including which screen is visible.
5
+ The native module manages Google Mobile Ads, UMP, proxy behavior and host lifecycle on both platforms. Your application controls navigation, layout and which screen is visible.
6
6
 
7
7
  ## Recommended path
8
8
 
9
- Copy [`example/lib/ads.js`](../assets/example/lib/ads.js) into the consuming application's `lib/` directory. It covers every format implemented by version `1.0.0` and supplies the startup, cleanup and placement code shared by applications.
9
+ Copy [`example/lib/ads.js`](../assets/example/lib/ads.js) to `app/lib/ads.js` in Alloy or `Resources/lib/ads.js` in Classic Titanium. It supports every format in version `1.0.0` and handles startup, placement lifecycle and cleanup.
10
10
 
11
11
  See [Copy-ready JavaScript library](library.md) for the API and examples, and [`example/library-usage.js`](../assets/example/library-usage.js) for a compact runnable integration.
12
12
 
@@ -33,10 +33,11 @@ An Android secondary window backed by another activity is already covered by the
33
33
 
34
34
  ```js
35
35
  banner.pause()
36
+ showSameActivityOverlay()
36
37
 
37
- const child = Alloy.createController('child').getView()
38
- child.addEventListener('close', () => banner.resume())
39
- child.open()
38
+ function onOverlayDismissed() {
39
+ banner.resume()
40
+ }
40
41
  ```
41
42
 
42
43
  Pausing does not change `visible`, opacity, height, the host, or the loaded creative. Do not destroy/recreate on tab changes, focus/blur, or light/dark appearance changes. Rebuild only for a real configuration or authorization boundary, or after a recoverable failure.
@@ -83,3 +84,4 @@ When using the raw API, keep named event callbacks so every listener can be remo
83
84
  - [Banner ads](banners.md)
84
85
  - [Fullscreen ads](fullscreen.md)
85
86
  - [Privacy and initialization](privacy.md)
87
+ - [Shipping an application](production.md)