Should we revert the no-ads sale-pack removal?

v32 (live 2026-09-21) removed skip.ad.permanent.sale ($10.99), skip.ad.30days.sale ($4.99) and skip.ad.annual.sale ($4.99). Data through 2026-10-10 — 19 post-removal days, 2,287 v32+ installs, up from 255 installs / 3 payers on 2026-09-25. The offer boundary is empirically v31 | v32: the last .sale purchase anywhere in the export is 2026-09-21 (Play's own order record), and every build from v32 on shows zero .sale taps, purchases or grant payouts.

In plain language

What's happening. In v32 (21 September) we removed the three discounted “no-ads” packs. The players who used to buy the $10.99 one did not move up to the $29.99 pack — we needed 37% of them to trade up for the change to pay for itself, and about 0% did. They moved sideways instead, to the surviving $10.99 30-day pack, which gives less for the same money and gets refunded at 32% of orders against 9% for the pack we removed. Net effect: roughly $241–$267 a week of discounted no-ads revenue disappeared, and nothing we can measure replaced it. The one number that made the removal look harmless — ad revenue went up — turns out to be a property of the new build, not of the shop change: v34 earns $78.46 per 1,000 ad impressions against $36.68 for v29 in the same weeks.

What should happen. Put the $10.99 permanent no-ads pack back as the new-player offer — the price point was working, and permanent is the better product than the 30-day pack that absorbed the volume. Separately, fix the bug that makes Google auto-refund purchases the client never acknowledged at exactly 72.17 hours after the charge; that bug is real, it is not specific to these packs, it is still live on the packs that remain, and restoring the offer neither causes nor cures it. Then re-measure.

This reverses the direction of the two earlier reads of this page. 2026-09-25 said no-ads revenue per install was down 70–75% but called the net “not yet significant”; the 2026-10-10 draft said keep it removed. Both leaned on an ad-revenue increase as the offsetting term. Section 4 tests that term directly and withdraws it — it is a property of the v32+ ad stack, not of the removal. With it withdrawn, every window's point estimate is negative, the destroyed revenue line is the only precisely measured term, and the trade-up thesis that justified the change fails by a factor of ten.

Verdict: roll it back — restore ONE $10.99 permanent no-ads pack

This recommendation rests on three things that do not depend on the design-sensitive net-per-install estimate, followed by the two disclosures that make the page defensible (section 2). Restore skip.ad.permanent.sale — permanent ad removal plus the same 20,000 gems as the $29.99 twin, at $10.99, as the standing new-player offer. Do not reinstate all three SKUs: skip.ad.annual.sale has never been tapped or bought once in the entire export, and skip.ad.30days.sale had already stopped selling of its own accord on 09-15. skip.ad.30days is a candidate to prune or fix on delivery grounds (it refunds at 31.6%).

  1. The trade-up definitively did not happen. Break-even needed ≥36.6% of .sale buyers to re-convert at $29.99; observed -3.6% (CI -18.9% … +12.4%) — the interval excludes break-even in every window. Play's order record agrees: the $29.99 twin went 14 → 8 orders, while the $10.99 30-day pack went 10 → 19. Buyers took 30 days instead of permanent at the same price. The demand was price-driven.
  2. The destroyed revenue has no demonstrated replacement. .sale revenue fell −$0.15 per install over D0–D2 (90% CI −$0.18 … −$0.11) — a 66% collapse of the no-ads IAP line — worth $241–$267/week ex-China depending on whether devices with no first_open are counted ($586–$648 over 17 days; August ran $280–632/wk). The offsetting terms are either not significant (other IAP $0.06, CI −$0.04 … $0.18) or withdrawn below as a build artifact (ads).
  3. What replaced it is a worse product at the same price. The same $10.99 now buys 30 days of no ads instead of permanent, and skip.ad.30days refunds at 31.6% against 9.4% for the pack that was removed (Fisher p = 0.026); order volume only partially transferred (19 post-removal orders in 20 days against 64 before).

Supporting, not decisive — the net-per-install estimate. With the ad term withdrawn the primary estimate is −$0.04/install over D0–D2 (CI −$0.20 … $0.12) and −$0.13 over D0–D7 (CI −$0.33 … $0.07) — −$112 to −$35/week at today's 843 installs/week, −$225 to −$70/week at the 1,691/week the game ran before the removal. Every one of those intervals spans zero, the design confounds the removal with calendar time, and a version-defined split of the same data says the opposite (section 2). Do not lead with this number; it is corroborating, not load-bearing.

Separate action item, not a condition of this verdict: the acknowledgement bug is real, unfixed, and hits the surviving full-price SKUs — 10 Google-initiated refunds, every one 72.167–72.173 h after the charge (skip.ad.permanent ×3, skip.ad.30days ×3, skip.ad.30days.sale ×1, skip.ad.permanent.sale ×1, auto.blessing ×2), which is Play auto-refunding a purchase the client never acknowledged. It is not .sale-specific, restoring the offer neither causes nor cures it, and it must be fixed regardless of this decision (section 7). The strongest argument against rolling back is in section 8.

0 · The numbers this verdict rests on

NumberValue90% CI / detailsignificantsample
Trade-up achieved vs the 36.6% break-even-3.6%-18.9% … +12.4%failed50 .sale buyers lost; $29.99 orders 14 → 8
Where the volume went insteadskip.ad.30dayssame $10.99, 30 days not permanent; orders 10 → 19worserefunds 6/19 = 31.6% (Fisher p=0.026)
.sale revenue destroyed−$0.15/install-0.1457 (−$0.18 … −$0.11)yes$241–$267/week; August $280–632/wk
.sale revenue destroyed, share of the no-ads line66%$0.22 → $0.12 per install—17d pre-window
Ad credit withdrawn: same week, v34 vs v29$77.93v29 $37.97 per 1k imps; fill 81.0% vs 65.1%buildrevenue per impression ≠ shop card
Net revenue per new install, ads withdrawn, D0–D2 (design-sensitive)−$0.04-0.1974 … +0.1177n.s.1,988 vs 4,107 installs
Net revenue per new install, ads withdrawn, D0–D7 (design-sensitive)−$0.13-0.3291 … +0.0739n.s.1,409 vs 4,107 installs
Naive version-defined split (a trap, not evidence)$0.06v32+ $0.35 IAP/install — would argue the oppositeinvalid47.1% of the v31-or-older arm is China mirror traffic
Play order refund rate, before → after3.7% → 5.4%17/461 → 13/239worsenon-ad-removal flat 2.3% → 2.9%

1 · What shipped, and what is left of the offer

Claim. The .sale packs stopped selling with v32 (live 2026-09-21) and have not been bought since. v32–v36 show zero .sale taps, purchases, reward grants or purchase successes across 2,311 installs, while v28 and v29 were still selling them right up to launch day (last .sale order 2026-09-21 22:52 UTC in Play's accounting record). skip.ad.annual.sale has never been tapped or bought once in the entire export despite being ACTIVE on Play — the one genuinely free prune.

buildfirst daylast dayusersinstalls.sale taps.sale buys.sale grants.sale $full-price tapsfull-price buysfull-price $last .sale buy
26.22026-09-142026-10-101,39558312126$147.51211$12.752026-09-16
282026-09-162026-10-10356180631$35.7193$100.582026-09-17
292026-09-172026-10-102,2231,497651,646$62.82193$96.672026-09-21
322026-09-212026-10-09493243000$0.00181$13.77–
332026-09-232026-10-10589345000$0.00473$54.44–
342026-09-252026-10-102,6441,707000$0.0011118$329.68–

earn is the decisive column: position holds the SKU for shop grants, so it is the client paying out the reward and cannot fire from a shop card that no longer exists. The 1,646 .sale grants still visible on v29 are the known ~230× re-emission of 30days.sale's 20,000-gem grant by devices that already own it (mostly the China mirror APK) — not sales. Do not read them as evidence the offer survives.

The .sale offer was a new-player window, not a standing shop item — re-confirmed on the extended data across 252 .sale purchases:

days since installpurchasesbuyerscumulative %
016015963.5
1686590.5
2181897.6
33398.8
51199.2
622100.0

90.5% of purchases land within one day of install and 97.6% within two — so D0–D2 is the window the offer actually occupied (97.6% of its purchases fit inside it), and that is the window every comparison on this page uses. Install cohorts at matched days-since-install are still the cleanest comparison available, but not a clean one — see the two design problems at the end of section 2.

wkskip.ad.10daysskip.ad.30daysskip.ad.30days.saleskip.ad.3daysskip.ad.permanentskip.ad.permanent.sale
2026-08-03$26.57$10.99$73.42$24.01$190.84$359.37
2026-08-10$32.43$23.04$89.72$29.95$94.62$542.72
2026-08-17$34.57$37.91$98.57$43.74$133.16$282.24
2026-08-24$35.22$23.72$39.26$17.22$29.99$370.57
2026-08-31$21.59$37.55$42.88$27.50$72.98$237.14
2026-09-07$22.81$55.77$24.14$6.82$88.91$279.99
2026-09-14$0.00$12.75$0.00$0.00$197.25$221.54
2026-09-21$0.00$68.06$0.00$0.00$32.46$24.51
2026-09-28$0.00$10.99$0.00$0.00$66.17$0.00
2026-10-05$0.00$97.97$0.00$0.00$122.24$0.00

Ad-removal revenue by ISO week from GA4, ex-China, USD gross. Note what happened in the same weeks: skip.ad.permanent and skip.ad.30days keep selling, permanent.sale ends the week v32 ships, and skip.ad.30days roughly doubles ($12.75/wk pre → $68–98/wk post in the surviving weeks).

Build-by-build ship check (also counts the full-price twins):
-- Did the removal ship, and on which build? Every no-ads signal by app version,
-- 2026-09-14 .. 2026-10-12 (v32 went live 2026-09-21).
-- `earn` is the decisive column: `position` holds the SKU for shop grants, so it is the
-- client paying out the reward and cannot fire from a shop card that no longer exists.
WITH e AS (
  SELECT app_info.version v, event_name, user_pseudo_id u, PARSE_DATE('%Y%m%d', event_date) d,
    event_value_in_usd usd,
    COALESCE((SELECT value.string_value FROM UNNEST(event_params) WHERE key='product_id'),
             (SELECT value.string_value FROM UNNEST(event_params) WHERE key='position')) sku
  FROM `tiny-knightfall-idle-rpg-game.analytics_538412813.events_intraday_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260914' AND '20261012'
)
SELECT v AS version, MIN(d) first_day, MAX(d) last_day,
  COUNT(DISTINCT u) users,
  COUNTIF(event_name='first_open') installs,
  COUNTIF(sku LIKE '%skip.ad%' AND sku LIKE '%.sale' AND event_name='purchase_click')  sale_taps,
  COUNTIF(sku LIKE '%skip.ad%' AND sku LIKE '%.sale' AND event_name='in_app_purchase') sale_buys,
  COUNTIF(sku LIKE '%skip.ad%' AND sku LIKE '%.sale' AND event_name='earn')            sale_grants,
  COUNTIF(sku LIKE '%skip.ad%' AND sku LIKE '%.sale' AND event_name='iap_purchase_success') sale_success,
  ROUND(SUM(IF(sku LIKE '%skip.ad%' AND sku LIKE '%.sale' AND event_name='in_app_purchase', usd, 0)), 2) sale_usd,
  COUNTIF(sku LIKE '%skip.ad%' AND sku NOT LIKE '%.sale' AND event_name='purchase_click')   full_taps,
  COUNTIF(sku LIKE '%skip.ad%' AND sku NOT LIKE '%.sale' AND event_name='in_app_purchase')  full_buys,
  ROUND(SUM(IF(sku LIKE '%skip.ad%' AND sku NOT LIKE '%.sale' AND event_name='in_app_purchase', usd, 0)), 2) full_usd,
  COUNTIF(sku LIKE '%skip.ad.annual.sale%' AND event_name IN ('purchase_click','in_app_purchase','earn')) annual_sale_signals,
  MAX(IF(sku LIKE '%skip.ad%' AND sku LIKE '%.sale' AND event_name='in_app_purchase', d, NULL)) last_sale_buy
FROM e
GROUP BY v
HAVING users >= 5
ORDER BY first_day
Weekly ad-removal revenue by SKU:
-- Ad-removal SKU revenue by ISO week (Mon-start) and SKU, 2026-08-01 .. 2026-10-12.
-- This is the revenue side of the removal: which SKUs carried it, when they stopped,
-- and what (if anything) replaced them. China is kept as its own row so it can be
-- excluded rather than silently dropped.
WITH e AS (
  SELECT PARSE_DATE('%Y%m%d', event_date) d, event_name, event_value_in_usd usd,
    geo.country country,
    COALESCE((SELECT value.string_value FROM UNNEST(event_params) WHERE key='product_id'),
             (SELECT value.string_value FROM UNNEST(event_params) WHERE key='position')) sku
  FROM `tiny-knightfall-idle-rpg-game.analytics_538412813.events_intraday_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260801' AND '20261012'
)
SELECT DATE_TRUNC(d, WEEK(MONDAY)) wk,
  REPLACE(sku, 'com.idle.tiny.', '') sku,
  IF(country = 'China', 'China', 'rest') geo_group,
  COUNTIF(event_name = 'purchase_click') taps,
  COUNTIF(event_name = 'in_app_purchase') buys,
  ROUND(SUM(IF(event_name = 'in_app_purchase', usd, 0)), 2) usd,
  COUNTIF(event_name = 'earn') grants
FROM e
WHERE sku LIKE '%skip.ad%'
GROUP BY wk, sku, geo_group
ORDER BY wk, sku, geo_group

2 · Design — install cohorts at matched days since install, and the two things wrong with that

Claim. Because the offer only ever sold to players in their first few days, the fair comparison is install cohorts scored over the same days-since-install window, with China excluded and only completed windows counted. Active-user cuts are unusable for the effect — early auto-updaters are the engaged core, i.e. the payers — and a version-defined cut is unusable too, for the reason in the disclosures at the end of this section. Section 4 uses active users deliberately, for a different question (are the arms' ad economics a property of the build?), where who the users are cannot change revenue per impression.

ArmInstall datesInstalls (ex-China)Note
Offer present2026-09-04 … 2026-09-204,107every build live then (v21–v29) sold the packs
Offer absent2026-09-21 … 2026-10-071,988installs whose `first_open` build is v32+
Offer absent, D0–D72026-09-21 … 2026-10-021,409smaller: only installs whose day 7 had happened by 10-09
Offer absent, gap-free D0–D72026-09-21 … 10-021,307excludes anyone whose window contains the 09-28 outage
Offer absent, all builds2026-09-21 … 2026-10-072,318includes 255 old-build (v29) installs that still show the offer

Windows are D0, D0–D2 and D0–D7; each arm is restricted to installs whose whole window finished by 2026-10-09 (10-10 is an intraday, partial shard), so the D0–D7 arm is smaller than the D0–D2 arm by construction — which is why the two must never be compared to each other, only to their own offer-present baseline. Two traps are handled explicitly:

The per-user-day extract (5.0 GiB scan, cached):
-- Per user-day facts, 2026-08-01 .. 2026-10-12 (one row per device per active day).
-- Source: GA4 raw export, events_intraday_* shards only. Extended from
-- notebooks/sql/15_user_day_facts.sql with the no-ads SKU classes split out, so the
-- substitution question (did .sale buyers move to the full-price twin?) can be answered
-- on the same per-install-day grid without a second scan.
WITH ev AS (
  SELECT user_pseudo_id u, PARSE_DATE('%Y%m%d', event_date) d, event_name e,
    event_timestamp ts, geo.country country, app_info.version v, event_value_in_usd usd,
    -- `earn` carries the SKU in `position`, purchases in `product_id`; take both, in this order
    COALESCE((SELECT value.string_value FROM UNNEST(event_params) WHERE key='product_id'),
             (SELECT value.string_value FROM UNNEST(event_params) WHERE key='position')) sku,
    -- Impression-level ad revenue reported by AppLovin MAX.
    (SELECT COALESCE(value.double_value, value.float_value, CAST(value.int_value AS FLOAT64))
       FROM UNNEST(event_params) WHERE key='value') val,
    (SELECT value.string_value FROM UNNEST(user_properties) WHERE key='appsflyer_id') af
  FROM `tiny-knightfall-idle-rpg-game.analytics_538412813.events_intraday_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260801' AND '20261012'
)
SELECT u, d,
  ANY_VALUE(country) country,
  ARRAY_AGG(v IGNORE NULLS ORDER BY ts DESC LIMIT 1)[SAFE_OFFSET(0)] v,
  MAX(af) af,
  COUNTIF(e='first_open') first_opens,
  -- all IAP
  SUM(IF(e='in_app_purchase', usd, 0)) iap_usd,
  COUNTIF(e='in_app_purchase') iap_buys,
  -- ad-removal SKUs, all of them
  SUM(IF(e='in_app_purchase' AND sku LIKE '%skip.ad%', usd, 0)) noads_usd,
  COUNTIF(e='in_app_purchase' AND sku LIKE '%skip.ad%') noads_buys,
  -- the discounted .sale variants removed in v32
  SUM(IF(e='in_app_purchase' AND sku LIKE '%skip.ad%' AND sku LIKE '%.sale', usd, 0)) sale_usd,
  COUNTIF(e='in_app_purchase' AND sku LIKE '%skip.ad%' AND sku LIKE '%.sale') sale_buys,
  -- the full-price twin of the pack that mattered: skip.ad.permanent at $29.99
  SUM(IF(e='in_app_purchase' AND sku LIKE '%skip.ad.permanent%' AND sku NOT LIKE '%.sale', usd, 0)) perm_full_usd,
  COUNTIF(e='in_app_purchase' AND sku LIKE '%skip.ad.permanent%' AND sku NOT LIKE '%.sale') perm_full_buys,
  -- the single pack that carried the volume: skip.ad.permanent.sale at $10.99
  SUM(IF(e='in_app_purchase' AND sku LIKE '%skip.ad.permanent.sale%', usd, 0)) perm_sale_usd,
  COUNTIF(e='in_app_purchase' AND sku LIKE '%skip.ad.permanent.sale%') perm_sale_buys,
  -- card taps: purchase_click, the player opening the offer
  COUNTIF(e='purchase_click' AND sku LIKE '%skip.ad%') noads_taps,
  COUNTIF(e='purchase_click' AND sku LIKE '%skip.ad.permanent%' AND sku NOT LIKE '%.sale') perm_full_taps,
  COUNTIF(e='purchase_click' AND sku LIKE '%skip.ad%' AND sku LIKE '%.sale') sale_taps,
  COUNTIF(e='purchase_click') shop_taps,
  -- ad revenue (impression-level) and impressions
  SUM(IF(e='ad_impression_MAX', COALESCE(val, 0), 0)) ad_usd,
  COUNTIF(e='ad_impression_MAX') ad_imps
FROM ev
GROUP BY u, d
The install-date / install-build lookup:
-- One row per device: install date, install country, install build.
-- Trimmed to `first_open` only (notebooks/sql/15_user_history.sql also pulled purchase
-- dates, which the user-day facts now carry at day resolution anyway).
SELECT user_pseudo_id u,
  MIN(IF(event_name='first_open', PARSE_DATE('%Y%m%d', event_date), NULL)) install_date,
  ARRAY_AGG(IF(event_name='first_open', geo.country, NULL)
            IGNORE NULLS ORDER BY event_timestamp LIMIT 1)[SAFE_OFFSET(0)] install_country,
  ARRAY_AGG(IF(event_name='first_open', app_info.version, NULL)
            IGNORE NULLS ORDER BY event_timestamp LIMIT 1)[SAFE_OFFSET(0)] install_ver
FROM `tiny-knightfall-idle-rpg-game.analytics_538412813.events_intraday_*`
WHERE event_name = 'first_open'
GROUP BY u

Two design problems, stated up front — read these before any number below

1. This is not a natural experiment. The offer-present arm is installs 2026-09-04…09-20 and the offer-absent arm is 2026-09-21…10-07, so any calendar trend — UA campaigns ending, traffic quality, the 09-28 data gap, a store algorithm change — loads onto the treatment. Install cohorts at matched days-since-install remove the update-speed selection that ruins a version cut, but they cannot remove time. Every net-per-install interval on this page spans zero, and the arms' install rate differs by 2×.

2. A version-defined split cannot measure this effect, and its face value is misleading. It looks like the clean cut — “installs on a build that still has the offer” vs “installs on a build that does not” — but once v32 shipped, Play would not serve an older build to a new device, so a post-removal install on v31-or-older is almost never a real Play install. In this data 47.1% of that arm's installs are the known China mirror/bot APK (294 of 624), against 1.1% for v32+. The naive version split therefore reads IAP $0.06 per install for the arm whose client still has the offer versus $0.35 for the arm that lost it — which would argue for keeping the removal — while measuring a third-party APK spike, not the offer. It is disclosed here and used nowhere else in this document.

post-removal installs by buildinstallsChina installsIAP $/installno-ads $/installad $/installtotal $/installChina %
v31 or older (offer still in client)624294$0.06$0.02$0.04$0.1047.1
v32+ (offer removed)2,01022$0.35$0.11$0.13$0.481.1

D0–D2 per install, all post-removal installs including China. The older-build row is what a version cut would report as the “offer present” counterfactual; it is an artifact of 47.1% mirror traffic, and it is why this cut is not used as evidence anywhere on this page.

What that leaves

The per-install comparison below is the best available read, but it is corroboration, not the load-bearing argument. The argument is the failure of the trade-up plus the un-replaced revenue plus the worse replacement product.

3 · Per-install outcomes

Claim. The removed .sale line was worth $0.15 per install over D0–D2 (−$0.18 … −$0.11, significant) — a 66% collapse of the no-ads IAP line, and the only precisely measured term in this whole comparison. Everything that appears to offset it is either not significant (other IAP $0.06, CI −$0.04 … $0.18) or is withdrawn in section 4 (ads). On IAP alone the removal costs −$0.04 per install over D0–D2 (CI −$0.20 … $0.12) and −$0.13 over D0–D7 (CI −$0.33 … $0.07). The intervals span zero: this is a preponderance call, not a proven effect.

Per new install — D0–D2offer presentoffer absent (v32+)changeratio
New installs (ex-China)4,1071,988
IAP payer rate2.0%1.9%-0.14 pp
No-ads card taps / 1,000 installs25.673.4+47.92.9×
Full-price no-ads taps / 1,0009.728.7+18.9
No-ads buyers / 1,000 installs15.37.5-7.8
.sale buyers / 1,000 installs12.20.0−12.2
Full-price $29.99 buyers / 1,0001.91.5-0.4
No-ads IAP $ / install$0.22$0.12-0.1056
.sale $ / install (removed line)$0.15$0.00−0.1457
full-price twin $ / install$0.06$0.04-0.0181
Other IAP $ / install$0.17$0.23+0.0644
Ad revenue $ / install$0.06$0.13+0.0690
TOTAL $ / install$0.46$0.48+0.0277
TOTAL $ / install, excl. top payer$0.43$0.45+0.0212
Per new installoffer presentoffer absent (v32+)change90% CIsignificant
No-ads IAP $/install$0.22$0.12−$0.11−$0.18 … −$0.03yes
Ad revenue $/install (withdrawn in §4)$0.06$0.13$0.07$0.04 … $0.10yes
Other IAP $/install$0.17$0.23$0.06−$0.04 … $0.18no
All IAP $/install — primary estimate$0.39$0.35−$0.04−$0.20 … $0.12no
TOTAL $/install (credits the build's ad gain)$0.46$0.48$0.03−$0.14 … $0.20no
All IAP $/install, excl. top payer$0.36$0.32−$0.05−$0.19 … $0.09no
No-ads IAP $/install, excl. top payer$0.21$0.11−$0.10−$0.18 … −$0.03yes
D0 onlyoffer presentoffer absent (v32+)change90% CIsignificant
No-ads IAP $/install$0.15$0.12−$0.03−$0.10 … $0.04no
Ad revenue $/install$0.06$0.12$0.06$0.04 … $0.09yes
Other IAP $/install$0.09$0.16$0.08$0.01 … $0.15yes
All IAP $/install — primary estimate$0.24$0.28$0.04−$0.07 … $0.17no
TOTAL $/install$0.30$0.40$0.11−$0.02 … $0.24no
D0–D7offer presentoffer absent (v32+)change90% CIsignificant
No-ads IAP $/install$0.23$0.08−$0.15−$0.22 … −$0.08yes
Ad revenue $/install (withdrawn in §4)$0.07$0.11$0.05$0.02 … $0.08yes
Other IAP $/install$0.24$0.25$0.02−$0.13 … $0.17no
All IAP $/install — primary estimate$0.46$0.33−$0.13−$0.33 … $0.07no
TOTAL $/install (credits the build's ad gain)$0.53$0.44−$0.09−$0.29 … $0.13no
All IAP $/install, excl. top payer$0.43$0.27−$0.16−$0.33 … $0.01no
Robustness variantinstalls present / absent no-ads $/install changeTOTAL $/install change90% CI (total) significant
D0–D7, gap-free cohorts only3,660 / 1,307 −$0.14−$0.10 −$0.32 … $0.14 no
D0–D2, all builds (old-build installs kept)4,107 / 2,318 −$0.12−$0.01 −$0.17 … $0.15 no

The offer-present arm sells across builds, which matters for the diagnosis: per-1,000 installs, .sale buyers were 16.9 on v24, 8.5 on v25, 13.3 on v26.2 and 8.0 on v29 — the offer was universal, not a quirk of one build.

4 · The decisive test — is the ad-revenue gain the removal, or the build?

Claim: the build. The offer-absent arm earns more ad revenue per install, and the earlier reads of this page treated that as the removal's doing — players who cannot buy ad removal watch more ads. Holding the calendar week fixed and varying only the build kills that reading: over 2026-09-21…10-07 the v32+ builds earn $78.46 per 1,000 impressions at 79.1% rewarded fill against $36.68 at 62.7% for the older builds that dominate the offer-present arm. Removing a shop card cannot change revenue per impression. The ad term is therefore withdrawn, and the primary net estimate becomes IAP-only (section 3).

build groupuser-daysimpressionsad $rewarded requestsfilledad $ / DAUimps / DAUad $ / 1k impsrewarded fill %
builds before v211090$0.0036711$0.000.0–3.0
v21–29 (offer present)6391,000$36.682,4361,528$0.061.6$36.6862.7
v32+ (offer removed)3,2614,843$380.0010,1318,013$0.121.5$78.4679.1

Three checks, in increasing order of strictness:

  1. Same week, by build. The two build groups are measured over exactly the same calendar days, so no time trend, UA campaign change or seasonal effect can produce the gap. v34 alone — 70% of the post arm — earns $77.93 per 1,000 impressions at 81.0% fill.
  2. Same week, the two builds side by side. In 09-21…09-27 alone, v34 earns $61.40 per 1,000 impressions at 86.3% fill while v29 — the build whose client still sells the pack — earns $34.23 at 65.8%.
  3. The one build present on both sides of the removal. v29's client never lost the offer, so the removal is a non-event for it. Across the removal its ad revenue per DAU is flat ($0.08 → $0.07) and its impressions per DAU are flat (1.7 → 1.8). Whatever moved ad revenue between the arms, it is not something the removal did to a build.

Is the gap bigger than ordinary build-to-build variation? No. In the pre-removal fortnight, with the offer on sale for everyone and no removal in existence, the same metric already ranged across builds — v26.2 $49.55, v25 $83.58, v24 $36.37, v29 $46.88, v28 $87.66 in the same two weeks. The arms' difference is inside the spread that builds produce on their own.

builduser-daysimpressionsad $rewarded requestsfilledrewardsad $ / DAUimps / DAUad $ / 1k impsrewarded fill %rewards / DAU
26.21,6322,345$116.205,5263,5513,089$0.071.4$49.5564.31.89
25696481$40.201,6971,104530$0.060.7$83.5865.10.76
24656556$20.222,0229781,171$0.030.8$36.3748.41.79
29565949$44.493,2721,423685$0.081.7$46.8843.51.21
28195231$20.25950401237$0.101.2$87.6642.21.22

Pre-removal window 2026-09-07…09-20, offer present for every build.

builduser-daysimpressionsad $rewarded requestsfilledrewardsad $ / DAUimps / DAUad $ / 1k impsrewarded fill %rewards / DAU
342,5264,140$322.638,1696,6184,217$0.131.6$77.9381.01.67
29491881$33.451,9791,2891,061$0.071.8$37.9765.12.16
33380308$35.13943685321$0.090.8$114.0672.60.84
32352394$22.241,015706538$0.061.1$56.4569.61.53

Post-removal window 2026-09-21…10-07.

China excluded, as everywhere else on this page. Including the China mirror/bot APK's active users makes the older builds look worse, not better — v29's rewarded fill falls from 65.1% to 46.2% and its impressions held roughly constant (881 → 903), while v34 barely moves (81.0% either way). The per-impression gap is 2.1× on either basis ($77.93 vs $37.97 excluding China, $77.93 vs $37.11 including it).

What this does and does not settle

countryv21–29 — ad $ / 1k impsv32+ — ad $ / 1k impsv21–29 — impsv32+ — imps
Brazil10.7210.58180378
Germany59.85106.0566172
Indonesia6.3626.80151440
Italy17.3714.2919238
Qatar–4.55–198
South Korea18.59183.1085174
Spain56.4721.5917415
United States40.12244.4085332
The same-week-by-build query:
-- Same-calendar-week ad economics by build — the decisive attribution test.
--
-- The question: the offer-absent arm earns more ad revenue per install than the
-- offer-present arm. Is that the removal (players who cannot buy ad-removal watch more
-- ads) or a property of the build (v32+ has much better rewarded fill than v29)?
--
-- Holding the calendar week fixed and grouping by build answers it, because a shop-card
-- change cannot alter revenue *per impression*. The window deliberately starts before the
-- removal so the same build gradient can be measured while the offer was still on sale.
WITH ev AS (
  SELECT app_info.version v, user_pseudo_id u, PARSE_DATE('%Y%m%d', event_date) d,
    event_name e, geo.country country,
    (SELECT COALESCE(value.double_value, value.float_value, CAST(value.int_value AS FLOAT64))
       FROM UNNEST(event_params) WHERE key='value') val,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key='ad_type') ad_type,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key='status') status
  FROM `tiny-knightfall-idle-rpg-game.analytics_538412813.events_intraday_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260901' AND '20261012'
    AND event_name IN ('ad_impression_MAX', 'ad_request_status', 'ad_reward')
)
SELECT DATE_TRUNC(d, WEEK(MONDAY)) wk, v AS version, country,
  COUNT(DISTINCT CONCAT(u, '|', CAST(d AS STRING))) user_days,
  COUNTIF(e = 'ad_impression_MAX') imps,
  ROUND(SUM(IF(e = 'ad_impression_MAX', COALESCE(val, 0), 0)), 2) ad_usd,
  COUNTIF(e = 'ad_request_status' AND ad_type = 'rewarded') rew_req,
  COUNTIF(e = 'ad_request_status' AND ad_type = 'rewarded' AND status = 'success') rew_ok,
  COUNTIF(e = 'ad_reward') rewards
FROM ev
GROUP BY wk, version, country
ORDER BY wk, version, country

5 · The crux — did .sale buyers trade up to the $29.99 twin?

Claim: no, and not close. Break-even on removing a $10.99 pack in favour of its identical-reward $29.99 twin needs ≥36.6% of the .sale buyers to re-convert at 2.7× the price. The measured trade-up is -3.6% (90% CI -18.9% … +12.4%), and the interval excludes 36.6% in every window. In Play's accounting record the $29.99 twin's orders fell across the removal (14 → 8). The volume moved to the surviving 30-day pack at the same $10.99.

Break-even arithmetic, per 1,000 installs (D0–D2)value90% CI
.sale buyers lost (S)12.2 50 buyers over 4,107 installs
full-price buyers before (P)1.9 8 buyers
full-price buyers after (F)1.5 3 buyers
incremental trade-up (F−P)/S-3.6% -18.9% … +12.4%
needed for revenue neutrality≥36.6% = $10.99 / $29.99
armSKUPlay ordersrefundedrefund %
absent30days19631.6
absentpermanent8112.5
absentpermanent.sale200.0
presentpermanent.sale6469.4
presentpermanent14214.3
present30days.sale1317.7
present3days1100.0
present30days1000.0
present10days800.0

Read the Play table as the authoritative cross-check (one row per order; GA4's in_app_purchase is Play's auto-logged telemetry and misses billing-only app instances). Two things stand out. Ad-removal orders fell 76% (120 → 29 over 20 days each) while the rest of the catalogue fell 38% (341 → 210, and total orders 461 → 239) — the removal cost the product real demand, and it did not come back at $29.99. And the one SKU that did pick up is a strictly worse product for the same money: 30 days of no ads instead of permanent.

6 · Net per install, per week, against the revenue that vanished

Claim. The .sale line was worth $241/week (ex-China, offer-present window). With the ad term withdrawn (section 4) the net effect is −$112 to −$35/week at today's 843 installs/week, or −$225 to −$70/week at the 1,691 installs/week the game was running before the removal. The intervals span zero — but note the asymmetry: the $241/week that disappeared is measured tightly, while the terms that appear to replace it are either not significant or (ads) a property of the build.

Money, per weekvaluenote
.sale revenue that vanished $241 ex-China, 09-04…09-20 run-rate ($586 over 17 days)
net, IAP only — primary estimate, D0–D2−$35 −$0.04 $/install × 843 installs/wk
net, IAP only — primary estimate, D0–D7−$112 −$0.13 $/install × 843 installs/wk
the same two, at the pre-removal install rate −$225 … −$70 × 1,691/843 — what it would have cost then
for reference, crediting the build's ad gain $23 to −$73 the earlier reading; not used (section 4)

Where the apparent replacement came from (D0–D2, $/install): the surviving no-ads SKUs $0.05 ($0.01 … $0.08, significant — it is skip.ad.30days picking up, i.e. a worse product at the same price), other IAP $0.06 (−$0.04 … $0.18, not significant) and ad revenue $0.07 ($0.04 … $0.10, significant but withdrawn — see section 4).

The ad term, in one paragraph. Impressions per 1,000 installs rose 1,043.3 → 1,368.7 (+31%) and revenue per 1,000 impressions $60.32 → $96.39 (+60%). The second of those is the larger half and is mechanically impossible to attribute to a shop-card change; section 4 shows it is present in the same calendar week against every older build. The arms' build mixes differ (the offer-present arm is v24/v25/v26.2, the offer-absent arm is 70% v34), so the whole line is withdrawn from the primary estimate rather than partly credited.

arminstallsimpr /1k installsinstalls with any impr %ad $ /1k installsad $ per 1k impr
offer present4,1071,043.317.4$62.94$60.32
offer absent (v32+)1,9881,368.721.0$131.93$96.39

Per-install outcomes in removal-aligned buckets (D0–D7)

installs fromtoarmnno-ads $.sale $full-price $other IAP $ad $total $no-ads taps/1kfull-price buyers/1k.sale buyers/1kgap
09-0109-07present1,782$0.26$0.19$0.04$0.34$0.06$0.6718.51.115.7yes
09-0809-14present1,991$0.18$0.12$0.03$0.20$0.06$0.4523.11.011.0yes
09-1509-20present1,029$0.29$0.13$0.16$0.28$0.08$0.6539.84.910.7yes
09-2109-27removed868$0.08$0.00$0.00$0.29$0.11$0.4879.50.00.0yes
09-2810-04removed541$0.08$0.00$0.06$0.19$0.11$0.3879.51.80.0yes

Revenue run-rate by week, ex-China

week (Mon)arminstallsall IAP $no-ads $.sale $full-price twin $other IAP $ad $
2026-08-03present2,537$2,092.79$666.72$414.32$190.84$1,426.07$163.48
2026-08-10present2,556$2,038.36$774.63$594.58$94.62$1,263.73$193.14
2026-08-17present2,698$1,577.32$612.42$363.03$133.16$964.91$207.32
2026-08-24present2,827$1,526.80$476.92$381.82$29.99$1,049.88$168.08
2026-08-31present1,768$1,326.75$423.65$264.03$72.98$903.10$95.24
2026-09-07present2,013$997.76$421.74$277.43$58.92$576.02$117.45
2026-09-14mixed1,297$1,032.61$407.55$197.55$197.25$625.06$119.69
2026-09-21removed1,153$742.55$125.03$24.51$32.46$617.53$129.36
2026-09-28removed857$394.41$40.98$0.00$29.99$353.43$149.76
2026-10-05removed619$645.98$220.21$0.00$122.24$425.77$128.84

Revenue is gross: event_value_in_usd on in_app_purchase is VAT-inclusive outside the US (75.4% of ad-removal revenue is non-USD) and the GA4 export has no refund event. VAT largely cancels in the twin-vs-sale comparison because both sell into a similar country mix, and refunds are handled separately in section 7. These weekly rows are restricted to devices with a first_open, so revenue and installs describe the same population; counted over the whole export the same weeks are 9–11% higher — .sale $277.43 → $304.13 in the 09-07 week and $197.55 → $221.54 in 09-14 — which is why the destroyed run-rate is quoted here as $241–$267/week rather than as one number.

Campaign mix — the arms are consecutive weeks, not a randomisation

The offer-absent arm has more Tier-1 ROAS traffic, which monetises better. Reweighting each arm's per-campaign outcome to the offer-present mix ((unattributed): 71%, GA_TinyKnightfa_A_ROAS_Global_20072026: 26%, GA_TinyKnightfa_A_ROAS_Tier1_150926: 3% of installs with an AppsFlyer id):

metricpresent (reweighted)absent (reweighted)change
no-ads IAP $$0.17$0.10−$0.07
full-price no-ads $$0.06$0.04−$0.02
other IAP $$0.12$0.15$0.04
all IAP $$0.28$0.25−$0.03
ad revenue $$0.04$0.08$0.03
TOTAL $$0.33$0.33$0.00

Mix reweighting moves D0–D2 slightly towards neutral (+0.0018 vs +0.0277 raw) and leaves the D0–D7 direction unchanged (-0.0819).

7 · Refunds — a separate action item, not a condition of the rollback

Claim. Two distinct delivery defects are visible in Play's own records. Neither is caused by the .sale SKUs and neither was fixed by removing them — the volume simply migrated onto a pack that refunds worse. Restoring the discounted offer is therefore not a trade against this problem, and it is not a substitute for fixing it: put the rollback and the purchase-completion fix in the same release.

What the removal did to refunds: it removed one SKU that refunded at 9.4% and replaced its volume with a SKU that refunds at 31.6% (Fisher p = 0.026). The game's refund rate went up, 3.7% → 5.4%, and the increase is entirely inside the ad-removal family (non-ad-removal: 2.3% → 2.9%).

Defect 1 — the client never acknowledges one-time-product purchases. 10 refunds in the ledger were initiated by Google, and every single one lands between 72.167 h and 72.173 h after the charge (mean 72.169 h) — a machine, not a player. That is the signature of Play's automatic refund for a purchase the client never acknowledged inside the 3-day deadline. By SKU: skip.ad.permanent ×3, skip.ad.30days ×3, skip.ad.30days.sale ×1, skip.ad.permanent.sale ×1 — i.e. one of the ten is on the pack that was removed and six are on the two SKUs that remain on sale — plus auto.blessing ×2, which is not an ad-removal SKU at all. Removing the .sale packs cannot fix this, and it is not .sale-specific.

SKUchargedrefunded by Googlelag (h)currencyamount
auto.blessing2026-08-292026-09-0172.2USD$5.99
permanent.sale2026-09-122026-09-1572.2EUR$11.99
30days.sale2026-09-132026-09-1672.2TRY$279.99
auto.blessing2026-09-162026-09-1972.2EUR$5.49
permanent2026-09-162026-09-1972.2CHF$24.00
permanent2026-09-182026-09-2172.2JPY$5,300.00
30days2026-09-242026-09-2772.2USD$10.99
30days2026-09-272026-09-3072.2ILS$32.00
30days2026-09-272026-09-3072.2USD$10.99
permanent2026-10-052026-10-0872.2USD$21.31

Defect 2 — the .sale pack's own grants went missing. Its ledger refunds are dominated by player-initiated reasons: 4× not_received, 2× defective, only 1× Google-initiated. That is players saying they paid and got nothing, on the pack that was ~30% of the offer-present arm's revenue per install (it alone was $0.13 of the $0.46 total). GA4 cannot separate a delivery bug from refund abuse of a cheap permanent entitlement; the reason mix, and the fact that the same acknowledgement signature appears elsewhere, both point at the delivery path rather than at abuse. The concrete next step is a client/Play question, not a telemetry one: take one refunded order, check its acknowledgement state, and check what the account was actually granted.

skuvoided_reasonrefunds
auto.blessingaccidental_purchase2
auto.blessingdefective1
auto.blessingundocumented_code_82
campaign.101accidental_purchase1
campaign.401not_received1
campaign.51not_received3
daily.challenge.key.1chargeback1
membershipaccidental_purchase3
membershipnot_received2
package.combo.mythicaccidental_purchase1
package.combo.mythicfriendly_fraud1
package.combo.mythicnot_received1
package.promotionfriendly_fraud1
skip.ad.30daysnot_received2
skip.ad.30daysremorse1
skip.ad.30daysundocumented_code_83
skip.ad.30days.saleundocumented_code_81
skip.ad.permanentundocumented_code_83
skip.ad.permanent.saledefective2
skip.ad.permanent.salenot_received4
skip.ad.permanent.saleundocumented_code_81
time.limit.mega.summonnot_received2
time.limit.mega.summonremorse1

The ledger is an accumulation of Play's 30-day voidedpurchases window (re-pulled 2026-10-10 20:29, 40 refunds; earlier pulls cannot be recovered). Play's monthly sales report, which is complete, is the table in section 5 and is the better denominator: 30 refunds on 700 orders, 4.3%, of which ad-removal SKUs are 16.

8 · Verdict, the counter-argument, and what would falsify it

Roll back: restore ONE $10.99 permanent ad-removal pack (skip.ad.permanent.sale)

Why roll back. Three things point the same way, and none of them depends on the one term that made the earlier reads look neutral:

  • The destroyed revenue is real and precisely measured. $0.15/install over D0–D2 (CI −$0.18 … −$0.11), $241/week ex-China — a 66% collapse of the no-ads IAP line, and 36.7% of tracked IAP revenue historically.
  • The change did not do what it was for. The trade-up failed by a factor of ten (-3.6% against a 36.6% break-even) and the refund problem it was credited with fixing got worse (3.7% → 5.4%, with the non-ad-removal catalogue flat). Nothing the removal was supposed to buy actually happened.
  • The compensation was bookkeeping, not behaviour. The ad gain is a property of the v32+ ad stack (section 4). Once withdrawn, the point estimate is negative in every window: −$0.04/install over D0–D2, −$0.13 over D0–D7 — −$112 to −$35/week at today's 843 installs/week, −$225 to −$70/week at the 1,691/week the game was running before the removal.

Why this form and not the three-SKU bundle. The price point was never the problem — the twin at $29.99 converts at roughly a tenth of the rate (8 Play orders in 20 post-removal days against 64 for the $10.99 pack in the 20 before it). What the surviving $10.99 offer got wrong is the product: 30 days instead of permanent, refunding at 31.6% (Fisher p = 0.026). So restore the permanent form at $10.99 inside the same new-player window. Leave skip.ad.annual.sale deleted — never tapped or bought once in the entire export — and leave skip.ad.30days.sale deleted unless the 30-day form is wanted for a different job.

How much it should recover: the mirror of the measured loss — $0.04 to $0.13 per install, i.e. roughly −$112 to −$35/week at today's volumes, plus the product-quality gain from selling permanence rather than a 30-day rental. Treat that as an expected value, not a promise: install volume is now half what it was and the traffic mix has changed.

What rolling back does NOT do. It does not fix the acknowledgement bug, and it is not a substitute for fixing it: 10 Google-initiated refunds land exactly 72.17 h after the charge on the surviving full-price SKUs (section 7). Restoring the pack adds a SKU that refunds at 9.4% on player complaints of non-delivery, so the two actions belong in the same release: restore the offer and fix the purchase-completion path, then confirm both against this page.

Honesty about the strength of this call: every net point estimate here has a 90% interval that spans zero. The reason to act anyway is that the individual pieces are not symmetric — the destroyed line is measured with a tight interval, the mechanism behind the offsetting ad term is disproved, and the stated rationale for the removal is falsified. That is a preponderance-of-evidence recommendation, not a statistically significant effect.

The strongest argument against rolling back

Two things, and they are why this is written as a preponderance call. First, the raw D0–D2 point estimate is positive — total revenue per install +0.0277 (CI −$0.14 … $0.20), and campaign-mix reweighted +0.0018. If even part of the ad gain is real behaviour rather than the build — players who cannot buy ad removal watching more rewarded ads, which the data cannot rule out because build and removal are collinear on the install side — then the removal is closer to neutral than to a loss, and a rollback would be undoing a change that paid for itself. Second, the arms are consecutive time periods, not a randomisation: UA campaigns ended, traffic quality moved (post-removal D1 retention was ~5 pts lower), and the install rate halved, so no observational cut here is a clean counterfactual. If the removal's real purpose was to stop refund abuse rather than to reprice, the case for undoing it must rest on the refund numbers, and those are small (13 post-removal refunds against 17 before).

What would falsify this verdict

9 · Traps respected (and what I could not establish)