技術について
このサイトの構成、保存しているもの、そしてどの数値が実測でどれが推算かについて。
レンダリング
Next.js 15 の App Router、React 19、strict モードの TypeScript。スタイルは Tailwind v4 で、パレットは単一のスタイルシートにテーマトークンとして宣言されています。コンポーネントライブラリもチャートライブラリもありません。5 種類のチャートはすべて手書きの SVG なので、サーバーで描画されテーマ変数を継承します — ライトモードが実行時ゼロコストなのもこのためです。
ロケール配下のページはすべて静的生成ではなくリクエストごとに描画されます。これは見落としではなく意図的な選択です。ヘッダーがセッションごとの状態(誰がサインインしているか、そのブックマーク)を表示するため、ロケールレイアウトが dynamic = "force-dynamic" を設定し、サブツリー全体が静的生成をオプトアウトします。ロケール自体は URL にあり、middleware が cookie、次に Accept-Language から交渉して、選ばれたロケールをリクエストヘッダーに写し、ルートレイアウトが <html lang> を設定できるようにします。
ページが公式 API を直接呼ぶことはありません。単一のサービスモジュールを呼び、そこがリクエストごとにライブ API・ローカルキャッシュ・トークン未設定時は決定論的なデモデータのどれを返すかを決め、その判断をデータと一緒に返すので、UI がラベル付けできます。
ローカル索引
永続化は node:sqlite、Node 22 以降に内蔵された SQLite バインディングです。ビルドするネイティブモジュールも、運用するデータベースサーバーも、ORM もありません — データ層全体が 1 つの SQL ファイルと少数の小さな型付きヘルパーです。ファイルは DATABASE_PATH の指す場所(既定 ./data/brawlinsights.db)にあり、WAL ジャーナリング、外部キー有効、5 秒の busy timeout で開かれます。
スキーマは毎回の起動時に CREATE TABLE IF NOT EXISTS で作られます。これは既存テーブルに列を足さないため、新しいフィールドは別ステップが各テーブルを検査して足りない ALTER TABLE を発行することで適用されます。既存インストールを捨てずに新しい列を取り込めるわけです。
公式 API が提供しない側のデータが、ここに保存されます:
- 名前検索を可能にするプレイヤーとクラブのディレクトリ
- ポーリング間の変化から再構成される名前履歴とクラブ履歴
- トレンドチャートの元になる毎時のプレイヤー進捗スナップショット
- アーカイブされたバトルログと、そこから積み上げた日次集計
- 装備中スキン、および観測プロフィールから学習したランク帯ラベル表
- そしてサイト自身が受け持つもの一式 — アカウントと、それに紐づくセッション、ブックマーク、閲覧履歴、アップロードした画像、チャットとダイレクトメッセージ、掲示板の投稿、不具合報告、モデレーション記録。完全な一覧はプライバシーポリシー第 2 節がスキーマから生成しているので、ここでは繰り返しません
公式 API との通信
1 つのモジュールが api.brawlstars.com/v1 をラップします。すべてのレスポンスはエンドポイント別の再検証ウィンドウとキャッシュタグ付きで Next のデータキャッシュにも保持され、プレイヤーとクラブのプロフィールはさらに、短い TTL の間は SQLite のコピーが再利用されてから新しいリクエストが出ます。404 以外の理由で失敗した場合はページをエラーにせず最後の保存コピーを返します。404 はアカウント消滅を意味するので、ページは見つからない旨を報告し、更新キューがそのタグを索引から外します。
| リクエスト | 再検証間隔 |
|---|---|
| プレイヤープロフィール | 120 s |
| バトルログ | 60 s |
| クラブ、クラブメンバー | 300 s |
| ランキング | 900 s |
| イベントローテーション | 600 s |
| キャラ一覧 | 86400 s |
API トークンはリクエスト元のパブリック IP に束縛されるため、403 accessDenied.invalidIp が本番で圧倒的に多い失敗です。これは特別に検出され、汎用エラーとして記録される代わりに、ヘルスエンドポイントに平易なヒント付きで表示されます。
更新キュー
API にはプッシュチャネルも「いつ以降の変更」フィルタもないため、最新に保つ唯一の方法はポーリングです — そして全部を平等にポーリングするのは遅く無駄です。そこで索引済みの全プレイヤーは優先バンドに割り当てられ、レコードはバンドが許す年齢を超えて初めて更新候補になります。候補はバンドの高い順、レコードの古い順に処理されます。
このプロフィールパスでは、バトルログはプロフィールと一緒に hot と warm のバンドでのみ取得されます — コールドなアカウントは前回見て以来プレイしていない可能性が高く、ログは追加の 1 リクエストだからです。クラブは warm しきい値で独自のスイープを持ち、ブックマークされたクラブが先。プレイヤーを更新するとそのクラブも更新されるので、第三のキューなしでクラブページが最新に保たれます。
ただしバトルログはこれらのバンドに縛られません。別枠のハーベスト(収集)パス — 通常は各サイクル最大のシェア — が、各アカウントのログを最後に収集してからの経過時間順に索引全体を巡回し(コールドのアカウントも含む)、HARVEST_INTERVAL(30 分)以内に収集されていない全員のログを取得します。誰にも見られていないアカウントも試合はしていて、その試合がティアリストのサンプルを育てます — コーパスを作るのはプロフィールキューではなくこのパスです。
すべての外向きリクエストは、まず毎秒 BS_API_RPS(既定 8)トークンで補充され容量 BS_API_BURST(既定 16)のバケットからトークンを取ります。バケットは単一の共有オブジェクトなので、サイクルや手動更新がいくつ重なってもレートは守られます。バケットが空だった呼び出し側は、スピンせずに必要な時間だけ正確に待ちます。
サイクルの駆動方法は二つ。ひとつはプロセス内のインターバルで、SYNC_INTERVAL_MS(既定 30 秒)ごとに最大 SYNC_BUDGET リクエスト(既定 100)を使います。通常はおよそ半分がバトルログ収集、約 3 分の 1 がプレイヤープロフィール、残りがクラブという配分で、初回のプロフィール取得を待つ新発見アカウントが 1,000 を超えている間はプロフィール優先に反転します。もうひとつは、バックグラウンドのインターバルを保てないプラットフォームで、外部スケジューラが /api/cron を呼ぶ方式です。重複したサイクルはリクエストレートを倍にせず即座に戻り、起動後の初回はランダムに数秒ずらして再起動の嵐が一斉に API を叩かないようにし、429 や IP 不一致の 403 は閉じた扉を叩き続ける代わりにサイクルを早期に打ち切ります。6 時間ごとの整理パスが、90 日超のアーカイブ済みバトル、120 日超の集計、1 年超のスナップショットを削除します。
プレイヤーやクラブのページの「今すぐ更新」ボタンは、タグごとに 20 秒のクールダウンを持つ別エンドポイント経由ですべての TTL をバイパスします。ボタンを押し続けても増幅器にはなりません。/api/health はライブかデモか、索引サイズ、アーカイブ量、キュー深度、最後のエラーを報告します。
バトルアーカイブ
API はプレイヤーの直近 25 戦しか返さず、履歴エンドポイントはありません。このサイトで 25 戦より前を見られる機能はすべて、そのウィンドウがアーカイブされているから存在します。プロフィール閲覧とキュー更新のたびに新しい分が保存され、キー付けにより同じバトルを二度見ても no-op です。
各バトルはアーカイブと同時に、日・モード・マップ・区分・キャラをキーとする日次集計テーブルへも積まれ、ピック・勝利・引き分け・スタープレイヤー数を数えます。1 バトルにつき 4 行が更新されます — 実際のマップと合成の all マップを、それぞれ 2 回ずつ: 一度は粗い区分(ranked か trophies)、もう一度は正確な帯(ranked:masters、trophies:500-750)の下に。all マップ行がモード全体のティアリストをスキャンではなく単一の索引読み取りにし、帯の行がティアリストのランク帯・トロフィー帯フィルタを飾りではなく本物にします。フレンドバトルと、プレイヤーのキャラを特定できないバトルはスキップされます。
ショーダウンには勝敗フィールドがなく順位だけなので、上位半分は勝利と数えます: ソロは 10 中 5 位以内、デュオは 5 中 3 位以内、それ以外は 3 中 2 位以内。このルールは勝率・ティアリスト・キャラ別内訳で一貫して適用されます。
アーカイブはこのインスタンスが見たアカウントから作られるため、全試合の母集団ではなくサンプルであり、しかも検索されるアカウントに偏ったサンプルです。これは相対的な形よりも絶対数にはるかに大きく影響します。
ティアリストの採点方法
生の勝率で並べると、先週たまたま調子が良かった 40 戦のニッチキャラが頂点に来ます。ピック率で並べるのは人気の言い換えにすぎません。スコアは両者をブレンドし、勝率はどこまで信じられるかに応じて割り引きます。
rows = { b : picks(b) ≥ 200 }
globalWinRate = Σ wins / Σ picks
shrunk = (wins + globalWinRate * 1500) / (picks + 1500)
pickRate = picks / total picks
popularity = log10(1 + pickRate * rows * 3) / log10(4)
score = (shrunk - globalWinRate) * 100 + popularity * 1.81500 は擬似バトル数で表した事前分布です。200 ピックのキャラは全体平均へ大きく引き戻され、30,000 ピックのキャラはほぼ自分の勝率を保ちます。経験ベイズの縮小推定の考え方を、粗くも透明に適用したものです。人気項は対数なので、10 倍ピックされるキャラは 10 倍のスコアではなく控えめなボーナスになります。
ティアは固定しきい値ではなくスコア分布から切り出します。入選したキャラで平均と標準偏差を計算し、各キャラを平均から何標準偏差上か下かで配置します — S は +1.25、A は +0.5、B は −0.25、C は −1、それ未満は D。知っておくべき帰結: ティアは現在のメタに対する相対値なので、メタが平坦でもリストには必ず S ティアがあります。
ティアリストが実データから作られるのは、選択中のウィンドウに 5,000 ピック以上あるときだけです(既定ウィンドウは 7 日)。それ未満ではページは決定論的ジェネレーターにフォールバックし、数百戦から自信ありげなランキングを見せる代わりに、自らデモデータと明示します。
API からそのまま
これらは公式レスポンスから読み取り、そのまま表示しています。API は多くのコミュニティラッパーがモデル化しているより多くを公開しており、他サイトが推定している数値のいくつかがここでは実測です。
- トロフィー、最高トロフィー、経験レベル、3v3/ソロ/デュオ勝利数
- ランク帯インデックスと表示名、ランクスコア、シーズン id
- シーズンおよび全期間の最高ランク帯とスコア
- 総プレステージレベル、フェイムとフェイム段階
- キャラごと: パワー、ランク、トロフィー、プレステージ、現在と最長の連勝
- キャラごと: ガジェット、スターパワー、ギア、ハイパーチャージ、装備中スキン
- クラブ所属、クラブ名簿とメンバーの役職、必要トロフィー
- 国別ランキングとライブのイベントローテーション
- プレイヤーごとの直近 25 戦(モード、マップ、種別、結果、トロフィー変動つき)
- キャラ一覧そのもの。同期スクリプトが生成ファイルへ取り込みます — 最終同期 2026/09/15
ここで導出
これらはいかなる形でも API に存在しません。このインスタンスが観測したものから計算され、表示されるすべての場所でラベル付けされています。
- 名前履歴とクラブ履歴 — 連続するポーリングの差分から構築
- トロフィー・スコア・プレステージ・フェイムの推移 — 毎時スナップショットテーブルから
- ティアリスト、勝率、ピック率、スタープレイヤー率 — バトルアーカイブから
- スキン使用率ランキング — 索引済みプロフィールの装備中スキン欄を集計
- ランク帯ラベル表 — ハードコードではなく観測プロフィールから学習
- 推定プレイ時間 — 勝利数と経験レベルから、およそ ±15%。API が時計を公開しないため
- ランクスコアのタイムライン — ランク戦バトルはスコア変動を持たないため、勝敗を約 ±30 でモデル化してシーズンの形を再構成
- キャラごとのレアリティとロール — API が省く唯一の一覧フィールドで、同梱テーブルを名前で引き、推測せず
unknownのまま残す - 計算機テーブル(強化コスト、トロフィー変動、スタードロップ確率、トロフィーロード)— ゲーム内から転記、スナップショット 2026-09-16
まだモデルの部分
一部のページはまだ実データに裏付けられておらず、推測を黙って見せる代わりに、ページ自身がそう明言しています:
- ランク分布。 ページは、完全に索引された各プロフィールに記録されたランク帯への実クエリに接続済みで、ランク帯を持つ索引済みプロフィールが 200 未満の間だけモデル化したラダーへフォールバックします — それ未満では母集団が小さく偏りすぎて、形がミスリードになるためです。フォールバック時はその旨と現在の件数を明示します。
- サンプルの薄いティアリスト。 アーカイブ済みピックが 5,000 未満のモード・マップ・区分は、わずかな試合で順位付けする代わりにモデル分布へフォールバックします。ページは現在の件数と必要な件数を表示します。
- ピン、バトルカード、プロフィール称号。 これらはいかなる形でも API に存在しないため、対応するランキングページは明示的な注意書き付きのレイアウトデモです。
- デモモード。 API トークン未設定の場合、サイト全体が検索したタグをシードとする決定論的ジェネレーターで動くため、同じタグは常に同じ数値になります。影響を受ける全ページに警告バナーが付きます。
索引が検索結果に何を意味するかは 検索仕様 を参照してください。