TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

試合の記録とプライバシー:終わった試合はどこに残り、何が残らないのか

ステータス:ドラフト(2026-10-26)、コードの根拠。 動作は2026-10-25基準のリポジトリコードと設計ドキュメントから読み取りました。本番DBの「修正した記録」の件数は2026-10-18に読み取った値です。ユーザー数などの運用統計は掲載していません。 作成規則:実測データが基本であり、測定できなかったものは「未測定」、根拠が見つからないものは「確認できず」と表示します。

目次

1. 試合が終わると

5ゲームマッチの試合が3:1で終わりました。カメラのスコアボードには「試合終了」画面が表示され、ゲームスコア(11:7、9:11、11:5、13:11)が並んで表示されます。この試合は今どこにあるでしょうか?

スマホの中にだけあります。 第2部で見たように、カメラのスコアボードは試合履歴全体をブラウザの localStorage(ttplay:cam)に保存します。履歴の項目ごとに出所も付いています。画面を押したスコア(tap)、リモコンキー(key)、スコア調整(adj)、自動判定を受け入れたスコア(auto)です。サーバーはこの試合を知りません。

この状態でサーバーに何かを残す方法は、ユーザーが選ぶ3つのルートだけです。

ルート サーバーに行くもの 残る場所
この試合を記録する ゲームスコア、試合時間、相手 試合記録、レーティング
ライブで共有(試合中) 得点・やり直し・終了イベント ライブマッチとイベント列
自動スコア(試合中) フレーム(保存しない)、人間がチップに回答した記録 修正した記録(映像なし)
경기 끝 이력 전체는 휴대폰 localStorage 에만 「이 경기 기록하기」 게임 점수만 넘김 · 손으로 올린 게임은 뺌 Google 로그인 · 표시 이름 구글 실명은 어디에도 안 나감 상대 고르고 「기록하기」 하루 30경기 · 같은 상대 10경기 계정 있는 상대: 확인 대기 상대가 「확인」을 눌러야 함 계정 없는 상대 개인 기록만 7일 안 확인 이의 7일 지남 확인됨 Elo 반영 K 40 · 24 · 16 이의 레이팅 안 움직임 개인 기록 레이팅에 안 들어감 상대 이름은 적은 사람만 순위표: 공개를 켠 사람 중 확인된 경기 3개 이상 프로필은 비공개가 기본 · 레이팅은 1500 에서 시작
図:終わった試合が記録・レーティングになるまで

2. 「この試合を記録する」

何が渡るのか

