10秒ピッタリのランキングを、公平にする:小数3桁・同着・書き込み回数
10秒ピッタリ 開発の記録 / 2026年10月
10秒ピッタリで止めろ!は、頭の中で10秒を数えて止めるだけのゲームです。3回勝負で、誤差が小さいほど強い。これにランキングをつけるとき、「公平とは何か」を、三つの場所で決める必要がありました。リポジトリの README とコードの注記に残っている理由で書きます。
表示は小数3桁まで
誤差は、ブラウザの performance.now() で測ります。README によると、この時計は、セキュリティ対策(Spectre)のために、わざと精度が落としてあります。Chrome は 5 マイクロ秒、Safari と Firefox は 1 ミリ秒刻み、とのことです。
ここで、誤差を 6 桁まで表示するとどうなるか。時計の粗い端末ほど、末尾が 000 になりやすく、有利になってしまいます。iPhone 勢が上位を独占する、というのが README の見立てです。そこで、3 桁に切って、全端末を揃えることにしました。計測の起点も、click ではなく、pointerdown の event.timeStamp を使います。click には、余計な遅れが乗るからです。
同着は、思ったより起きる
3 桁にすると、「ぴったり 0.000」は、1 回あたり 1/1000 の確率で出ます。3 回のうち、どれか 1 回でも 0.000 になる確率は、約 1/333。これで「3回のうちベスト 1 回」の盤を作ると、0.000 の同着が詰まります。
そこで、同着のときは、残り 2 回の誤差の合計が小さいほうを上にします。それも同じなら、先に出したほうです。0.000 を出して、しかも残り 2 回も揃えた人が上に来るので、まぐれの 1 回だけの人と、区別がつきます。
なお、「合計」と「平均」のランキングは、順位が完全に一致します(3 で割るだけだからです)。だから、1 本にまとめてあります。そのうえで、別の軸として、「ベスト 1 回」の盤を置きました。平均が実力、ベスト 1 回が運、という考えです。
書き込み回数を数える
ランキングは、Cloudflare Workers と KV(キーと値の保存場所)でできています。コードの注記では、KV の書き込みは、無料枠で 1 日 1,000 回です。この枠に収めるために、次のことを決めています。
- 盤面は、モードごとに1 つのキーにまとめる。1 回の投稿で書くのは、最大 2 回(今日のぶんと、通算のぶん)。
- 順位に入らない投稿と、同じ名前で前より悪い投稿は、そもそも書かない。
- 同じ名前は 1 行だけ残す。一人で盤を埋めることはできない。
それから、記録はクライアントからの申告なので、不正は防げません。README にも、そう書いてあります。だから、毎日リセットする前提で運用します。日付は日本時間で切り、今日のぶんは 3 日で消えます。
公開の前後に直したこと
- 受け付ける誤差の幅。画面が出せる一番遅い記録(10 秒モードで 99.999 秒)まで、受け付けるようにしました。ひどい記録でも、投稿は通します。どうせ盤には残らないからです。
- 書く前の読みは、キャッシュしない。KV の読み取りに、短いキャッシュを付けていると、古い値の上に新しい値を書いてしまい、その間に入った投稿が消えます。書き込みの前の読みだけは、キャッシュを付けません。見るだけの読み取りは、寝かせたままです。
- 繋がらなかったときの、やり直し。最初は、ゲームを開いたときにランキングへ繋がらなければ、ランキングの枠を出さず、その後は二度と試しませんでした。ランキングの置き場が、遊んでいる間に立ち上がっても、載せる道が戻ってきません。3 回そろったところで、枠が隠れたままなら、一度だけ読み直すようにしました。
- 黙っておく。繋がるまでの間は、ランキングの枠そのものを出しません。断りも出しません。空の枠や赤い字を見せるより、黙っているほうがいい、と決めました。
公開した日のつまずき
最初に Cloudflare Pages へ出したとき、--branch を付けませんでした。そのとき居た作業ブランチの名前が、Pages の本番ブランチとして登録されてしまいました。その後、--branch main で出しても、プレビューに入るだけで、公開している URL は変わりません。1 時間、気づきませんでした。
ダッシュボードで、本番ブランチを main に直しました。もう一つ、混同していた点があります。デプロイ一覧の「Production」の行に出るブランチ名は、そのデプロイを出したときの名前で、設定を変えても書き換わりません。設定を見るのは、プロジェクト一覧の「Production branch」の列です。
分かったこと
公平さは、桁数と、同着と、書き込み回数で、できている。
どれも、ゲームの面白さには見えない部分です。ただ、3 桁に切る、同着の決め方を先に決めておく、という二つは、遊ぶ人から見える順位の納得感に、直接効きます。
この記事は、リポジトリの README、コードの注記、コミット(2026年9月8日〜12日)の記録から書きました。ブラウザごとの時計の精度は、README の記述によります。
この話に出てくるもの
- 10秒ピッタリで止めろ!(作品ページ) — 頭の中で10秒を数えるゲーム
- 広告の間隔を、回数ではなく時間で測る — 数え方を決め直した、別の記録
ほかの記録
- App Store の審査で止まった四つ:iPad のスクショ、ITMS-91064、アカウント削除、投稿の通報 — iPad スクリーンショットの要求、自動検査の ITMS-91064(NSPrivacyTrackingDomains が空)、アカウント削除(5.1.1(v))、投稿の通報(1.2)。審査に出す手前で止まった四つと、その直し方。
- 「9種セット」の正体は御三家9商品だった:同じ商品なのに、決して一致しなかった — 他のまとめサイトと突き合わせたら、抽選の表示は54%だった。商品の取り違え、比べ方の左右非対称、記事IDの穴。カバー率を91%にした記録。
- iOS アプリが固まって強制終了される(0x8BADF00D):SwiftUI の計算プロパティが毎回走っていた — 実機のログにあった 0x8BADF00D(watchdog)。951件の記事を、画面を描き直すたびにメインスレッドで数え直していた。原因の特定と、バックグラウンドで計算する形に直した記録。
コメント
感想や、記事の間違いの指摘は、ここへ。書くには、GitHub のアカウントが要ります。
押すと、giscus(giscus.app)と GitHub に接続します。押さなければ、接続しません。詳しくはあつかいに書いています。