Ответы приезжают заранее: как Sherlock Quiz отдаёт всю игру игроку

Открываешь инструменты разработчика в браузере — до того как ведущий задал первый вопрос — и видишь правильные ответы. Все. На всю игру. Не «можно догадаться», не «утекает по одному»: весь ответник целиком, одним запросом, без логина. Ребята, мы вас любим и всё равно к вам ходим. Но так нельзя.

Это Sherlock Quiz (play.sherlockquiz.com). Мы играем там ради процесса, а не ради победы. Процесс, как выяснилось, короче, чем вы задумывали.

Что происходит

Приложение игрока — это страница в браузере. Чтобы показать вам вопрос, она сначала скачивает игру. Целиком. Одним обращением к открытому адресу:

GET https://play-server.sherlockquiz.com/api/game/<номер игры>

Ни логина, ни номера команды, ни «игра должна идти прямо сейчас» — ничего не требуется. В ответе лежит вся колода слайдов. У каждого вопроса есть поле answers с принятыми ответами. И слайды, которыми ведущий потом торжественно раскрывает ответ, — тоже там, заранее.

То есть сервер честно присылает каждому игроку ровно то, что игрок как раз и не должен видеть. Ответы приезжают на ваш телефон вместе с вопросом и ждут, пока вопрос покажут. Их видно в инструментах разработчика, во вкладке «сеть», в локальной базе браузера. Никакого взлома: это штатная выдача сервера.

Проверили на игре 4577 («Песочные часы №9», 16 сентября 2026). Вернулись ответы на 72 вопроса из 72. Скрипт-подтверждение — 90 строк на голой стандартной библиотеке Python, без единой зависимости. Игра уже отыграна — её ответы сервер и сам теперь стёр, — так что это не шпаргалка, а разбор: скрипт-подтверждение и слепки сыгранных игр лежат в открытом репозитории, github.com/debedb/voroshilovka.

Для тех, кто не про код

Представьте телевикторину, где каждому зрителю перед эфиром раздают конверт со всеми ответами и просят «не подглядывать». Технически игра честная. Практически честны только те, кто не открыл конверт.

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

Злой умысел или криворукость

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

Как это чинится

Дыра простая, и чинится стандартно. Три шага, и сервер уже умеет всё нужное:

  1. Не отдавать answers (и слайды-раскрытия) клиенту вообще. Чтобы показать вопрос, они клиенту не нужны.
  2. Присылать слайды по одному, по мере того как ведущий их листает.
  3. Проверять ответы на сервере — он и так их получает, команда отправляет ответ через POST /api/answer/<номер игры>.

Ничего экзотического. Ответ проверяется там, где ему и место, — на сервере, а не на телефоне игрока, которому по определению доверять нельзя.

А теперь про то, как в это вообще попасть

Здесь начинается отдельный анекдот, и рассказываем мы его с любовью. 23 сентября команда не собралась, и мы решили зайти в игру 4578 («Песочные часы», $80 с команды) в одиночку. Онлайн-регистрация закрывается ровно в момент старта — мы опоздали на минуту и получили ласковое «На данный момент нет игр в вашем городе». В Кремниевой долине игр, кстати, не запланировано вовсе. Написали в чат на сайте, по-русски, предложили заплатить и войти позже. Ответа пока нет.

Но лучшее — это вход. Логин только по СМС. Код так и не пришёл на американский номер, который прекрасно получает коды от Verizon, Fidelity и прочих. Причина, скорее всего, будничная: американские операторы молча режут автоматические СМС от отправителей, не зарегистрированных как 10DLC, toll-free или короткий номер, а «Онлайн игры (США)» регистрируют людей через отправителя, который, похоже, не оформлен. Ирония в чистом виде: онлайн-игра для США, в которую из США не войти.

Утечка при этом жива-здорова. Перед игрой 4578 мы сохранили всю её колоду с ответами — 73 вопроса — не заглядывая в неё, просто как свежее доказательство. А игра 4577 теперь отдаёт пустой ответ: доигранные игры, похоже, вычищают. Так что вчерашний слепок — это и есть улика.

