Экран создания героя в MMORPG для Telegram не листался на Android, и дважды до этого я чинил не ту поломку

Я делаю EclipseFall — MMORPG внутри Telegram, которую не нужно устанавливать. В прошлом лонге я разбирал, почему у каждого из 527 предметов игры своя нарисованная иконка. Сегодня история попроще на вид и подлиннее по времени: экран выбора народа не прокручивался на Android, игрок не мог создать героя и уходил. До того как я нашёл настоящую причину, я успел дважды починить эту же прокрутку и оба раза мимо.

Технический разбор поломки прокрутки
Технический разбор поломки прокрутки

Как всё началось: защита, которая всё сломала

Седьмого августа выяснилось неприятное: игру закрывали случайно. Доскроллил список до конца, потянул пальцем дальше — Telegram считает это жестом закрытия и сворачивает окно. Игрок вылетает посреди подземелья.

Лечится это в CSS одной строкой: запретить контейнеру отдавать прокрутку наружу, когда она в нём кончилась. Я поставил правило глобально, по классу прокрутки, а не в каждом экране отдельно — рассуждение было такое, что иначе про него забудут в следующем новом экране. Свайп-закрытие прекратилось.

Заодно я подложил себе мину, которая рванула трижды.

Первый взрыв: колесо мыши

Двадцатого августа владелец аккаунта (я же, но с другого устройства) открыл игру в Telegram Desktop и обнаружил, что колесо мыши не работает вовсе. Страница стоит намертво, стрелки на клавиатуре при этом листают нормально, на телефоне тоже всё едет.

Дефект к тому моменту жил тринадцать дней и никем не был замечен, потому что играли с телефонов.

Воспроизвёл я его без Telegram, на чистом Chromium и Edge, а виновника нашёл бисекцией по CSS: тем самым запретом. Механика такая. Контейнер, который якобы прокручивается, на деле вырос на всю высоту содержимого, и прокручивать ему нечего. Колесо крутится над ним, он остаётся на месте, передать прокрутку странице ему запрещено моим правилом. Прокрутка умирает между ними.

Починил я тогда только мышь: вернул нормальное поведение через условие «указатель точный, наведение доступно», то есть для мыши правило отключается, а для касаний остаётся. Тач ведь работал.

Вот это «тач ведь работал» и было ошибкой. Тач работал на моём iPhone.

Второй взрыв: рамка ВКонтакте

Первого сентября игра поехала во ВКонтакте, и там выбор народа снова не листался: список стоит на месте, вместо него уезжает страница самого ВК.

Причина оказалась чужая и совсем другая: ВКонтакте открывает приложение в рамке, у которой прокрутка отключена самой площадкой, а размер фиксирован. Моя вёрстка рассчитана на то, что прокручивается страница браузера — в Telegram так и есть. Внутри рамки страницы нет, и содержимое просто обрезается по нижнему краю.

Починка встала отдельной веткой: под ВКонтакте областью прокрутки становится само тело документа. Правила включаются пометкой, которую ставит только ветка ВК, — в Telegram и в обычном браузере их нет вовсе, так что сломать там нечего по построению.

Я закрыл задачу и записал в голове: «с прокруткой разобрались». Разобрался я с двумя симптомами из трёх.

Второе сентября: два человека, ноль боёв

Второго сентября два читателя предыдущей статьи написали одно и то же: экран создания героя на Android не листается. Ни выбор народа, ни выбор призвания. Телефон у одного из них — Poco C85, Telegram, обычный Android.

Воронка за этот день выглядит так: пришли двое, оба создали героя, оба закрыли пролог, боёв — ноль. Выборка крошечная, выводы из двух человек я делать не стану, но это ровно тот день, о котором мне написали.

Экран создания героя — первая дверь в игру. Человек, который не смог её открыть, не увидит ни одного зала и ни одного врага.

Эмблема зала «Затопленный шлюз»
Эмблема зала «Затопленный шлюз»

Синтетический жест ничего не доказал

Дальше началась диагностика, и первое, обо что я споткнулся, — инструмент.

Проверять прокрутку в браузерном стенде принято командой, которая изображает жест прокрутки целиком. Я её запустил, и она отчиталась, что всё работает. На голой пустой странице она тоже отчиталась, что всё работает, — при том что страница не сдвинулась ни на пиксель. То есть команда в этой сборке движка не двигает вообще ничего, и её зелёный ответ не значит ничего.

Пришлось спуститься на уровень ниже и слать настоящие касания — три события подряд: палец коснулся, палец поехал, палец отпустили. Тогда появились числа, с которыми можно работать:

- голая страница с длинным текстом под пальцем едет примерно на 440 пикселей — это контроль, он доказывает, что прибор вообще что-то меряет; - экран выбора народа в игре едет на 0 пикселей; - он же после починки — едет, и едет как голая страница.

Первая строчка тут важнее двух остальных. Без неё я бы снова смотрел на ноль и гадал, сломана игра или сломан стенд.

Настоящая причина: контейнер, который только притворялся

Виновата была раскладка, повторённая в шести экранах: создание героя, Логово, снаряжение, сборка группы, редактор ротации, выбор пути.

Выглядит она безобидно — внешняя обёртка высотой «не меньше экрана», внутри блок, который занимает остаток места и объявлен прокручиваемым. Беда в том, что «не меньше экрана» означает «и больше тоже можно»: обёртка спокойно растёт под содержимое, вместе с ней растёт внутренний блок, и прокручивать ему становится нечего. Он остаётся прокручиваемым по документам и никогда не прокручивается на деле.

А раз он числится прокручиваемым, на него действует августовский запрет отдавать прокрутку наружу. Палец лежит на блоке, которому некуда ехать, страница прокрутку не получает, потому что запрещено. Ничего не происходит.

На мыши я это вылечил месяцем раньше, отключив запрет для точного указателя. На касаниях запрет остался — он там и нужен, ради него всё затевалось. Поэтому Android молчал, а iPhone в моих руках прощал: у него собственная инерция прокрутки перекрывала дефект.

Починка — не в том, чтобы снять запрет, а в том, чтобы убрать фальшивые контейнеры: содержимое отдаётся странице, страница прокручивается, запрет продолжает ловить свайп-закрытие ровно там, где для него есть настоящий скроллер.

Правило вместо памяти

Такую раскладку я повторил шесть раз, значит повторю и седьмой. Поэтому вместе с починкой в проект встал тест, который читает все экраны и требует: если блок объявлен прокручиваемым, у него обязана быть ограниченная высота. Сочетание «занимает остаток места» и «прокручивается» запрещено прямо — это и есть фальшивый скроллер.

Исключение в тесте ровно одно: окно боевого реплея, чью высоту держит внешняя обёртка, прибитая к краям экрана. Оно записано поимённо, и расти этому списку без доказательства стендом нельзя.

Сам тест я потом проверил порчей: вернул запрещённую раскладку на место и убедился, что он краснеет. Тест, который ни разу не краснел, доказывает только то, что его написали.

Отдельно поехала проба с настоящими касаниями по четырём экранам: она меряет, сколько пикселей проехало, а не спрашивает у браузера, всё ли хорошо.

Вышло это в версии 1.54.0 третьего сентября, на следующий день после жалобы. Один из написавших вернулся и подтвердил: теперь работает.

Портрет обитателя болот — то, ради чего игрок и пробивается через первый экран
Портрет обитателя болот — то, ради чего игрок и пробивается через первый экран

Подсказку дал читатель

Был ещё один человек. Первого сентября в комментариях на другой площадке мне написали фразу в одну строку: проверьте запрет отдачи прокрутки на контейнере во весь экран. Это был точный диагноз, за сутки до жалоб и за двое до починки.

Я тогда прочитал это как общее замечание о вёрстке и прошёл мимо. Своё «тач ведь работает» весило больше чужой строчки.

Что из этого следует

Живых игроков в игре пока меньше трёх десятков. Число честное и маленькое, этот блог я веду как историю разработки, а не как отчёт об успехе. Но и на такой выборке видно, что дефект первой двери стоит дороже любого дефекта в глубине: пропущенный бой можно переиграть, а несозданного героя никто не создаст задним числом.

Для себя я вынес три вещи, и все скучные. Глобальное правило, поставленное ради одной защиты, действует на весь код, включая тот, что напишут после него. Зелёный ответ инструмента без контрольного замера — не доказательство. А фраза «на моём устройстве работает» означает ровно то, что написано: работает на моём устройстве.

Игра живёт внутри Telegram, ставить ничего не нужно: открыть EclipseFall. Про такие разборы я пишу в девлоге: t.me/eclipsefall_game.