# 10秒ピッタリのランキングを、公平にする：小数3桁・同着・書き込み回数

> 時計の精度の差で iPhone が有利にならないよう小数3桁に切り、同着の確率（約1/333）に備えてタイブレークを決め、KV の書き込み回数を数えた。設計の理由と、公開した日のつまずき。

- URL: https://koshinstudio.com/notes/stopwatch-rank/
- 公開: 2026-10-08
- 書いた人: Koshin（https://koshinstudio.com/about/）
- サイト: Koshin Studio

  English version (https://koshinstudio.com/en/notes/stopwatch-rank/)

10秒ピッタリ 開発の記録 ／ 2026年10月

  10秒ピッタリで止めろ！ (https://koshinstudio.com/works/stopwatch10/)は、頭の中で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秒ピッタリで止めろ！（作品ページ） (https://koshinstudio.com/works/stopwatch10/) — 頭の中で10秒を数えるゲーム

- 広告の間隔を、回数ではなく時間で測る (https://koshinstudio.com/notes/adgap/) — 数え方を決め直した、別の記録

  遊ぶ (https://koshinstudio.com/play/stopwatch10/)作品ページへ (https://koshinstudio.com/works/stopwatch10/)