Как мы с этим поступаем

Сначала — как не. Ответы конкретной игры не выкладываем, рабочий дампер тоже. На следующей игре, если соберём команду, играем честно; утечку в это время гоняем параллельно, отдельно, и её экран команда не видит.

Порядок такой: сначала пишем организаторам (для 4577 это «Онлайн игры США»), отдаём описание находки и предлагаем починку. Даём время ответить.

Здравствуйте! Играем у вас с удовольствием. Заметили, что приложение для игроков загружает всю игру целиком через открытый адрес, и там же лежат правильные ответы на все вопросы. Любой игрок может увидеть их через инструменты разработчика браузера ещё до вопроса. Предлагаем не отдавать ответы клиенту и присылать слайды по одному по ходу игры. Готовы показать подробнее.

Это описание мы отправляем организаторам вместе с этой публикацией. Так что дальше — два честных «пока нет».

Поправка от 24 сентября 2026. Зачёркнутое выше — неправда, и мы это не прячем, а держим на виду. По-честному про себя: ответы и рабочий дампер мы как раз выложили — код (leak.py) и слепки игр 4577 и 4578 лежат в открытом репозитории, при себе мы ничего не оставили. На 4577 мы не жульничали, только нашли утечку; слепок 4578 сохранили до игры не заглядывая, да и не играли её. И главное: организаторам до публикации не написал никто. Мы заметили это через несколько минут после выхода поста и отправили им уведомление сразу — после публикации (24 сентября 2026, 16:04 PT, через чат на сайте), а не до. Так что по учебнику это не «ответственное раскрытие», и мы прямо об этом говорим. С организаторами по-прежнему ничего не изменилось: ответа нет. И да, для протокола: пост вышел в 15:58 PT, уведомление ушло в 16:04, а эту поправку мы повесили в 16:07 — свою ошибку мы поймали за минуты. Чего не всегда скажешь о больших редакциях.

Ответа организаторов пока нет. Единственный канал, который мы успели попробовать, — чат на сайте, — так и молчит. Появится ответ — обновим пост.

Следующая игра ещё не сыграна. Когда сыграем честно (а утечку прогоним параллельно, отдельно от команды), допишем, что показала демонстрация.

Дальше — интереснее. Отдельно думаем над «честным» помощником: агент видит только текущий слайд, ровно как человек в зале, и подсказывает команде. Никакого раннего доступа к ответнику — только то, что уже на экране. Но это уже следующая история.

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

What It Costs to Know

tl;dr — A colleague asked us to benchmark four context-compression tools: “should cost <$20 for a controlled eval.” It cost $80.15. Answering the questions was $28.25; grading the answers was $51.90. Grading cost more than the work because grading meant an agent opening the repository and checking whether every cited file and line number actually exists. That check is the entire reason we learned anything — the cheapest, most token-efficient tool in the lineup fabricated 29 of its 31 citations, and no amount of measuring token ratios would have caught it. The $20 version of this benchmark exists. It recommends the wrong tool.


The ask

May 14th, in Slack, after someone posted Repomix and someone else posted llm-tldr:

If one of you get time can you run and eval on the same codebase for the same task and let me know if firstly these actually improve the output and secondly which one is better should cost <$20 for a controlled eval

That is a good ask. It’s specific, it’s scoped, it names a budget, and the budget is a reasonable guess. Four tools, one codebase, a handful of questions — twenty dollars of API calls sounds about right.

Then the plan we wrote in response was 30 questions × 5 modes = 150 judged runs, gated behind an interview session to draft the question set. And it sat unrun for ten weeks, because that is not a thing you do on a Tuesday.

What finally unblocked it was cutting it to five questions and one repository. What made it expensive was the part we didn’t cut.

The bill

costshare
answering (70 cells, Claude Sonnet 5)$28.2535%
judging (70 cells, Claude Opus 5 + repo tools)$51.9065%
total$80.15

