Собесов

Happy Games — обвал retention на 7-й день: стратегия поиска проблемных уровней

Кейсы и метрикиРасследование падений retentionСредняяJunior

Условие

Абстрактный казуальный Match-3 проект (как Candy Crush Saga). Карта с цепочкой уровней.

Проблема: retention rate с D1 по D6 — нормальный (плавно угасает: 40 / 30 / 26 / 24 / 22 / 20%). На D7 резкий обвал до 15%. Хорошему ретеншну свойственно плавное угасание; резкое падение сигнализирует о продуктовых проблемах.

С технической стороны всё гладко. Логично предположить, что есть проблемы с уровнями (например, со сложностью или интересностью).

Задача: придумать стратегию выдвижения гипотез, какие номера уровней проблемные, начиная с какого номера резко проседает retention rate.

Вы не ограничены в инструментах — можете добавить любые события на client/server side в неагрегированную аналитику (a-la ClickHouse) и строить любые агрегации.

Решение

Подход

Главная идея: связать «момент ухода» юзера с уровнем, на котором он находился, и найти уровни-аномалии.

Сразу несколько важных гипотез:

  1. «Уровень-стенка»: один или несколько уровней слишком сложные → юзер пытается, не проходит, бросает.
  2. «Уровень-скука»: уровень слишком лёгкий или однотипный → потеря интереса.
  3. «Уровень-paywall»: после уровня N экономика игры (жизни / ходы) меняется так, что без покупки нельзя продвигаться.
  4. «День 7» — это не «уровень N», а время-ориентированная проблема: например, на день 7 кончаются daily-rewards / интро-бонус.

Шаг 1. Какие события логировать

Если можем добавить события — нужны:

Событие Параметры Зачем
level_started level_id, attempt_number, lives_left, moves_available, boosters_owned, ts старт уровня
level_failed level_id, attempt_number, moves_used, score, reason поражение
level_completed level_id, attempt_number, moves_used, score, stars_earned победа
level_quit level_id, attempt_number, time_in_level, ts юзер вышел из уровня
app_close last_screen, last_level_id, ts момент закрытия игры
purchase_offered offer_id, level_id, context паyoll-предложение
purchase_completed / declined конверсия в покупку
tutorial_step step_id для воронки туториала
daily_reward_claimed day, reward_value связь с D7
chapter_completed chapter_id, level_id прогресс по главам

Шаг 2. Гипотеза «уровень-стенка»

Метрики:

  1. CR (level pass rate) = level_completed / level_started для каждого уровня. Аномально низкий = «стенка».
  2. Average attempts per level = level_started / level_completed_unique_users. > 5 = слишком много попыток.
  3. Drop-off after level N = % юзеров, никогда не вернувшихся после level_started(N). > 30% = стенка.
-- Pass rate по уровням (за выбранный период)
SELECT
  level_id,
  COUNT(*)                                                 AS total_starts,
  COUNT(DISTINCT user_id)                                  AS unique_starters,
  COUNT(*) FILTER (WHERE event = 'level_completed')        AS completes,
  COUNT(*) FILTER (WHERE event = 'level_completed') * 1.0
       / COUNT(*) FILTER (WHERE event = 'level_started')   AS pass_rate
FROM level_events
GROUP BY level_id
ORDER BY level_id;

И визуализация: график pass_rate по level_id с подсветкой «дропов» (где pass_rate резко падает относительно соседей).

Шаг 3. Гипотеза «связь дня 7 с конкретным уровнем»

Самое прямое: на каком уровне находится «средний» юзер на день 7?

-- Уровень, на котором стоит юзер в момент ухода
WITH last_level AS (
  SELECT
    user_id,
    MAX(level_id) AS last_level,
    DATE(MAX(ts)) - DATE(MIN(ts)) AS days_to_quit
  FROM level_events
  WHERE event IN ('level_started', 'level_failed', 'level_completed')
  GROUP BY user_id
)
SELECT
  days_to_quit,
  AVG(last_level) AS avg_last_level,
  MEDIAN(last_level) AS median_last_level
FROM last_level
GROUP BY days_to_quit
ORDER BY days_to_quit;

Если на дне 7 медианный «последний уровень» — это уровень N, наиболее вероятно, проблема — на N или близких уровнях.

Шаг 4. Гипотеза «уровень-paywall»

