作者はサードパーティ製のガチャ記録ツールでデータを失ったことをきっかけに、自作のガチャ分析ツール「Astrionyx」を開発。フロントエンドにNext.js、バックエンドにGoを採用し、鳴潮・原神・スターレイルなど複数ゲームに対応、データは手動またはAPI経由で取り込める。本記事ではデータ取り込みと更新の実装を解説。タイムスタンプ・ガチャ種別・通し番号から一意のIDを生成し、十連中の同一アイテムを区別する。確率計算は理論値とベイズ平滑化を組み合わせ、少サンプルのばらつきに対処。デプロイはGitHubワークフローとAlibaba Cloudのミラーサービスで自動化している。
原神、スターレイル、ゼンゼロを卒業したあと、最近は鳴潮にどっぷりハマっている(そう、例の「○○は○○さえすればいい、でも○○は考えることがたくさんある」ってやつ)。こういう二次元ゲームの話になると、厄介なガチャは避けて通れない。ガチャがあればキャラも武器も「当たり/ハズレ」が必ず生まれる。そこから「○○小助手」「○○工房」みたいなサードパーティ製ミニアプリが山ほど出てきて、その代表的な機能のひとつがガチャ分析だ。
きっかけは、とある友人が自分のスターレイルのガチャ記録を見たがったこと。ところが、以前使っていたミニアプリを開いてみると、保存していたはずの記録が跡形もなく消えていた。スターレイルクラウドを入れてガチャリンクを再インポートし、データを復元しようとしたものの、散々格闘したものの、ミニアプリは「ガチャURLが正しくありません」としか言ってくれず、結局履歴は取り戻せなかった。その後もずっとこのことが引っかかっていて、それなら自分でガチャ記録の分析ツールを作り、データを自分の手元に置いておけばいいじゃないか——そうして書いたのが、このガチャ分析ツールだ。
Astrionyx は Next.js ベースの Web アプリで、現状はフロントが Next.js、バックエンドが Go のプロジェクト。さまざまな種類のガチャ記録の分析に対応し、データは手動でインポート・更新できるほか、API 経由でも取り込める。鳴潮だけでなく、今では原神、スターレイル、ゼンゼロ、アークナイツ:エンドフィールド、異環といったゲームもついでに対応済みだ。フロントは Vercel / EdgeOne Pages にそのまま置いてホストでき、バックエンドは Docker で自前のサーバーにデプロイする。デプロイはめちゃくちゃ簡単だ。
Astrionyx を作る中で、面白い問題に何度も出くわした。同じようなアプリを自分で書いてみたい人の役に立てばうれしい。
データのインポートと更新
データのインポート
大半のゲームと同じく、鳴潮にはガチャデータをエクスポートする公式 API がない。ガチャ履歴の表示方法はこうだ。「ガチャ履歴」ボタンを押すと、ゲームが一時的なリンクを生成し、ゲーム内ブラウザでそのリンクを開いてガチャ履歴の内容を表示する。この aki-gm-resources.aki-game.com/aki/gacha/index.html で始まるリンクを、パケットキャプチャやログファイルから取得できる。
このページは POST 方式で API gmserver-api.aki-game2.com/gacha/record/query にアクセスし、各ガチャの抽選履歴を取得する。このリクエストにはプレイヤー ID(playerId)、サーバー ID(serverId)、ガチャ ID(gachaId)といった重要情報が含まれていて、いずれも URL のパラメータから取り出せる:
このうちガチャ種別パラメータ cardPoolType の値は [1, 13] で、マッピングは以下のとおり:
あとはバックエンド API をひとつ作って公式 API に転送し、各ガチャの抽選履歴を取得すればいい。
データ更新
鳴潮などのゲームでは、ガチャに「10連」が存在する。
図からわかるように、「10連」では同じアイテムを2つ獲得することがある。つまり2件の記録が、抽選時刻(10連ではすべてのアイテムが同一時刻に生成される)を含めてすべての項目が完全に一致しているわけだ。JSON で取得するとこんな結果になる:
このままだと、ガチャ記録を後から更新するときに、どの記録がすでに取り込み済みなのか判別できず、集計データの正確さに響く。そこで、同じ時刻でも同じアイテム2つを区別できる番号を作る必要がある。
この一意な番号は タイムスタンプ+ガチャ種別 ID+同一時刻内の抽選番号 で決められる。同一時刻内の抽選番号とは、1 から数えて同じタイムスタンプの中で何回目の抽選かを表す。これで上の2つの同じ武器に uniqueId を振ると、それぞれ 1748454456000100001 と 1748454456000100005 になる。
こうしておけば、インポートするデータが既存データと重複していても、新しく追加されたガチャ記録を正しく判別してデータベースを更新できる。
確率データの計算
ページの統計概要では、ECharts を使ってガチャ確率分析の折れ線グラフをコンポーネントの背景として描画し、ガチャシステムの確率分布を示している。折れ線グラフのデータは主に2つの関数で計算する:
理論確率の計算
中国の動画サイト Bilibili の投稿者・一棵平衡树 が書いた 『鳴潮ガチャシステム簡析』という記事 によると、鳴潮のガチャ確率は以下のモデルに従う。ここで
iは抽選回数を表す:P_5[i] = \begin{cases} 0.008 & (i < 66) \\ 0.008 + 0.04 \cdot (i - 65) & (66 \leq i < 71) \\ 0.208 + 0.08 \cdot (i - 70) & (71 \leq i < 76) \\ 0.608 + 0.1 \cdot (i - 75) & (76 \leq i < 79) \\ 1 & (i = 79) \end{cases}これをもとに理論確率関数 calculateTheoreticalProbability を作る:
実測確率の計算
このツールは主に個人利用なので、ガチャデータのサンプル数が少なく、抽選回数の位置によってはデータがまったくないこともある。そこを単純に頻度推定してしまうと、確率曲線が大きくばらつき、0% や 100% といった極端な値が出てしまう。
そんな事態を避けるために、ベイズ平滑化 を導入すると、よりよい結果が得られる。
P_{\text{posterior}} = \frac{k_i + S \cdot P_{\text{prior}}}{n_i + S}ここで、
k_i:i回目の抽選位置で観測された星5の数、n_i:i回目の抽選位置の抽選総数、P_{\text{prior}}:理論確率、S:平滑化因子その挙動は次のとおり:
もし
n_iが小さい(データ量が少ない)ときは、P_{\text{posterior}}は理論確率P_{\text{prior}}もし
n_iが大きければ(データ量が多いとき)、P_{\text{posterior}}実測頻度の\frac{k_i}{n_i}のほうに近づく。最適化後の結果は以下のとおり:
実装コードはこちら:
自動デプロイ
現在、このプロジェクトはワークフロー+Alibaba Cloud コンテナイメージサービスで自動デプロイしている
以下の自動デプロイ方法はすでに陳腐化しているため、記録としてのみ残しておく:
前述のとおり、Astrionyx は Vercel と自前サーバーの両方にデプロイされている。そのためプロジェクトを修正してリポジトリに push するたびに、サーバー上で pull して build する必要があり、これがひどく面倒だった。そこで、このテーマの作者が公開していた「テーマを自動ビルドしてリモートサーバーにデプロイするワークフロー」を少し手直しして、Astrionyx でも使わせてもらった。
ところが使ってみると、GitHub とサーバーの間のネットワーク品質が極端に悪く、ビルド済みのパッケージをサーバーへ転送するだけで毎回かなりの時間がかかった(平均 20 分以上)。ちょうど Cloudflare の R2 オブジェクトストレージには、毎月 10 GB までの無料ストレージと、ダウンロード無料という特典があり、国内からのダウンロード品質も悪くない。「中継地点」として、この問題を解決するのに使える。
オブジェクトストレージを組み合わせた主要なワークフローは以下のとおり。アップロードとダウンロードの両方に5回のリトライを付けた(1回のダウンロードでもネットワークの揺らぎで失敗することがあるが、実際に試したところ5回リトライすればほとんど解決する)
このワークフローにより、main ブランチにコードを push するたびに Astrionyx が自動でビルドされ、サーバーへデプロイされる。さらに R2 オブジェクトストレージを中継地点にすることでデプロイ時間を大幅に短縮できる(ワークフロー全体が 20 分から 4 分以内に短縮)。命がちょっと長くなる。
以上🎉