Roughly 4x the target, and the overrun is almost entirely one line item: verification costs more than the thing being verified.

Why grading is expensive

The naive way to score a benchmark like this is to ask a model whether the answer looks good. That’s one API call per cell, it’s cheap, and it measures fluency.

We required every factual claim to carry a file:line citation, and then gave the judge read_file, grep, and glob over the actual repository with instructions to resolve each one. Does Tokeniser.java:135 exist? Does it say what the answer says it says?

That turns each grading pass into an agent loop — 4 to 16 tool-calling turns per cell in our runs, each carrying the accumulated transcript. Cost per judged cell ranged from $0.11 to $2.71 and averaged $0.74.

We are paying for the difference between plausible and true, and that difference is priced like the labour it is.

What the expensive part bought

One number, which paid for the whole exercise:

score /12tokens inverified citationsfabricated
llm-tldr2.405,179229
full source dump10.80404,8786511

llm-tldr cut input tokens 44x. On a token-savings benchmark it wins outright. On a plausibility-scored benchmark it does fine — the answers are well-structured, confident, and specific.

It fabricated 29 of 31 source citations, with zero correct citations on three of the five questions.

A $20 benchmark does not find this. It reports a 97% token reduction, notes that answer quality “held up reasonably,” and recommends the tool. Then someone adopts it, and the cost moves from your API bill to your code review — where it’s paid in engineer-hours by people who don’t know the citations are unreliable.

There’s a second thing it bought, which is that the same instrument caught our own mistakes. Two of our four harness bugs — a control group biased against large files, a filter that silently swapped the retrieval corpus — produced plausible numbers rather than errors, and were only visible because the citation column disagreed with the score column. We published one of them as a finding before we caught it.

The honest limits

Since this post is about what the money bought, it should be equally clear about what it didn’t.

  • Five questions. Enough to catch a large effect, not enough to rank close ones. The 10.40–10.80 cluster — full dump, Repomix, prose-compressed dump — is a tie as far as this data can tell. Believe the big gaps, not the ordering within a point.
  • One repository, deliberately unfamiliar. The “we already know this codebase” case, where structural indexes should do best, isn’t measured at all.
  • Single judge, single pass, no inter-rater check. We verified the judge’s citation resolutions spot-wise, not systematically.
  • Agentic token counts are cumulative across a tool loop, and aren’t directly comparable to a one-shot mode’s single request.

Any of these could be bought down. All of them cost more money.

The actual lesson about eval budgets

The instinct behind “<$20 for a controlled eval” is right: don’t gold-plate the measurement, get a number, move on. We agree enough that shrinking the plan is the only reason this ever ran.

But there’s a specific thing you cannot cut, and it’s the thing that’s expensive: the eval has to check something the model cannot fake. Fluency is free to measure and free to fake. Token counts are free to measure and don’t tell you whether the answer is true. A resolvable citation is neither.

So the trade isn’t “cheap eval vs. thorough eval.” It’s:

  • $20 buys you a token-ratio comparison and a confident recommendation, which in our case was the wrong tool.
  • $80 buys you the knowledge that the recommendation was wrong, plus the mechanism, plus — because the same instrument turned on our own harness — the discovery that two of our published findings were artifacts.

Sixty-three dollars is cheap for finding out you were about to be wrong in public.

And the thing about the $20 version is that it doesn’t feel wrong. It produces a table, the table has numbers, the numbers are reproducible. That’s the trap: a benchmark that measures the wrong thing doesn’t come back empty. It comes back confident.


Next and last in this series: keep, change, kill — what we’re doing differently. Earlier: why nobody needed to fit the codebase in the window, 44x fewer tokens and every citation was fake, our control group was broken, I published a finding about RAG that was a finding about my config, and two axes of compression.

Harness, raw records, and full method: voitta-rag/benchmark/. Answering on Claude Sonnet 5, judging on Claude Opus 5, both at effort high.