SELECT
  level_id,
  COUNT(DISTINCT CASE WHEN event = 'purchase_offered'   THEN user_id END) AS offered,
  COUNT(DISTINCT CASE WHEN event = 'purchase_completed' THEN user_id END) AS bought,
  COUNT(DISTINCT CASE WHEN event = 'purchase_declined'  THEN user_id END) AS declined,
  COUNT(DISTINCT CASE WHEN event = 'app_close'
                       AND last_screen = 'paywall'      THEN user_id END) AS quit_at_paywall
FROM events
GROUP BY level_id;

Уровень с высоким quit_at_paywall — проблемный paywall.

Шаг 5. Гипотеза «не уровни, а время»

Возможные не-уровневые причины обвала на день 7:

  • End of intro-promo — закончились бесплатные бустеры новичка → стало сложнее, юзер ушёл.
  • First рекламный push — слишком навязчивая на день 7.
  • Daily-reward chain reset — cycle ребута бонусов даёт «нечего делать».
  • Жизневременной gate: на день 7 у юзера набралось много прогресса и жизней «не хватает».

Для проверки — построить timeline пользователя на день 7 и найти типовую причину закрытия:

SELECT
  EXTRACT(EPOCH FROM (last_event_ts - first_event_ts)) / 3600 / 24 AS days_to_quit,
  last_screen,
  COUNT(*)
FROM (
  SELECT user_id,
    MIN(ts) AS first_event_ts,
    MAX(ts) AS last_event_ts,
    LAST_VALUE(screen) OVER (PARTITION BY user_id ORDER BY ts
                              ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_screen
  FROM events
  GROUP BY user_id
) t
GROUP BY days_to_quit, last_screen
ORDER BY days_to_quit, COUNT(*) DESC;

Шаг 6. Визуализации

  1. Pass rate vs level_id (line + scatter) — найти «уровни-стенки».
  2. Distribution последнего уровня по days_to_quit (boxplot) — на каком уровне «застревают» в день 7.
  3. Cohort retention — посмотреть, не зависит ли паттерн от UA-канала / гео.
  4. Timeline event_count per day — может, юзеры просто играют меньше на день 7 (вечером пятницы устают?).

Анализ / интерпретация

Шаги действий:

  1. Сначала добавить события для уровневой телеметрии.
  2. Построить «pass rate per level» — найти 1–3 «уровня-стенки» с резко низким CR.
  3. Построить «median last level on day 7» — если совпадает с уровнем-стенкой — диагноз ясен.
  4. Если нет — переходить к гипотезе «time-based» и проверять paywall, daily-reward, ad-load.

Подводные камни

  1. Не путать «retention» с «day-X retention». Retention D7 = «вернулся ли юзер на 8-й день»? Если резко упал — это потеря на D7. Если на D6 — потеря между D6 и D7.
  2. Ловушка усреднения: «средний юзер на дне 7 на уровне 20» — но среди них могут быть те, кто на уровне 5 (плохой) и те, кто на 35 (хороший). Используйте distribution, а не среднее.
  3. Survivor bias. На день 7 остались «лучшие» — для них «уровень-стенка» может быть на 30, а для среднего юзера — на 15.
  4. Cohort effect: разные UA-каналы дают разные retention-кривые. Анализируйте сегментно.
  5. Корреляция ≠ причина. Юзер «уходит на уровне 17» может потому, что 17-й сложный, а может потому, что 16-й вечно показывал ad. Нужны explicit события app_close с контекстом.
  6. «15% retention D7» — мало это или много? Бенчмарк в casual для match-3 — 20-25%. То есть проблема реальная, +5 пп — это огромный эффект.
  7. Время логирования событий. Если события агрегируются раз в час, можно потерять «момент ухода». Нужны realtime-события.

Альтернативы

  • A/B-тест с упрощением «уровня-стенки»: если кривая retention перестала просаживаться → диагноз верный, можно раскатывать.
  • Funnel analysis по уровням в Amplitude / Mixpanel — дешёвая альтернатива кастомным запросам.

Эталонный ответ

Шаги стратегии:

  1. Логи: level_started/failed/completed/quit, app_close (с контекстом), purchase_offered/completed/declined, daily_reward_claimed.
  2. Метрики: pass rate per level (главный график), avg attempts, drop-off rate, median last_level by days_to_quit.
  3. Гипотезы в порядке проверки: уровни-стенки → paywall → time-based (ивенты, daily, ad) → UI-баг.
  4. Решающий артефакт: график pass_rate vs level с обведёнными «уровнями-стенками» + распределение «последнего уровня» по days_to_quit.

Главное — измеримая декомпозиция: связать день обвала с конкретными уровнями через медиану last_level_id на дне 7, и проверить pass rate каждого уровня для подтверждения.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти