SPSP — 理論編 1: 概念
基本編で SPSP が「順位評価」「直対評価」「総合評価」の 3 つで成り立っていることは紹介した。
このページでは、そのアルゴリズムが どんな考え方で動いているのか を、数式抜きの言葉だけで説明する。
読み終わると「なぜ強い相手に勝つと大きく上がるのか」「なぜ大会順位ポイントが上位ほど大きく増えるのか」
といった理屈が概念として腑に落ちる状態を目指す。
厳密な検証 (どのパラメータがなぜその値か) は 理論編・評価、
数式そのものは 理論編・数式 で扱う。
1. 直対評価の仕組み (まずこっちから)
直対評価は、よくあるオンライン対戦レーティング (例: スマメイトの 1v1 レート) と 同じ系統の Bradley-Terry レーティング を採用している。 試合 1 つの勝敗が起きるたびに、両プレイヤーのレートをほんの少しずつ更新していく方式だ。
1.1 ベースの考え方: 「期待勝率」と「実結果」のズレを反映
レーティングシステムは、現在の両者のレートから「この対戦カードならプレイヤー A が勝つ確率はだいたい これくらい」という 期待勝率 を算出する。 そして実際の試合が終わったあと、期待勝率と実結果のズレを見て、ズレた分だけレートを動かす。
- 期待通りに強者が勝った → 強者は少しだけ上がる、弱者は少しだけ下がる
- 勝つはずだった強者が負けた (アップセット) → 強者は多めに下がる、勝った弱者は多めに上がる
- 差が小さいカードで強者が勝った → 両者の動きは中くらい
これを直感に言い換えると: 「強い相手に勝てば大きく上がる、格下に勝っても小幅、格下に負けると痛い」。 総当たり戦をしなくても、ランダムに当たった試合を積み重ねるだけでだんだん正しい序列に収束していく。 これがレーティング系の強みである。
SPSP が採用している直対評価は、OpenSkill ライブラリの BradleyTerryFull というモデル。 Elo 系のシンプルな計算をベイズ的に拡張したものと考えてよい。 具体的な式は数式編にある。
1.2 もう一つの軸: 不確実性 (分散)
直対評価は単なる「レート 1 つ」ではなく、「現在のレート推定値」と「その推定がどれくらい不確実か」 という 2 つの値をプレイヤーごとに持っている。後者がいわゆる分散・標準偏差にあたる。 不確実性が大きいプレイヤーほど、1 試合の結果でレートが大きく動く。
- 試合数が少ないレア: 推定が不確実 → 1 試合で大きく動く (どんどん本来位置に向かって駆け上がる/下がる)
- 大ベテラン: 試合を多数こなしているので推定が安定 → 1 試合では少ししか動かない
- 不確実な選手同士の対戦: お互いの不確実性が掛け合わさり、両者ともよく動く
- ベテラン vs レア: レア側は大きく動き、ベテラン側はあまり動かない
2. σ floor — 不確実性を狭めすぎないコツ
そのままの Bradley-Terry を長期間動かすと、ある問題が出てくる: 試合数が増えるほど不確実性が縮んでいき、ベテランは何百試合をしてもレートがほとんど動かなくなる ということ。 これ自体は数学的には正しい (推定が確信に近づくため) のだが、競技シーンの実態とは噛み合わない場面がある。
実際のシーンでは、何年も平凡だった選手が ある日突然強くなる (覚醒) ことがある。 キャラ変、立ち回り改善、研究の成果といった理由で、過去のレートが急に過小評価になるケースだ。 不確実性が小さくなりすぎていると、そうした「最近本当に強くなった」シグナルにシステムが追従できない。
そこで SPSP では、不確実性に下限 (= σ floor) を設ける。 試合をどれだけ重ねても、不確実性はこの下限より下には行かない。 結果として、ベテランでも勝ち続ければそれなりに上昇し、覚醒選手のレートが急速に伸びるようになる。
2.1 不参加期間による不確実性の増加 (inactivity inflation)
σ floor で「下限」は決めたが、長期間試合をしていないプレイヤーには 逆に不確実性を増やす 仕組みも入れている。 1 年以上大会に出ていないプレイヤーは、レートが「過去の実力」のままで止まっていて、復帰時の本当の実力が分からない状態。 これに合わせて σ を膨らませることで、復帰戦の結果がより柔軟にレートへ反映されるようにする。
- 30 日以内: 何もしない (= 通常の σ)
- 1.5 年経過頃: σ が floor (= 3.0) と上限 (= 6.0) のちょうど中間まで上昇
- 2 年以上: σ が上限 (= 6.0) 付近に漸近
増加カーブは sigmoid 曲線で滑らか に作っており、ある日を境にいきなり σ がジャンプすることはない。 また σ を下げる方向には作用しない (= 通常の試合更新で σ が小さくなるのは妨げない)。
3. レア / 常連ゲート
直対評価には、もう 1 つ独自の工夫がある。大会の「種類」によって学習対象を切り替える 仕組みだ。 SPSP はこれを「ゲート (gating)」と呼んでいる。
3.1 仕分けルール
- レア (累計 20 大会以下): すべての大会の試合を学習に使う。 レアはそもそもデータが少ないので、平日大会も含めて全部使い、レートを早く正しい位置に持っていきたい。
- 常連 (累計 20 大会超): 休日大会 (土曜・日曜・祝日) の試合だけを学習に使う。 平日大会・下位クラスイベントの結果はレートを動かさず、無視する。
3.2 なぜ常連は休日のみ?
平日に開催されるスマコミ・スマパといった大会は、強者が「ガチ大会の前の練習」「キャラ調整」「手抜き気味の参加」になりがちで、 本来の実力どおりの結果が出ないことが多い。 こうした試合を常連のレートに反映すると、本気の試合のレートが歪んでしまう。
一方で、休日に開催される大会は、参加者がスケジュールを空けて本気で出ているケースが大半なので、 本来の実力差が結果に出やすい。常連は休日の試合だけを使うことで、レートの精度を上げる という発想。
3.3 「混在」する試合の扱い
レアと常連が平日大会で当たった場合は、片側だけがレートを更新する。 つまりレア側のレートだけ動き、常連側は試合がなかったかのように据え置き。 これにより、平日のノイズで常連の評価が歪むのを防ぎつつ、レアにとっては学習データを増やせる。
3.4 DQ (途中棄権) の扱い
試合に出ずに棄権 (= DQ) となったプレイヤーは、その試合をスコア計算から取り除く。 具体的には、start.gg 側で「dq」フラグが立っている試合は、勝敗もスコアもなかったこととして扱い、 直対評価 (BT) の学習にも、対戦相手の SPR / UF の計算にも使われない。
一方で「この大会から DQ した」という事実だけは保持しておき、 プレイヤーページの大会履歴では SPR の代わりに DQ と表示する。これによって「最終 placement だけ見ると最下位だが、実際には不参加 = 実力評価の対象外」を区別できる。
- 明示的 DQ: matches.json に
dq=Trueの試合があれば、その敗者を DQ 扱い - 暗黙的 DQ (no-show): 試合データが取れている大会で、 参加者 standings に名前があるのに 1 試合もしていない (wins=0 かつ losses=0)、 かつ placement が 3 位以下のプレイヤーは「不参加扱い」として DQ 認定
4. 順位評価の核心アイデア
順位評価は、各大会の「最終順位」をベースにポイントを配るシステム。 ただし単純に「1 位は 10 点」「2 位は 7 点」とテーブルで配るのではなく、 その大会の参加者の強さに応じて配点を動的に決める。
- 順位評価が見るのは 大会参加者の強さ (= 直対評価レート) と自分の最終順位 だけ。 トーナメント中に 誰と当たったか・誰に勝ったか は一切見ない (それは直対評価の仕事)。
- 各順位のもらえるポイントは 大会開始前 (参加者が確定した時点) に決まる。 誰がどこまで勝ち上がるかとは独立。
- その大会で 同じ最終順位なら必ず同じポイントがもらえる。 どんな組み合わせで勝ち上がっても、結果のラウンド数 (= 最終順位) が同じなら配点も同じ。
4.1 順位は「敗者復活で何ラウンド勝ち上がったか」に等しい
SPSP では、大会の順位を 敗者復活トーナメント (ダブルイリミの負け側ブラケット) の構造に当てはめて解釈する。 敗者復活ブラケットは、負けた人が下に集まり、そこからさらにトーナメントを進めていく構造をしている。 優勝者は全ラウンドを勝ち抜けた人、最下位はラウンドゼロで終わった人になる。
- 100 人規模の大会で 1 位 = 全ラウンドを勝ち抜けた
- 3 位 = 残り 2 ラウンドで負けた (= 上位 2 ラウンドぶんを勝ち抜けた)
- 17 位 = 残り 5〜6 ラウンドで負けた (= それ以上のラウンドぶんを勝ち抜けた)
- 最下位 = ラウンド 1 で即敗退
つまり、最終順位を聞けば「どのラウンドまで勝ち上がったか」が機械的に決まる。 ここでは実際にダブルエリミ大会である必要はなく、シングルエリミやスイス大会、リーグでも、 最終順位だけ揃っていれば「敗者復活ブラケットならこのラウンドで敗退したのと等価」と換算できる。
4.2 ラウンドの難しさ = そのラウンドの「場」にいる参加者の平均 gain
次に、各ラウンドの「難しさ」をどう測るか。 SPSP は、大会の参加者をシード番号で並べたうえで 仮想的なダブルイリミの敗者ブラケットを組み、 各ラウンドにどのシードが「居る」かを機械的に決める。そのラウンドに居る参加者の 直対評価レートから計算される gain (= Elo のレート増加量) を平均して、ラウンドの難しさにする。
重要なのは 実際の大会の進行 (誰が勝ち上がったか) は見ていない 点。 あくまで「シードどおりに進めばこのラウンドの場にはこの人たちが居るはず」という 想定だけで全ラウンドの強さを大会開始前に確定させる。
8 人ダブルイリミの敗者ブラケットを模式化すると下のようになる (5 ラウンド構造):
各ラウンドの「場」には、そのラウンドで敗退する人と、そこを勝ち抜けて次のラウンドへ進む人の 両方 が含まれている。 SPSP の「ラウンドの難しさ」は、その 場にいる全員 (= 勝つ人 + 負ける人) の gain を平均することで、 「そのラウンドの場の平均的な強さ」を表現している。
- 序盤ラウンド: 場に含まれるのは下位シード中心 → 平均レート低め → 難しさ低め
- 中盤ラウンド: 中堅シードが集まる → 平均レート中くらい
- 終盤ラウンド: 上位シードだけ残る → 平均レート高め → 難しさ高め
そして「ラウンドの難しさポイント」は、スマメイトと同じ Elo レーティングを使うとして、 その場にいる相手 1 人ずつに勝った時のレート増加量を計算し、それを全員で平均したもの。 つまり 「強い人だらけのラウンドを勝ち抜けた = 1 試合あたりの価値が高い」 という発想がそのままポイントに反映される。
具体的な数値例 (8 人大会で各 mj を実際に計算する流れ) はページ末尾の §8. 具体例 にある。
4.3 大会のポイント = 勝ち上がったラウンドの難しさの合計
あるプレイヤーが大会で「ラウンド k まで勝ち上がった」なら、 そのプレイヤーがその大会で稼ぐポイントは ラウンド 1 から k までの「難しさポイント」の合計。 上位ラウンドほど難しさが大きいので、上位入賞ほど合計が大きく増える設計になっている。
4.4 「最強プレイヤーがその場にいなかった」として計算する
順位評価には、放っておくとどうしても出てしまう偏りがある。それは、各大会の 「最強プレイヤー」が他の参加者より高い点数を稼ぎやすいという傾向。
理由は単純で、ラウンドの難しさを「その場にいる参加者の平均的なレートを使って、その相手から 勝った時にもらえるポイント」で計算しているため。最強プレイヤー自身も「場の参加者」に含まれるので、 自分の高いレートが場の難しさを引き上げ、結果として自分が稼ぐポイントも持ち上げてしまう。 実際の大会で 1 位を取った人ほど、こうした自己強化の影響を受けやすい。
そこで SPSP では、各大会で「場の難しさ」を計算するとき、出場者の中で一番レートが高い人を その場にいなかったものとして扱う。場から最強プレイヤーを 1 人抜いた残りで平均を取り直すことで、 上の自己強化が起きなくなる。 実際の最終順位や、他の人がもらうポイントの仕組みは普通に計算するので、ラウンド構造そのものは変えない。
精度評価でこの調整を入れた結果、順位評価ランキング単体での予測精度 (= シード位置と実際の最終順位のずれ |SPR|) は 1.26 → 1.07 と大幅に改善。実際に優勝した人 (Top1) の予測ズレで見ても 4.92 → 2.92 と約 4 割小さくなった。
5. 5 層カスケード (= 5 つのレベル)
順位評価は 1 回だけでなく 5 回 計算される。 Lv1 から Lv5 までの 5 段階で、それぞれ対象プレイヤーや大会フィルタを少しずつ厳しくしながら再計算する。 プレイヤーは到達した最高 Lv のスコアでランクづけされる。
5.1 なぜ複数レベルにするのか? — 解像度の問題
Elo 系のレート差から計算される「勝った時のレート増加量」は、 両者のレートが大きく離れすぎると 差がほぼ飽和して見えなくなる 性質がある (上位帯にとって格下相手の試合はほぼ 0 ポイント、格上相手の試合はほぼ満額に張り付く)。
§4.2 で説明したとおり、SPSP は 自分側のレートをレベルごとの固定値で評価する。 そのため固定値を一通りにしてしまうと、「中堅層から見たラウンドの難しさ」と 「上位層から見たラウンドの難しさ」のどちらかの解像度が必ず潰れる:
- 固定値を 低めに置くと、中堅層のラウンド難易度はきれいに見えるが、 上位陣どうしの厚みの差はほぼ満額に張り付き、上位帯の細かい違いが見えない。
- 固定値を 高めに置くと、上位帯のラウンド難易度は解像度高く見えるが、 中堅層から見るとどのラウンドもポイントが微小に潰れて、中位帯の差が出ない。
そこで Lv ごとに 母集団 (どこまでの上位を残すか) と固定値の組 を切り替えて再計算し、 各実力帯にとって解像度が最も高い設定を使う。 Lv1 は広い層をカバーする中庸設定、Lv2 以降は上位帯向けに絞ったうえで固定値を厳しく上げる。 これによって、ピラミッドのどの段にいるプレイヤーも「自分の帯から見たちょうど良いものさし」で評価される。
また、強い人ほど「規模の小さい大会」と「一定規模以上の大会」でのパフォーマンス差が大きく出る (= 一定規模以上の大会では本気度・対戦相手の質ともに上がりやすい)。 Lv3 以降は 参加者数や休日開催を条件にした大会フィルタ を追加することで、 上位帯では「本気度の高い大会」だけを使って順位評価を再計算する。 これによって、トップ層の評価が小規模・カジュアル大会の結果に揺さぶられにくくなる。
5.2 カスケードの流れ (ざっくり)
- Lv1: 全プレイヤーを母集団に計算 → 上位 2048 名を抽出
- Lv2: 上位 2048 名を母集団に再計算 → 上位 1024 名を抽出
- Lv3: 上位 1024 名 + 大会フィルタを厳しめに → 上位 512 名を抽出
- Lv4: 上位 512 名 + 大会フィルタ (休日のみ等) → 上位 256 名を抽出
- Lv5: 上位 256 名 + さらに厳しい大会フィルタ
プレイヤーは Lv5 まで残ればその Lv5 スコアでランクづけされ、Lv4 止まりの人は Lv4 スコアで、… という形で「自分が到達した最高 Lv」のスコアを採用する。 Lv5 まで残った Top 256 のあいだでは、最も精度の高いカスケードの結果で順位が決まる仕組みだ。
6. 集計の重みづけ (時間減衰 + 上位 3 大会)
プレイヤーが持つ最終スコアは、出場した 全大会のポイントの合計 である。 ただし、ただ足すのではなく次の 2 つの重みを掛けてから足す。
6.1 時間減衰: 古い大会ほど軽くなる
過去のレートをそのまま現在の評価に持ち込むと、何年も前のピークがいつまでも反映される。 SPSP では、毎月およそ 3 % 減衰するように設計している。 たとえば半年前の大会のポイントは約 83 %、1 年前は約 70 %、2 年前は約 48 % まで縮む。 最新の活動が最も重く、古い実績はだんだん影響を弱める。
6.2 上位 3 大会まではフル評価、それ以降は減衰
プレイヤーの大会ポイントを「貢献の大きい順」にソートし、
- 上位 3 件: そのまま (= 重み 1) で合計に入れる
- 4 件目以降: 1 件下がるごとに 重みが 0.3 倍 になる (4 件目 = 0.3、5 件目 = 0.09、6 件目 = 0.027、…)
たとえばある大会のポイントが「100 ポイント」だったとすると、 その大会が上位 3 件に入っていれば 100 ポイントそのまま、 4 件目だと 30 ポイント、5 件目だと 9 ポイント、6 件目だと約 3 ポイント、と急激に小さくなる。
これによって 「同じ強さのプレイヤーなら、何十大会出てもポイントが青天井に伸び続けることはない」 設計になっている。 本当に効くのは上位 3 大会で、それ以降は「ちょっとずつ補強」程度の意味合い。 短期間に強いパフォーマンスを集中させた人と、薄いパフォーマンスを長期間続けた人で、 前者の方が評価されやすくなる。
7. 総合評価 (順位評価 + 直対評価)
最後に、SPSP の公式順位である「総合評価」。 これは 順位評価のランクと直対評価のランクを単純平均 したもの。 スコアそのものを平均するのではなく、ランク (= 順位) を平均することで、 両方式のスコアのスケール差に依存しない仕組みになっている。
たとえば順位評価で 15 位、直対評価で 9 位のプレイヤーは、 総合の暫定値が (15 + 9) / 2 = 12 となる。 すべてのプレイヤーをこの暫定値でソートし直したものが、最終的な SPSP 順位。
平均順位が並んだ場合のタイブレークは、順位評価と直対評価の Elo 換算スコアの平均を降順で使う。 順位だけだと粒度が粗いので、スコアで細かい差を拾う設計。
8. ちょっとした具体例 (8 人の小さな大会)
架空の 8 人参加大会で、ラウンドポイントから最終スコアまでをステップバイステップで計算してみる。 §4.2 で説明した「場の平均 gain」モデルを実装通りに辿る。
8.1 仮定するレートとパラメータ
各シード位置のプレイヤーの直対評価レートを次のように仮定する:
| シード | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| レート r | 2500 | 2300 | 2100 | 1900 | 1700 | 1500 | 1300 | 1100 |
計算に使うパラメータは Lv1 のものを採用する (Lv1 = 全プレイヤー対象、最も基本的な層):
- base = 1700 — 「自分のレート」として固定で使う基準値 (Lv ごとに異なる固定値)
- floor = 350 — 相手レートの下限。これ未満は底上げ
- ε = 0.01 — 各ラウンドポイントに足す微小値 (零回避項)
ラウンド j の難しさポイント mj の計算式 (Rj = そのラウンドの場に居る参加者シード集合):
m_j = ε + average over i ∈ R_j of g( max(r_i, floor) )
ただし g(r) = 1 − 1 / (1 + 10^((r − base) / 400))
8.2 各ラウンドの「場」のシード集合
8 人ダブルイリミの敗者ブラケットを「強い半分が勝つ」前提でシミュレートすると、各ラウンドに残るシードは次の通り:
| ラウンド | 場にいるシード | 人数 |
|---|---|---|
| Round 1 (LB 初戦) | 5, 6, 7, 8 | 4 |
| Round 2 (WB R2 敗者と合流) | 3, 4, 5, 6 | 4 |
| Round 3 (LB 内部) | 3, 4 | 2 |
| Round 4 (LB Final · WB 決勝敗者と合流) | 2, 3 | 2 |
| Round 5 (Grand Final · WB 優勝者と合流) | 1, 2 | 2 |
各プレイヤーが「何ラウンドぶんを稼ぐか」は 最終順位 (placement_to_tier 経由) から決まる:
| 最終順位 | 稼ぐラウンド数 (rw) | 稼ぐラウンド |
|---|---|---|
| 1 位 | 5 | R1 + R2 + R3 + R4 + R5 |
| 2 位 | 4 | R1 + R2 + R3 + R4 |
| 3 位 | 3 | R1 + R2 + R3 |
| 4 位 | 2 | R1 + R2 |
| 5 位 / 6 位 | 1 | R1 |
| 7 位 / 8 位 | 0 | (なし) |
8.3 ステップ 1: 各シードの gain 値を計算
まず、参加するシード 1〜8 全員について gain 値を計算しておく。 gain 関数 g(r) = 1 − 1 / (1 + 10(r − 1700) / 400) を各シードの相手レートに当てはめる:
| シード | レート r | (r − 1700) / 400 | 10(r−1700)/400 | g(r) |
|---|---|---|---|---|
| 1 | 2500 | +2.00 | 100.000 | 0.9901 |
| 2 | 2300 | +1.50 | 31.623 | 0.9693 |
| 3 | 2100 | +1.00 | 10.000 | 0.9091 |
| 4 | 1900 | +0.50 | 3.162 | 0.7597 |
| 5 | 1700 | 0.00 | 1.000 | 0.5000 |
| 6 | 1500 | −0.50 | 0.316 | 0.2403 |
| 7 | 1300 | −1.00 | 0.100 | 0.0909 |
| 8 | 1100 | −1.50 | 0.032 | 0.0307 |
8.4 ステップ 2: ラウンドポイント mj を計算
各ラウンドの場にいるシードの gain を平均し、ε = 0.01 を足す:
Round 1 — シード [5, 6, 7, 8]:
= 0.01 + 0.8619 / 4
= 0.01 + 0.2155 ≈ 0.2255
Round 2 — シード [3, 4, 5, 6]:
= 0.01 + 0.6023 ≈ 0.6123
Round 3 — シード [3, 4]:
Round 4 (LB Final) — シード [2, 3]:
Round 5 (Grand Final) — シード [1, 2]:
上位ラウンドほど場に強いシードが集まるので、mj はラウンドごとに単調に大きくなっていく。
8.5 ステップ 3: 最終順位ごとに合計
各プレイヤーは「自分の最終順位に応じたラウンド数 (rw)」ぶんの mj を Round 1 から順に足し合わせる:
| 最終順位 | 稼ぐラウンド | 合計ポイント (raw) | サイト表示用 Elo (= raw × 100) |
|---|---|---|---|
| 1 位 | R1 + R2 + R3 + R4 + R5 | 0.2255 + 0.6123 + 0.8444 + 0.9492 + 0.9897 = 3.6211 | 362.1 |
| 2 位 | R1 + R2 + R3 + R4 | 0.2255 + 0.6123 + 0.8444 + 0.9492 = 2.6314 | 263.1 |
| 3 位 | R1 + R2 + R3 | 0.2255 + 0.6123 + 0.8444 = 1.6822 | 168.2 |
| 4 位 | R1 + R2 | 0.2255 + 0.6123 = 0.8378 | 83.8 |
| 5 位 / 6 位 | R1 | 0.2255 | 22.5 |
| 7 位 / 8 位 | (なし) | 0 | 0.0 |
※ サイト上の「順位評価スコア」表示は raw 合計を 100 倍した値 (オフセットなし)。 raw のままだと値が小さすぎて差が見えにくいため、整数 1〜2 桁の桁感に揃えるためのスケーリング。 順位そのものは raw でも × 100 でも変わらない。