Astrionyx - A Wuthering Waves Gacha Analysis Tool and the Details Behind It
·(edited)· / , · reads· likes0Handmade
AI TranslationSimplified ChineseEnglish
Key Insights
After losing data to a third-party gacha tracking tool, the author built Astrionyx, a gacha analytics tool. It uses a Next.js frontend and Go backend, supports multiple games including Wuthering Waves, Genshin Impact, and Honkai: Star Rail, and allows data import manually or via API. The article covers data import and update implementation: unique IDs are generated from timestamps, banner type, and pull sequence number to distinguish identical items within a ten-pull. Probability calculations combine theoretical rates with Bayesian smoothing to handle small-sample fluctuations. Deployment is automated through GitHub workflows and Alibaba Cloud mirror services.
After quitting Genshin Impact, Honkai: Star Rail, and Zenless Zone Zero, I recently got hooked on Wuthering Waves (yes, that "xxx only needs to do xxx, while xxx has a great many things to consider" game). When it comes to this kind of anime-style game, the dreaded gacha is an unavoidable part of the experience. And where there's gacha, there's always the question of whether you win or lose the 50/50 on a character or weapon — which in turn gave rise to a whole crop of third-party mini-apps, the likes of "Assistant" and "Workshop" apps. One of their key features is gacha analysis.
It all started when a friend wanted to check my Honkai: Star Rail gacha records, only to open the mini-app he'd been using and find that all the records he'd saved were gone. He tried downloading Honkai: Star Rail Cloud to re-import the gacha link and restore the data, but after hours of tinkering the mini-app kept reporting an invalid gacha URL, and he never did get the history back. I couldn't stop thinking about it afterward, so I decided to build my own gacha record analysis tool and keep the data in my own hands. That's how this gacha analysis tool came to be.
Astrionyx is a Next.js-based web app, Astrionyx is currently a Next.js frontend with a Go backend. It supports analyzing records from different types of banners, and lets you manually import or update data as well as import via API. Besides Wuthering Waves, it also supports Genshin Impact, Honkai: Star Rail, Zenless Zone Zero, Arknights: Endfield, Neverness to Everness, and other games. The frontend can be dropped straight onto Vercel or EdgeOne Pages for hosting, while the backend is deployed to your own server with Docker — deployment is very, very convenient.
While building Astrionyx, I ran into quite a few interesting problems. If you're thinking of writing an app like this yourself, I hope this helps.
Importing and Updating Data
Importing Data
Like the vast majority of games, Wuthering Waves doesn't offer an official API for exporting gacha data. Here's how it displays gacha history: when you tap the "Gacha History" button, the game generates a temporary link and opens it in the in-game browser to show your gacha history. We can grab this link — the one starting with aki-gm-resources.aki-game.com/aki/gacha/index.html — by capturing network traffic or from the log file.
That page sends a POST request to the API gmserver-api.aki-game2.com/gacha/record/query to fetch the pull history for each banner. The request contains key details — the player ID (playerId), server ID (serverId), and banner ID (gachaId), all of which can be extracted from the parameters in the URL:
Build a backend API that forwards the request to the official API to fetch each banner's gacha history.
Updating Data
In Wuthering Waves and similar games, gacha pulls can be done as ten-pulls.
Ten-pull
As the image shows, a ten-pull can yield two of the same item — that is, two records with completely identical attributes, including the pull timestamp (in a ten-pull, all items are generated at the same instant). The result fetched via JSON looks like this:
This means that when we later update our gacha records, we can't tell which records have already been imported, and that skews the accuracy of our statistics. So we need an ID that can distinguish two identical items even when they share the same timestamp.
We can use timestamp + banner type ID + pull index to define this unique ID. The pull index means: counting from 1, which pull this is at the same timestamp. So the two identical weapons above can be assigned unique IDs of 1748454456000100001 and 1748454456000100005.
That way, even if imported data overlaps with existing data, we can still correctly identify newly added gacha records and update the database.
Calculating Probability Data
In the page's stats overview, I used the ECharts library to render a line chart of gacha probability analysis as the component's background, to show the probability distribution of the gacha system. The line chart's data is computed by two main functions:
Theoretical Probability Calculation
According to A Quick Look at Wuthering Waves' Gacha System by Bilibili creator Yike Pinghengshu, Wuthering Waves' gacha probability follows the model below, where i is the number of pulls:
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}
From this we build a theoretical probability function calculateTheoreticalProbability:
Since the tool is mainly for personal use, the gacha data sample is small, and some pull positions may have no data at all. Estimating directly by frequency would make the probability curve swing wildly, producing extreme values like 0% or 100%.
Frequency estimation
To avoid this, we can introduce Bayesian smoothing for a better result.
P_{\text{posterior}} = \frac{k_i + S \cdot P_{\text{prior}}}{n_i + S}
Where:
k_i: the number of 5-stars observed at the ith pull position, n_i: the total number of pulls at the ith pull position, P_{\text{prior}}: the theoretical probability, S: the smoothing factor
It behaves as follows:
If n_i is small (i.e., when there isn't much data), P_{\text{posterior}} will be closer to the theoretical probability P_{\text{prior}}
If n_i is large (i.e., when the data volume is large), P_{\text{posterior}} will be closer to the empirical frequency \frac{k_i}{n_i}
The project currently uses a workflow plus Alibaba Cloud Container Registry to automate deployment.
The following automatic deployment method is outdated and kept only as a record:
As mentioned earlier, Astrionyx is deployed both on Vercel and on my own server, so every time I made a change and pushed it to the repo, I had to pull and build on the server — a real pain. The author of this theme had written a workflow that automatically builds the theme and deploys it to a remote server, so I adapted it slightly and used it for Astrionyx.
But in practice I found that the network connection between GitHub and the server was very poor, so transferring the built package to the server took a long time every time (20 min+ on average). As luck would have it, Cloudflare's R2 object storage offers10 GB of free storage per month and no download fees, and its download speeds within the mainland are decent too, so it could serve as a "relay station" to solve this problem.
The main workflow with object storage looks like this. Both uploads and downloads are set to retry 5 times (a single download can still fail due to network fluctuations, but in practice 5 retries resolve the vast majority of problems).
# ci-release-build.sh
#!env bash
set -e
CWD=$(pwd)
npm run build
cd .next
pwd
rm -rf cache
cp -r ../public ./standalone/public
cd ./standalone
echo ';process.title = "Astrionyx"' >>server.js
mv server.js server.mjs
mv ../static/ ./.next/static
cp $CWD/ecosystem.standalone.config.cjs ./ecosystem.config.js
cd ..
mkdir -p $CWD/assets
rm -rf $CWD/assets/release.zip
zip --symlinks -r $CWD/assets/release.zip ./*
With this workflow, every time I push code to the main branch, Astrionyx automatically builds and deploys to the server. And using R2 object storage as a relay station dramatically cuts deployment time (the whole workflow went from 20 min to under 4 min) — a real life-saver.
After quitting Genshin Impact, Honkai: Star Rail, and Zenless Zone Zero, I recently got hooked on Wuthering Waves (yes, that "xxx only needs to do xxx, while xxx has a great many things to consider" game). When it comes to this kind of anime-style game, the dreaded gacha is an unavoidable part of the experience. And where there's gacha, there's always the question of whether you win or lose the 50/50 on a character or weapon — which in turn gave rise to a whole crop of third-party mini-apps, the likes of "Assistant" and "Workshop" apps. One of their key features is gacha analysis.
It all started when a friend wanted to check my Honkai: Star Rail gacha records, only to open the mini-app he'd been using and find that all the records he'd saved were gone. He tried downloading Honkai: Star Rail Cloud to re-import the gacha link and restore the data, but after hours of tinkering the mini-app kept reporting an invalid gacha URL, and he never did get the history back. I couldn't stop thinking about it afterward, so I decided to build my own gacha record analysis tool and keep the data in my own hands. That's how this gacha analysis tool came to be.
Astrionyx is a Next.js-based web app, Astrionyx is currently a Next.js frontend with a Go backend. It supports analyzing records from different types of banners, and lets you manually import or update data as well as import via API. Besides Wuthering Waves, it also supports Genshin Impact, Honkai: Star Rail, Zenless Zone Zero, Arknights: Endfield, Neverness to Everness, and other games. The frontend can be dropped straight onto Vercel or EdgeOne Pages for hosting, while the backend is deployed to your own server with Docker — deployment is very, very convenient.
While building Astrionyx, I ran into quite a few interesting problems. If you're thinking of writing an app like this yourself, I hope this helps.
Importing and Updating Data
Importing Data
Like the vast majority of games, Wuthering Waves doesn't offer an official API for exporting gacha data. Here's how it displays gacha history: when you tap the "Gacha History" button, the game generates a temporary link and opens it in the in-game browser to show your gacha history. We can grab this link — the one starting with aki-gm-resources.aki-game.com/aki/gacha/index.html — by capturing network traffic or from the log file.
That page sends a POST request to the API gmserver-api.aki-game2.com/gacha/record/query to fetch the pull history for each banner. The request contains key details — the player ID (playerId), server ID (serverId), and banner ID (gachaId), all of which can be extracted from the parameters in the URL:
Here, the banner type parameter cardPoolType takes the value [1, 13], mapped as follows:
Build a backend API that forwards the request to the official API to fetch each banner's gacha history.
Updating Data
In Wuthering Waves and similar games, gacha pulls can be done as ten-pulls.
As the image shows, a ten-pull can yield two of the same item — that is, two records with completely identical attributes, including the pull timestamp (in a ten-pull, all items are generated at the same instant). The result fetched via JSON looks like this:
This means that when we later update our gacha records, we can't tell which records have already been imported, and that skews the accuracy of our statistics. So we need an ID that can distinguish two identical items even when they share the same timestamp.
We can use timestamp + banner type ID + pull index to define this unique ID. The pull index means: counting from 1, which pull this is at the same timestamp. So the two identical weapons above can be assigned unique IDs of 1748454456000100001 and 1748454456000100005.
That way, even if imported data overlaps with existing data, we can still correctly identify newly added gacha records and update the database.
Calculating Probability Data
In the page's stats overview, I used the ECharts library to render a line chart of gacha probability analysis as the component's background, to show the probability distribution of the gacha system. The line chart's data is computed by two main functions:
Theoretical Probability Calculation
According to A Quick Look at Wuthering Waves' Gacha System by Bilibili creator Yike Pinghengshu, Wuthering Waves' gacha probability follows the model below, where
iis the number of pulls: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}From this we build a theoretical probability function calculateTheoreticalProbability:
Empirical Probability Calculation
Since the tool is mainly for personal use, the gacha data sample is small, and some pull positions may have no data at all. Estimating directly by frequency would make the probability curve swing wildly, producing extreme values like 0% or 100%.
To avoid this, we can introduce Bayesian smoothing for a better result.
P_{\text{posterior}} = \frac{k_i + S \cdot P_{\text{prior}}}{n_i + S}Where:
k_i: the number of 5-stars observed at theith pull position,n_i: the total number of pulls at theith pull position,P_{\text{prior}}: the theoretical probability,S: the smoothing factorIt behaves as follows:
If
n_iis small (i.e., when there isn't much data),P_{\text{posterior}}will be closer to the theoretical probabilityP_{\text{prior}}If
n_iis large (i.e., when the data volume is large),P_{\text{posterior}}will be closer to the empirical frequency\frac{k_i}{n_i}The improved result looks like this:
Here's the implementation code:
Automatic Deployment
The project currently uses a workflow plus Alibaba Cloud Container Registry to automate deployment.
The following automatic deployment method is outdated and kept only as a record:
As mentioned earlier, Astrionyx is deployed both on Vercel and on my own server, so every time I made a change and pushed it to the repo, I had to pull and build on the server — a real pain. The author of this theme had written a workflow that automatically builds the theme and deploys it to a remote server, so I adapted it slightly and used it for Astrionyx.
But in practice I found that the network connection between GitHub and the server was very poor, so transferring the built package to the server took a long time every time (20 min+ on average). As luck would have it, Cloudflare's R2 object storage offers10 GB of free storage per month and no download fees, and its download speeds within the mainland are decent too, so it could serve as a "relay station" to solve this problem.
The main workflow with object storage looks like this. Both uploads and downloads are set to retry 5 times (a single download can still fail due to network fluctuations, but in practice 5 retries resolve the vast majority of problems).
With this workflow, every time I push code to the main branch, Astrionyx automatically builds and deploys to the server. And using R2 object storage as a relay station dramatically cuts deployment time (the whole workflow went from 20 min to under 4 min) — a real life-saver.
That's all🎉.