「試合終了」シートの「この試合を記録する」を押すと(backend/static/tt/play/lib/cam-sheets.mjs)画面は試合の概要 {best_of, games, source:'scorekeeper'} を sessionStorage に一時的に保存し、「マイ記録」画面(/tt/me#match)に移動します。ログイン前であればログインを経由します。記録画面で相手を選んで「記録する」を押すと、その時点でサーバーに送信されます(POST /api/tt/matches).

渡るのは ゲームスコアのみです。ラリーごとの履歴、誰がいつどのように得点したか、自動判定の痕跡は渡りません。いくつかのケースでは記録できません。

ログインと表示名

記録を保存するにはGoogleログインが必要です。ゲストは閲覧のみです。書き込み権限はサイトのコミュニティと同様の関門です。

ここで重要な設計が1つあります。 Googleアカウントの名前はどこにも出力しません。 Googleの名前は通常実名であり、対戦相手やランキングにそのまま表示されると、卓球場で会った人に本名を知られてしまいます。そのため、卓球エリアのいかなる応答・画面でもGoogleの名前を使用しません(backend/app/tt_records.py ヘッダーの「Googleの名前は出力しない」)。表示名を決める前の選手は「卓球人 #番号」であり、試合を記録するにはまず表示名(2〜20文字)を決める必要があります。相手の画面に番号だけで表示される試合は、誰が記録したのか判別できないためです。

カメラのスコアボードのニックネーム欄(12文字まで)にも「実名の代わりにニックネーム」という案内が付いています。

相手が確認して初めてレーティングが動く

1人で記録した勝利でレーティングを上げられる場合、ランキングはどちらがたくさん記録したかの表になってしまいます。そのため、アカウントを持つ相手との試合は 確認待ちに入り、相手が「確認」を押して初めて 確認済みになり、その時点で一度レーティングが変動します。相手が「異議」を申し立てることもあります。 7日以内に確認がない場合 レーティングには入らず、個人の記録としてのみ残ります。期限切れはバッチ処理で行を修正せず、読み取り時に作成された時刻で判定します(DBの1つの時計で測定)。

アカウントのない相手(近所の友達)との試合も記録できます。このとき、相手の名前は その試合を記録した人にだけ 表示され、レーティングは変動しません。

Eloレーティング

レーティングはチェス由来のElo方式を採用しています。

# backend/app/tt_records.py 에서 줄임
def k_factor(rating, rated_games):
    if rated_games < 10:   return 40      # 처음 10경기: 제자리를 빨리 찾게
    if rating >= 2000:     return 16
    return 24

def expected(ra, rb):                     # A 가 이길 기대값
    return 1 / (1 + 10 ** ((rb - ra) / 400))

# 변화량 = K × (실제 결과 − 기대값). 두 사람이 각자 자기 K 를 쓴다.

例えば、両者とも1500で最初の10試合以内であれば期待値は0.5、勝った人は40 × 0.5 = +20、負けた人は−20となります。同じK 40で1600が1500に勝った場合、期待値は約0.64なので+14.4程度しか上がらず、負けると−25.6程度下がります。

1日の上限と同時性

上限 値 理由
1日の試合記録 30 まとめての入力を防止
同じ相手と1日に 10 示し合わせてスコアをやり取りし、レーティングを上げる行為を遅らせる
1日の練習記録 20 —
試合日 1年以内 過去の記録をまとめて入力してレーティングを作る抜け道を狭める
1ゲームスコアの上限 50 入力ミス(110:9)を弾く

上限はDBでカウントします。同じ人の2つのリクエストが同時に「29番目」としてカウントされて一緒に通過しないよう、人ごとにDBのロック(advisory lock)で順番を制御します。確認する際は試合の行を最初にロックし、2人の選手の行を 番号が小さい方から ロックします。アリスとボブが互いの異なる試合を同時に確認する際に「自分の行から」ロックすると、互いに相手の行を待ってデッドロックに陥るためです。

3. 記録画面

画面 表示される内容 対象
マイ記録 /tt/me 自分の試合、確認待ち・異議、レーティング、練習日誌 本人
ランキング /tt/rank 公開選手の順位、サークル・今月のフィルター、今週の急上昇 誰でも
履歴 /tt/me/history 申請・試合・スコアの変化を集約、CSVダウンロード 本人
試合共有 /tt/m/{코드} 共有を有効にした1つの試合 リンクを受け取った人

履歴は新しい記録を蓄積せず、各機能がすでに残したものを読み込んで集約します(backend/app/tt_history.py)。試合記録、ライブスコアボードイベント(サーバーが再計算)、録画解析結果、自動スコアを修正した記録、レーティングの変動が1行に混ざって表示されます。ライブスコアボードと試合記録は互いにキーでつながっていないため、「終了スコアが同じで時間が近い」試合を 推測で つなぎます。誤ってつなぐ可能性があるため、画面にはフローの出所(ライブ・映像)を常に併記します。URLに人の番号が含まれず、すべてのクエリがログイン中の人のみに限定されるため、他人の履歴を見る方法はありません。CSVはExcelの数式インジェクション対策を施して出力されます。

試合共有は試合番号ではなく、別途作成した11文字のコードを使用します。試合番号は連番であるためURLに使用すると他人の試合を1つずつ総当たりで閲覧できるためです。確認された試合は両選手のどちらでも、個人の記録は記録した人のみが共有を有効にできます。確認待ちや異議が申し立てられている試合は、「実際にあった試合」であるかすら決まっていないため共有されません。共有画面でも公開していない選手は「卓球人」、アカウントのない相手の名前は最後まで表示されません。

4. ライブ共有:サーバーがスコアを再計算する

試合中に「ライブで共有」を有効にすると、観戦リンクを受け取った人がリアルタイムでスコアを閲覧できます(backend/app/tt_watch.py).

名前のルールも同様です。リンクは記録者が直接共有したものであるためその試合で設定されたニックネームが表示されますが、誰でも閲覧できる ホームフィードでは、公開を有効にしていない所有者は「卓球人」、相手は「対戦相手」として隠されます。

5. 修正した記録:同意したものだけ、そしてまだ0件

第3部で、人間が自動判定のチップに回答した場合(「正解」、別の選手に修正、「無効」)に、その回答が 修正した記録になると説明しました。これがどこに向かうのかを追ってみます。

何を含むのか

自動スコア画面は、人間が回答したラリーごとに1行を作成して POST /api/tt/corrections に送信します。行には以下が含まれます(backend/app/tt_corrections.py).

映像は含みません。 サーバーはブラウザが送信したものをそのまま格納するのではなく、既知の形状に新しく整形します。テキストが入る余地がほとんどないため、悪口・URL・スクリプトが蓄積される場所はありません。行のキーにはログイン中の人の番号が含まれるため、他人の行を上書きすることはできません。上限は1人あたり1分間に30リクエスト、1日に2,000行です。

時間が経ってそのまま取り込まれた判定には修正の記録を残しません。人間が確認したものではないからです。

誰が何を持ち出すのか

これまでに学習に使ったことはない

2026-10-18時点の運営DBの読み取りにおいて、修正の記録は 0件でした。したがって、 修正記録を学習や評価に使用したことはまだ一度もありません。 第5作の学習モデルはいずれも公開データと合成画像で学習しています。

計画はあります。第5作で見たように、学習型モジュールを商用利用可能なデータで再学習する際、学習の利用に同意した人の修正判定は、「確認必要」の補正といったモジュールの材料になり得ます。「人間が修正した判定」はすなわち「エンジンが間違えた箇所」の正解だからです。ただしそれには修正の記録が十分に蓄積される必要があり、学習材料として使う際の保管期間や同意に関する案内の再点検が先決です。

6. 映像は端末の外に出ない、そして例外

カメラ得点板の第一原則は カメラ映像は端末の外に出ないことです(backend/static/tt/play/cam.js まるがき)。録画もスマートフォン内で合成し、スマートフォンに保存します。得点読み上げもブラウザの音声合成を使用します。

例外となるのは、ユーザーが有効にする2つの機能です。

自動得点。 ボールを探すGPUがサーバーにあるため、フレームを送信します。代わりに範囲を絞りました(第1部)。

録画ファイルのアップロードによる分析。 ユーザーが映像ファイルを直接選択してアップロードする別の機能です。ブラウザが事前に360p60 500kbpsに縮小してアップロードし、元ファイルは30日後に削除します。分析作業者はDBとストレージのキーを持たず、ウェブの内部APIのみで作業を行います。

7. 本名の代わりにニックネーム

プライバシー設計をひとことでまとめると 「卓球場で出会った人に本名を知られないようにする」 ことです。

場所 表示名
カメラ得点板の画面 その試合で設定したニックネーム(12文字、「本名の代わりにニックネーム」の案内)
試合記録・対戦相手の画面 表示名(2〜20文字)。設定する前は「卓球人 #番号」
ランキング・公開プロフィール 公開を有効にした人のみ。デフォルトは非公開
アカウントのない対戦相手 その試合を記録した人のみに表示
ライブホームフィード 公開していない主人は「卓球人」、相手は「対戦相手」
修正記録のエクスポート 名前なし、エクスポートごとに変更されるマスキング値
Googleアカウント名 どこにも出力されない

デモ映像でも同じ原則を適用しています。元の画面に焼き込まれている名前(中継の字幕)はそのまま残しますが、こちらで描き加えた文字は「選手A・B」のみを使用します。

8. サーバーにはどのようなテーブルがあるか

記録に関連するサーバー側のテーブルをまとめると以下のようになります。テーブルごとにDBのマイグレーション番号が付与されており、1つのマイグレーションが1つの機能を導入しています。

マイグレーション テーブル 格納するもの 格納しないもの
0036 tt_players 表示名(2〜20文字、空欄の場合は「卓球人 #番号」)、レーティング、レーティング対象となった試合数、公開設定(デフォルト非公開) Google名
0036 tt_matches 試合時間、ゲームスコア、ソース(直接入力・得点アシスト・録画分析)、ステータス(確認待ち・確認済み・異議・期限切れ・個人記録)、2人のレーティング変動量 ラリーごとの履歴
0036 tt_practice 練習日誌(本人のみ閲覧) —
0039 tt_live_matches·tt_live_events ライブの試合とイベントの列(番号付き得点・取り消し・終了)。同じマイグレーションで試合共有用の11文字のコードを tt_matches に追加 スコア自体(サーバーがイベントから再計算)、映像
0040 tt_corrections 元の判定・修正された判定・イベントの要約・根拠・同意の有無 映像
0048 tt_videos ユーザーがアップロードした映像の分析キューと結果 — (元ファイルは30日後に削除)

これらのテーブルで注目すべき点は、 「何を格納しないか」が設計の半分であるということです。試合記録はラリーを把握せず、ライブはスコアを保存せずにイベントのみを保存し、修正記録は映像を把握しません。各テーブルは自身の機能に必須なものだけを保持し、履歴画面は新しいテーブルを作成せずにこれらのテーブルを読み込んで集約します(第3節)。格納されていないデータは流出することもありません。

9. まとめ

ティティの一言。 今日の試合はスマホが先に覚えています。サーバーには「何対何だったのか」だけを伝え、名前もニックネームだけで大丈夫です。相手に「その通り」と承認してもらって初めてスコアが上がるため、勝った日はぜひ確認をお願いします。

参考文献

  1. A. E. Elo. The Rating of Chessplayers, Past and Present. Arco, 1978. (Eloレーティングの原典。原文は今回参照せず、当社方式はコード backend/app/tt_records.py で確認)
  2. International Table Tennis Federation. ITTF Statutes 2026, Chapter 2: The Laws of Table Tennis, §2.12(試合とゲーム数). https://db.ittf.com/document
  3. コードと設計: リポジトリ backend/app/tt_records.py·tt_history.py·tt_watch.py·tt_corrections.py·tt_live.py の冒頭、 backend/static/tt/play/cam.js·lib/cam-sheets.mjs·lib/camauto.mjs.

あわせて読みたい

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます