mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-05 13:28:08 +03:00
+2
-1
@@ -1,2 +1,3 @@
|
||||
/build
|
||||
.vscode
|
||||
.vscode
|
||||
site
|
||||
@@ -1,6 +1,6 @@
|
||||
# Internal Authentication
|
||||
|
||||
*****
|
||||
These endpoints relate to internal authentication methods. Read the warning below.
|
||||
|
||||
!!! danger
|
||||
This is **NOT** for external use. You should **NEVER**
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
This is documented for completeness' sake.
|
||||
|
||||
*****
|
||||
|
||||
## Login with username and password.
|
||||
|
||||
```POST /api/v1/login```
|
||||
|
||||
@@ -57,7 +57,7 @@ scoreData=<file data>
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
```js
|
||||
{
|
||||
"importType": "file/eamusement-iidx-csv",
|
||||
"idStrings": [
|
||||
@@ -80,6 +80,6 @@ scoreData=<file data>
|
||||
"classDeltas": [],
|
||||
"goalInfo": [],
|
||||
"milestoneInfo": [],
|
||||
"userIntent": true
|
||||
"userIntent": false, // if X-User-Intent was set, this would be true.
|
||||
}
|
||||
```
|
||||
@@ -1,5 +1,8 @@
|
||||
# Status Checks
|
||||
|
||||
These endpoints are generally for programmers checking their code works.
|
||||
They can also be used to check the status of the server.
|
||||
|
||||
*****
|
||||
|
||||
## Check server status.
|
||||
|
||||
@@ -0,0 +1,529 @@
|
||||
# Individual User on Specific Game
|
||||
|
||||
This endpoints are for specific users information on specific game + playtype combinations.
|
||||
|
||||
*****
|
||||
|
||||
## Get information about a user's plays on a game + playtype.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:game/:playtype`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The ID or username of the user to retrieve information from. |
|
||||
| `:game` | URL Parameter | The game to retrieve information from. Must be a supported game. |
|
||||
| `:playtype` | URL Parameter | The playtype to retrieve information for. Must be a supported playtype of the previous game. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `gameStats` | UserGameStatsDocument | The User's GameStats for this game + playtype. |
|
||||
| `firstScore` | ScoreDocument or Null | The user's first score for this game + playtype. This is null if the user has no scores with timestamps. |
|
||||
| `mostRecentScore` | ScoreDocument or Null | The user's most recent score. This is null if the user has no scores with timestamps. |
|
||||
| `totalScores` | integer | The total amount of scores this user has. |
|
||||
| `rankingData` | { ranking: integer, outOf: integer } | The position of this player on the default leaderboards for this game, and how many players it is out of. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
gameStats: {
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
ratings: {
|
||||
ktRating: 15
|
||||
},
|
||||
classes: {
|
||||
dan: 14
|
||||
}
|
||||
},
|
||||
firstScore: null,
|
||||
mostRecentScore: null,
|
||||
totalScores: 5,
|
||||
rankingData: {
|
||||
ranking: 3,
|
||||
outOf: 18
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve a user's goals for this game + playtype.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:game/:playtype/goals`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The ID or username of the user to retrieve information from. |
|
||||
| `:game` | URL Parameter | The game to retrieve information from. Must be a supported game. |
|
||||
| `:playtype` | URL Parameter | The playtype to retrieve information for. Must be a supported playtype of the previous game. |
|
||||
| `unachieved` (Optional) | Presence | If present, only unachieved goals are returned. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `userGoals` | Array<UserGoalDocument> | The array of user-subscriptions to goals this user has. |
|
||||
| `goals` | Array<GoalDocument> | The array of goal documents this user has a subscription to. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP/goals
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
userGoals: [
|
||||
{
|
||||
userID: 1,
|
||||
goalID: "foobar",
|
||||
progress: 4,
|
||||
progressHuman: "CLEAR",
|
||||
outOf: 5,
|
||||
outOfHuman: "HARD CLEAR",
|
||||
// ... so on
|
||||
}
|
||||
],
|
||||
goals: [
|
||||
{
|
||||
goalID: "foobar",
|
||||
title: "Hard Clear 5.1.1. SP ANOTHER",
|
||||
// ... so on
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve a user's milestones for this game + playtype.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:game/:playtype/milestones`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The ID or username of the user to retrieve information from. |
|
||||
| `:game` | URL Parameter | The game to retrieve information from. Must be a supported game. |
|
||||
| `:playtype` | URL Parameter | The playtype to retrieve information for. Must be a supported playtype of the previous game. |
|
||||
| `unachieved` (Optional) | Presence | If present, only unachieved milestones are returned. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `userMilestones` | Array<UserMilestoneDocument> | The array of user-subscriptions to milestones this user has. |
|
||||
| `milestones` | Array<MilestoneDocument> | The array of milestone documents this user has a subscription to. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP/milestones
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
userMilestones: [
|
||||
{
|
||||
userID: 1,
|
||||
milestoneID: "foobar",
|
||||
// ... so on
|
||||
}
|
||||
],
|
||||
milestones: [
|
||||
{
|
||||
milestoneID: "foobar",
|
||||
title: "IIDX SP 9th Dan Milestone",
|
||||
// ... so on
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Search a user's personal bests.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:game/:playtype/pbs`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The ID or username of the user to retrieve information from. |
|
||||
| `:game` | URL Parameter | The game to retrieve information from. Must be a supported game. |
|
||||
| `:playtype` | URL Parameter | The playtype to retrieve information for. Must be a supported playtype of the previous game. |
|
||||
| `search` | String | Limits the returned scores to those where the corresponding song is most similar to this query. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `songs` | Array<SongDocument> | The array of songs this search returned. |
|
||||
| `charts` | Array<ChartDocument> | The array of charts this search returned. |
|
||||
| `pbs` | Array<PBDocument> | The array of personal bests this search returned. This is limited to 30 returns. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP/pbs?search=Verfl
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
pbs: [{
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
score: 123,
|
||||
// ...
|
||||
}
|
||||
}],
|
||||
songs: [{
|
||||
title: "Verflucht",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "HYPER",
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## Get a user's best 100 personal bests.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:game/:playtype/pbs/best`
|
||||
|
||||
This returns the users' best 100 personal bests according
|
||||
to the [Default Rating Algorithm](todo) for this game.
|
||||
|
||||
This is returned in descending sorted order.
|
||||
|
||||
The query parameter `alg` can be used to specify a
|
||||
different rating algorithm to sort under.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The ID or username of the user to retrieve information from. |
|
||||
| `:game` | URL Parameter | The game to retrieve information from. Must be a supported game. |
|
||||
| `:playtype` | URL Parameter | The playtype to retrieve information for. Must be a supported playtype of the previous game. |
|
||||
| `alg` | String | An overriding rating algorithm to use instead of the default. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `songs` | Array<SongDocument> | The array of songs this search returned. |
|
||||
| `charts` | Array<ChartDocument> | The array of charts this search returned. |
|
||||
| `pbs` | Array<PBDocument> | The array of personal bests this search returned. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP/pbs/best?alg=BPI
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
pbs: [{
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
// ...
|
||||
},
|
||||
calculatedData: {
|
||||
ktRating: 15,
|
||||
BPI: 4.4
|
||||
}
|
||||
}, {
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
// ...
|
||||
},
|
||||
calculatedData: {
|
||||
ktRating: 19,
|
||||
BPI: 4.2
|
||||
}
|
||||
}],
|
||||
songs: [{
|
||||
title: "Verflucht",
|
||||
// ...
|
||||
}, {
|
||||
title: "AA",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 14,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Search a user's individual scores.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:game/:playtype/scores`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The ID or username of the user to retrieve information from. |
|
||||
| `:game` | URL Parameter | The game to retrieve information from. Must be a supported game. |
|
||||
| `:playtype` | URL Parameter | The playtype to retrieve information for. Must be a supported playtype of the previous game. |
|
||||
| `search` | String | Limits the returned scores to those where the corresponding song is most similar to this query. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `songs` | Array<SongDocument with __textScore> | The array of songs this search returned. |
|
||||
| `charts` | Array<ChartDocument> | The array of charts this search returned. |
|
||||
| `scores` | Array<ScoreDocument> | The array of scores this search returned. This is limited to 30 returns. |
|
||||
|
||||
!!! info
|
||||
All `songs` returned also have the `__textScore`
|
||||
property. This property describes how close the query
|
||||
was to the actual text, and is mostly internal.
|
||||
|
||||
You can read more into the details of this at [Search Implementation](../../codebase/implementation-details/search.md)
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP/scores?search=Verfl
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
scores: [{
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
score: 123,
|
||||
// ...
|
||||
}
|
||||
}],
|
||||
songs: [{
|
||||
title: "Verflucht",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "HYPER",
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Get a user's most recent 100 scores.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:game/:playtype/scores/recent`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The ID or username of the user to retrieve information from. |
|
||||
| `:game` | URL Parameter | The game to retrieve information from. Must be a supported game. |
|
||||
| `:playtype` | URL Parameter | The playtype to retrieve information for. Must be a supported playtype of the previous game. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `songs` | Array<SongDocument> | The array of songs this search returned. |
|
||||
| `charts` | Array<ChartDocument> | The array of charts this search returned. |
|
||||
| `scores` | Array<ScoreDocument> | The array of scores this search returned. This is limited to 30 returns. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP/scores/recent
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
scores: [{
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
score: 123,
|
||||
// ...
|
||||
}
|
||||
}],
|
||||
songs: [{
|
||||
title: "Verflucht",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "HYPER",
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Search a user's sessions.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:game/:playtype/sessions`
|
||||
|
||||
Searches the names of sessions from a given user. This
|
||||
does not search session descriptions, nor does it search
|
||||
song titles of played songs inside sessions.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The ID or username of the user to retrieve information from. |
|
||||
| `:game` | URL Parameter | The game to retrieve information from. Must be a supported game. |
|
||||
| `:playtype` | URL Parameter | The playtype to retrieve information for. Must be a supported playtype of the previous game. |
|
||||
| `search` | string | The session name to search for. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Array<SessionDocument> | The array of sessions that matched this query. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP/sessions?search=epic%20session
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
name: "My Epic Session!!!",
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
// ...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Get a user's best 100 sessions.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:game/:playtype/sessions/best`
|
||||
|
||||
Retrieves a user's best 100 sessions according to the
|
||||
game + playtypes default algorithm. The algorithm can
|
||||
be overrode with the `alg` query string parameter.
|
||||
|
||||
These are returned in descending order.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The ID or username of the user to retrieve information from. |
|
||||
| `:game` | URL Parameter | The game to retrieve information from. Must be a supported game. |
|
||||
| `:playtype` | URL Parameter | The playtype to retrieve information for. Must be a supported playtype of the previous game. |
|
||||
| `alg` (Optional) | string | The name of the algorithm to use instead of the default. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Array<SessionDocument> | The array of the users best sessions. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP/sessions/best
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
!!! info
|
||||
The default rating algorithm for IIDX:SP is `ktRating`.
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
calculatedData: {
|
||||
ktRating: 14,
|
||||
bpi: 3
|
||||
}
|
||||
// ... more properties
|
||||
},
|
||||
{
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
calculatedData: {
|
||||
ktRating: 13.2,
|
||||
bpi: 4
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,140 @@
|
||||
# Users
|
||||
|
||||
These endpoints are related to users in general.
|
||||
|
||||
*****
|
||||
|
||||
## List Users
|
||||
|
||||
` /api/v1/users`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `online` (Optional) | Presence | If present, this limits the returned users to those that are currently online. |
|
||||
| `username` (Optional) | String | If present, this endpoint works like a search engine for usernames. Users will be returned in their proximity to the original text. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Array<UserDocument> | The array of up to 100 users returned. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[{
|
||||
"userID": 1,
|
||||
"username": "zkldi",
|
||||
// ... continued
|
||||
}]
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve user with ID
|
||||
|
||||
`GET /api/v1/users/:userID`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The user's userID or their username. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | UserDocument | The user this ID/username corresponds to. |
|
||||
|
||||
### Example
|
||||
|
||||
!!! note
|
||||
`zkldi` is the username for the user with userID 1.
|
||||
|
||||
it's also the username of the person writing these
|
||||
docs. Hi!
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi
|
||||
OR
|
||||
GET /api/v1/users/1
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
userID: 1,
|
||||
username: "zkldi",
|
||||
// ... so on
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve user's statistics on all games.
|
||||
|
||||
`GET /api/v1/users/:userID/game-stats`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `:userID` | URL Parameter | The user ID or username to fetch the data of. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Array<UserGameStatsDocument> | The array of User Game Stats this user has. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/zkldi/stats
|
||||
OR
|
||||
GET /api/v1/users/1/stats
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[{
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
ratings: {
|
||||
ktRating: 15
|
||||
},
|
||||
classes: {
|
||||
dan: 14
|
||||
},
|
||||
}, {
|
||||
userID: 1,
|
||||
game: "gitadora",
|
||||
playtype: "Dora",
|
||||
ratings: {
|
||||
skill: 1404
|
||||
},
|
||||
classes: {
|
||||
skillColour: 1
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
!!! info
|
||||
In the event a user has played no games, this will
|
||||
return an empty array.
|
||||
@@ -0,0 +1,149 @@
|
||||
# What is BATCH-MANUAL?
|
||||
|
||||
BATCH-MANUAL is a JSON format that Tachi accepts.
|
||||
This format can be submitted as [a file](../../api/routes/import.md#import-scores-from-a-file)
|
||||
using the `file/batch-manual` [Import Type](../import/import-types.md), or it can be submitted as a
|
||||
[HTTP request body](todo)
|
||||
|
||||
*****
|
||||
|
||||
## Motivation
|
||||
|
||||
Instead of Tachi writing new support for every kind of
|
||||
possible export, and bothering other service providers
|
||||
to write exports, we could write a generic format we accept
|
||||
and then users with a bit of scripting knowledge can import
|
||||
their own scores.
|
||||
|
||||
This has the additional advantage of allowing extremely
|
||||
obscure imports, and reduces the workload on Tachi.
|
||||
|
||||
## Format
|
||||
|
||||
The format is incredibly simple JSON.
|
||||
|
||||
It is comprised of two base keys, `head` and `body`.
|
||||
|
||||
### Head
|
||||
|
||||
The `head` key contains metadata, and looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"game": "iidx",
|
||||
"playtype": "SP",
|
||||
"service": "foobar"
|
||||
}
|
||||
```
|
||||
|
||||
The fields have the following values:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `game` | Any Game Identifier | The game this import is for. |
|
||||
| `playtype` | Any Playtype for the above game. | The playtype this import is for. |
|
||||
| `service` | string | A humanised string to explain where these scores are from. This must be between 2 and 15 characters. |
|
||||
| `version` (Optional) | string | Optionally, you can specify a version of the game this import is for. This should be used when conflicting versions of songs exist, or when this import is rather old. Most of the time, this does not need to be present. |
|
||||
|
||||
### Body
|
||||
|
||||
The body is an array of Batch Manual Scores. An example
|
||||
score is as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"score": 500,
|
||||
"lamp": "HARD CLEAR",
|
||||
"matchType": "songTitle",
|
||||
"identifier": "5.1.1.",
|
||||
"difficulty": "ANOTHER",
|
||||
"timeAchieved": 1624324467489
|
||||
}
|
||||
```
|
||||
|
||||
The properties are described as this:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Number | The score for this, well, score. This should use the default scoring algorithm for this game. |
|
||||
| `lamp` | Lamp | The lamp for this score. This should be one of the lamps as described in the config for your game + playtype. |
|
||||
| `matchType` | "songTitle" \| "ddrSongHash" \| "tachiSongID" \| "bmsChartHash" | This determines how `identifier` will be used to match your scores' chart with Tachi's database of songs and charts. |
|
||||
| `identifier` | string | A string that Tachi uses to identify what chart this is for. How this is used depends on the `matchType`. |
|
||||
| `difficulty` (Conditional) | string | If `matchType` is "tachiSongID" or "songTitle", this field must be present, and describe the difficulty of the chart this score is for. |
|
||||
| `timeAchieved` (Optional) | integer \| null | This is *when* the score was achieved in unix milliseconds. This should be provided if possible, as Tachi uses it for a LOT of features. |
|
||||
| `comment` (Optional) | string \| null | A comment from the user about this score. |
|
||||
| `judgements` (Optional) | Record<Game Judgement, integer> | This should be a record of the judgements for your game + playtype, and the integer indicating how often they occured. |
|
||||
| `hitMeta` (Optional) | See [Game Specific Hit Meta](../documents/score.md#game-specific) | This can be a partial record of various `hitMeta` props for this game. |
|
||||
|
||||
#### Match Type
|
||||
|
||||
There are four match types, and they all use identifier
|
||||
in a different way.
|
||||
|
||||
- songTitle
|
||||
|
||||
As the name implies, this searches for a song who's title
|
||||
resembles `identifier`. **THIS IS NOT FUZZY MATCHING**,
|
||||
and is by far the least reliable way to send scores to
|
||||
Tachi. However, in most scenarios, it will work.
|
||||
|
||||
This match type *necessitates* that `difficulty` be defined
|
||||
and set to a valid difficulty for this game + playtype.
|
||||
|
||||
- tachiSongID
|
||||
|
||||
This uses `identifier` as if it were an integer, to match
|
||||
songs based on the `id` field of a tachi song.
|
||||
|
||||
This match type *necessitates* that `difficulty` be defined
|
||||
and set to a valid difficulty for this game + playtype.
|
||||
|
||||
- bmsChartHash
|
||||
|
||||
As the name implies, this looks for the chart hash BMS
|
||||
uses. This can be either the MD5 hash or the SHA256 hash,
|
||||
both will match.
|
||||
|
||||
This match type can only be used for BMS.
|
||||
|
||||
- ddrSongHash
|
||||
|
||||
This looks for the DDR 'song hash'. This is an identifier
|
||||
used on the e-amusement website, and references a song,
|
||||
not a chart. As such:
|
||||
This match type *necessitates* that `difficulty` be defined
|
||||
and set to a valid difficulty for DDR.
|
||||
|
||||
This match type can only be used for DDR.
|
||||
|
||||
## Example
|
||||
|
||||
A final example of a simple BATCH MANUAL format
|
||||
looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"head": {
|
||||
"game": "iidx",
|
||||
"playtype": "SP",
|
||||
"service": "My Service"
|
||||
},
|
||||
"body": [{
|
||||
"score": 500,
|
||||
"lamp": "HARD CLEAR",
|
||||
"matchType": "songTitle",
|
||||
"identifier": "5.1.1.",
|
||||
"difficulty": "ANOTHER"
|
||||
}, {
|
||||
"score": 123,
|
||||
"lamp": "FAILED",
|
||||
"matchType": "tachiSongID",
|
||||
"identifier": "1",
|
||||
"difficulty": "HYPER",
|
||||
"comment": "This score sucked!",
|
||||
"hitMeta": {
|
||||
"bp": 5
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
@@ -53,6 +53,20 @@ is open sourced under AGPLv3.
|
||||
Contributions to this will be under increased scrutiny as
|
||||
I am trying to keep the codebase well organised and tidy.
|
||||
|
||||
!!! tip
|
||||
If you're setting up a development environment locally,
|
||||
commit `47c981f` converted the codebase from spaces to tabs.
|
||||
This revision is hidden using .git-blame-ignore-revs.
|
||||
|
||||
You can fix it with this command.
|
||||
```
|
||||
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
```
|
||||
|
||||
!!! warning
|
||||
This only works on git 2.23 or greater. Your package manager may not have a version
|
||||
this recent. See [Git Installation for Linux](https://git-scm.com/download/linux).
|
||||
|
||||
### Pull Requests
|
||||
|
||||
You can contribute to `tachi-server` by going to the
|
||||
@@ -115,4 +129,3 @@ Non-Specific bug reports will be closed immediately and marked as invalid.
|
||||
- Documentation issues go in the other repo.
|
||||
|
||||
Documentation issues should go [here](https://github.com/zkldi/tachi-docs) instead.
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Documents
|
||||
|
||||
Tachi's database has specific documents for specific
|
||||
things. This part of the documentation covers the
|
||||
shape of those documents.
|
||||
|
||||
!!! note
|
||||
Quite often, the API exposes these documents as-is,
|
||||
without running a projection step on the fields.
|
||||
|
||||
This means that the API documentation will
|
||||
involve frequent links to this part of the
|
||||
codebase documentation.
|
||||
|
||||
*****
|
||||
|
||||
## Definition
|
||||
|
||||
This part of the documentation will define the shape of the
|
||||
document, and explain fields where necessary.
|
||||
|
||||
Depending on whats easiest, this may just be a raw
|
||||
TypeScript interface definition, or it may be a table.
|
||||
|
||||
## Example
|
||||
|
||||
This part of the documentation will show an example document,
|
||||
with some explainations if necessary.
|
||||
@@ -0,0 +1,331 @@
|
||||
# Score Document
|
||||
|
||||
- Stored in `scores`.
|
||||
|
||||
!!! info
|
||||
The same kind of score document is used for every game.
|
||||
As such, this document is rather complex.
|
||||
|
||||
To make parsing this document easier, we're going to
|
||||
define the "Base Score" document, then define all
|
||||
the game-specific fields and properties that appear.
|
||||
|
||||
*****
|
||||
|
||||
## Definition
|
||||
|
||||
```ts
|
||||
interface PublicUserDocument {
|
||||
service: string;
|
||||
game: "iidx" | "bms" // ...etc;
|
||||
playtype: __GameSpecific;
|
||||
userID: integer;
|
||||
scoreData: {
|
||||
score: number;
|
||||
lamp: __GameSpecific;
|
||||
percent: number;
|
||||
grade: __GameSpecific;
|
||||
lampIndex: integer;
|
||||
gradeIndex: integer;
|
||||
esd: number | null;
|
||||
judgements: __GameSpecific;
|
||||
hitMeta: __GameSpecific;
|
||||
};
|
||||
scoreMeta: __GameSpecific;
|
||||
calculatedData: __GameSpecific;
|
||||
timeAchieved: integer | null;
|
||||
songID: integer;
|
||||
chartID: string;
|
||||
isPrimary: boolean;
|
||||
highlight: boolean;
|
||||
comment: string | null;
|
||||
timeAdded: integer;
|
||||
scoreID: string;
|
||||
importType: ImportTypes | null;
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
All fields marked with __GameSpecific change
|
||||
depending on the game and playtype this score
|
||||
is for. We'll go over all of those in a bit.
|
||||
|
||||
The base score document is structured as follows:
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `game` | This is the game this score is for. |
|
||||
| `service` | This is a humanised string for representing the place this score came from. This is primarily used by [Batch-Manual](../batch-manual/overview.md) formats to declare where the scores are coming from. |
|
||||
| `userID` | The user that got this score. |
|
||||
| `timeAchieved` | This is the time this score was actually achieved. This is **NOT NECESSARILY** the time this score was inserted into the database. If this is not known, it can be set to null. |
|
||||
| `timeAdded` | This is the time the score was added to the Tachi database. This is **NOT NECESSARILY** the time the score was achieved. |
|
||||
| `songID` | The song this score is on. Even though this can be derived from `chartID`, it's kept next to the score for certain query optimisations. You can read on the difference between charts and songs [here](../implementation-details/songs-charts.md). |
|
||||
| `chartID` | The chart this score was achieved on. |
|
||||
| `isPrimary` | Whether this score is was achieved on a "primary" chart or not. You can read more on what a primary chart is [here](../implementation-details/songs-charts.md#isPrimary). |
|
||||
| `highlight` | Whether this individual score was highlighted or not by the user. This is one of the few mutable fields on the score document. |
|
||||
| `comment` | A comment left by the user on this score. If one is not present, it is left as `null`. Comments are capped at 240 characters. |
|
||||
| `scoreID` | A unique identifier for this score. Score IDs are prefixed with `R`. This identifier is derived from the content of the score, and thus can be used to dedupe scores. See [Score IDs](todo). |
|
||||
| `importType` | The import type used to import this score. For more on this, see [Import Types](../import/import-types.md) |
|
||||
|
||||
Now, absolutely none of the above fields contain
|
||||
information about the actual *score* the user got,
|
||||
as in, the numbers they achieved! (Other than the
|
||||
`timeAchieved` property, maybe).
|
||||
|
||||
That is stored in the below sub-documents.
|
||||
|
||||
### `scoreData`
|
||||
|
||||
The below table describes the properties of the `scoreData`
|
||||
sub-document.
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `score` | A number describing the "score" the user got. Depending on the game, this may be bounded between various numbers. |
|
||||
| `lamp` | The lamp the user got. For more information on what a lamp is, see [What are Lamps?](../../user/terminology/lamps.md)
|
||||
| `percent` | The 'percent' the user got. That is, their score scaled to the total amount of score they could have possibly got. There are some oddities with this field.[^1]. |
|
||||
| `grade` | The grade the user got. For most games, this is a set of discrete cutoffs for the score's `percent`.[^2] |
|
||||
| `lampIndex`, `gradeIndex` | While `lamp` and `grade` are both strings, these are the raw enum values for those fields. This can be used for filters (select scores where lampIndex > lamps.HARD_CLEAR), or other query methods. |
|
||||
| `esd` | Some games support ESD. This field contains the ESD for that score. For more information on what ESD is, see [What is ESD?](../../user/stats/esd.md)
|
||||
| `judgements` | A record of Judgement->Integer values. The keys for this property depend on the game the score is for. |
|
||||
| `hitMeta` | 'Meta' information about the user's *hits*. That is, things that aren't *literally* about the score, but are related to how well the user played. This contains properties such as `maxCombo` and `fast` and `slow` counts. All the fields here are optional and nullable. Some games extend this to provide things like `gauge`. |
|
||||
|
||||
### `scoreMeta`
|
||||
|
||||
The counterpart to `scoreData` is `scoreMeta`, which is
|
||||
an entirely game-specific record containing meta information
|
||||
about how the play was achieved.
|
||||
|
||||
!!! warning
|
||||
It's easy to confuse this with `hitMeta`.
|
||||
|
||||
`hitMeta` is for meta information about **HOW**
|
||||
the user performed on the score. Things that
|
||||
aren't literally the score or percent, but are
|
||||
important properties nonetheless.
|
||||
|
||||
`scoreMeta` is for completely meta information
|
||||
about the **PLAY** the user made, such as the
|
||||
mods and options they had on when playing.
|
||||
|
||||
Since `scoreMeta`'s properties are entirely game-specific,
|
||||
they will be covered in more detail in the below section.
|
||||
|
||||
## Example Score
|
||||
|
||||
The below document is an example IIDX - SP score.
|
||||
|
||||
```json
|
||||
{
|
||||
"service": "e-amusement",
|
||||
"comment": null,
|
||||
"game": "iidx",
|
||||
"importType": "file/eamusement-iidx-csv",
|
||||
"scoreMeta": {},
|
||||
"timeAchieved": 1608317520000,
|
||||
"scoreData": {
|
||||
"lampIndex": 7,
|
||||
"gradeIndex": 8,
|
||||
"esd": 10.15625,
|
||||
"score": 1729,
|
||||
"lamp": "FULL COMBO",
|
||||
"hitData": {
|
||||
"pgreat": 821,
|
||||
"great": 87
|
||||
},
|
||||
"hitMeta": {
|
||||
"bp": 0
|
||||
},
|
||||
"percent": 95,
|
||||
"grade": "MAX-"
|
||||
},
|
||||
"highlight": false,
|
||||
"timeAdded": 1623955294917,
|
||||
"userID": 1,
|
||||
"calculatedData": {
|
||||
"BPI": null,
|
||||
"K%": null,
|
||||
"ktRating": 9.110264135494702,
|
||||
"ktLampRating": 8
|
||||
},
|
||||
"songID": 212,
|
||||
"chartID": "efa36c73259409ea6a86c689f5750a2de395143b",
|
||||
"scoreID": "Rcf644b6c07830c0948d3a8b457427c1e449f22bf60a7bbed6c3970171e0fa969",
|
||||
"playtype": "SP",
|
||||
"isPrimary": true
|
||||
}
|
||||
```
|
||||
|
||||
## Game Specific
|
||||
|
||||
Every game has its own specific properties it adds to
|
||||
or changes about the base score document.
|
||||
|
||||
For all games, the following changes are applied:
|
||||
|
||||
| Property | Change |
|
||||
| :: | :: |
|
||||
| `scoreData.lamp` | This field is restricted to only Lamps for that game. For more information, see [Game Enums](../implementation-details/game-configuration.md). |
|
||||
| `scoreData.grade` | This field is restricted to only Grades for that game. For more information, see [Game Enums](../implementation-details/game-configuration.md). |
|
||||
| `scoreData.judgements` | The keys of this field are set to only valid Judgements for that game. For more information, see [Game Judgements](../implementation-details/game-configuration.md).
|
||||
|
||||
!!! info
|
||||
All games implicitly have `fast`, `slow` and `maxCombo`
|
||||
as `integer | null` in their hitMeta.
|
||||
|
||||
!!! warning
|
||||
As mentioned above, all properties inside ScoreMeta
|
||||
and HitMeta are optional.
|
||||
|
||||
### IIDX:SP
|
||||
|
||||
```ts
|
||||
interface HitMeta {
|
||||
bp: integer | null;
|
||||
gauge: number | null;
|
||||
gaugeHistory: (number | null)[] | null;
|
||||
scoreHistory: number[] | null;
|
||||
comboBreak: integer | null;
|
||||
gsm: {
|
||||
EASY: (number | null)[];
|
||||
NORMAL: (number | null)[];
|
||||
HARD: (number | null)[];
|
||||
EX_HARD: (number | null)[];
|
||||
} | null;
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `bp` | The total amount of bads this user got plus the total amount of poors. This is generally used by players as a more continuous form of evaluating lamp ability. |
|
||||
| `gauge` | The gauge the user had at the end of the chart. |
|
||||
| `gaugeHistory` | An array, describing the gauge the user had at each measure in the chart. If the user dies, null is used until the end of the array. |
|
||||
| `scoreHistory` | An array describing the exscore the user had at each measure in the chart. |
|
||||
| `comboBreak` | The amount of times this score broke combo. Only some poors actually cause combo breaks! |
|
||||
| `gsm` | Data for GAUGE_SHIFT_MANEUVER. This is `gaugeHistory` replicated for all possible gauges at once. |
|
||||
|
||||
```ts
|
||||
interface ScoreMeta {
|
||||
random: "NONRAN" | "RANDOM" | "R-RANDOM" | "S-RANDOM" | "MIRROR" | null;
|
||||
assist: "NO ASSIST" | "AUTO SCRATCH" | "LEGACY NOTE" | "FULL ASSIST" | null;
|
||||
range: "NONE" | "SUDDEN+" | "HIDDEN+" | "SUD+ HID+" | "LIFT" | "LIFT SUD+" | null;
|
||||
gauge: "ASSISTED EASY" | "EASY" | "NORMAL" | "HARD" | "EX HARD" | null;
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `random` | The RANDOM option that was used during this score. |
|
||||
| `assist` | The ASSIST option that was used during this score. |
|
||||
| `range` | The RANGE option that was used during this score. |
|
||||
| `gauge` | The GAUGE option that was used during this score. |
|
||||
|
||||
### IIDX:DP
|
||||
|
||||
Same as IIDX:SP, but with the following changes to Hit Meta:
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `random` | Is now an array of [LEFT_HAND_RANDOM, RIGHT_HAND_RANDOM], instead of just the single option.
|
||||
|
||||
### SDVX
|
||||
|
||||
```ts
|
||||
interface HitMeta {
|
||||
gauge: number | null;
|
||||
btnRate: number | null;
|
||||
holdRate: number | null;
|
||||
laserRate: number | null;
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `gauge` | The gauge the user had when the chart finished. |
|
||||
| `btnRate`, `holdRate`, `laserRate` | The values the user had for the "rate" bars. These are not of much interest to anyone. |
|
||||
|
||||
```ts
|
||||
interface ScoreMeta {
|
||||
inSkillAnalyser: boolean | null
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `inSkillAnalyser` | Whether or whether not this score was achieved inside the skill analyser mode. |
|
||||
|
||||
### USC
|
||||
|
||||
```ts
|
||||
interface HitMeta {
|
||||
gauge: number | null;
|
||||
btnRate: number | null;
|
||||
holdRate: number | null;
|
||||
laserRate: number | null;
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `gauge` | The gauge the user had when the chart finished. |
|
||||
| `btnRate`, `holdRate`, `laserRate` | The values the user had for the "rate" bars. These are not of much interest to anyone. |
|
||||
|
||||
```ts
|
||||
interface ScoreMeta {
|
||||
noteMod: "NORMAL" | "MIRROR" | "RANDOM" | "MIR-RAN" | null;
|
||||
gaugeMod: "NORMAL" | "HARD" | null;
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `noteMod` | The NOTE modifier used on this score. |
|
||||
| `gaugeMod` | The gauge modifier used on this score. |
|
||||
|
||||
### BMS:7K
|
||||
|
||||
```ts
|
||||
type BMSJudgePermutations = `${"e" | "l"}${"bd" | "pr" | "gd" | "gr" | "pg"}`;
|
||||
|
||||
type BMSHitMeta = BASE_VALID_HIT_META &
|
||||
{
|
||||
[K in BMSJudgePermutations]: integer;
|
||||
} & {
|
||||
bp: integer | null;
|
||||
gauge: number | null;
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `epg`, `lpg`... | These are the (E)arly and (L)ate judgements used by beatoraja internally. |
|
||||
| `bp` | The total bads + the total poors of this score. |
|
||||
| `gauge` | The gauge the user had at the end of this chart. |
|
||||
|
||||
```ts
|
||||
interface ScoreMeta {
|
||||
random: "NONRAN" | "RANDOM" | "R-RANDOM" | "S-RANDOM" | "MIRROR" | null;
|
||||
inputDevice: "KEYBOARD" | "BM_CONTROLLER" | null;
|
||||
client: "LR2" | "beatoraja" | "lr2oraja";
|
||||
lntype: null | "LN" | "CN";
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `random` | The random option this user selected. |
|
||||
| `inputDevice` | Whether this score was achieved on a keyboard or a beatmania controller. If null, this is not known. |
|
||||
| `client` | What client this score was performed on. Scores achieved on beatoraja have a warning next to them indicating differences in their gauge implementation. |
|
||||
| `lnType` | What type of LNs this user was using. This only applies to beatoraja and lr2oraja, where CNs are an option. |
|
||||
|
||||
### DDR, maimai, MÚSECA, CHUNITHM
|
||||
|
||||
These games have no extended properties on its `hitMeta` object.
|
||||
|
||||
These games also have no properties on its `scoreMeta` object.
|
||||
|
||||
*****
|
||||
|
||||
[^1]: SEGA games, such as maimai and CHUNITHM have percents that go greater than 100%. For this, we just let the field overflow 100. There's no reason it can't! It is still strange for a percent to be over 100, however. In some scenarios, the `score` property for a score may be identical to the `percent` property. This occurs when a games primary score indicator is also their percent property, or when their default `score` property is useless (such as in maimai).
|
||||
|
||||
[^2]: Some games, however, have special grade requirements, and that is why this is a separate field (and not just derived from `percent`.)
|
||||
@@ -0,0 +1,65 @@
|
||||
# User Document
|
||||
|
||||
- Stored in `users`.
|
||||
|
||||
*****
|
||||
|
||||
## Definition
|
||||
|
||||
```ts
|
||||
interface PublicUserDocument {
|
||||
username: string;
|
||||
usernameLowercase: string;
|
||||
id: integer;
|
||||
socialMedia: {
|
||||
discord?: string | null;
|
||||
twitter?: string | null;
|
||||
github?: string | null;
|
||||
steam?: string | null;
|
||||
youtube?: string | null;
|
||||
twitch?: string | null;
|
||||
};
|
||||
lastSeen: integer;
|
||||
about: string;
|
||||
customPfp: boolean;
|
||||
customBanner: boolean;
|
||||
clan: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
| :: | :: |
|
||||
| `username` | This is guaranteed to be unique even across casings. Valid usernames match `/^[a-zA-Z_-][a-zA-Z0-9_-]{2,20}$/`. |
|
||||
| `usernameLowercase` | Lowercased versions of usernames are stored so we can efficiently query whether a username exists. This means we don't have to do case insensitive searches! |
|
||||
| `id` | This is a unique auto-incrementing integer for the user, and is completely immutable. |
|
||||
| `socialMedia` | Contains popular social media sites and a way of referencing that user. |
|
||||
| `lastSeen` | The last time this user made a request to Tachi. |
|
||||
| `about` | This user's about me. This is evaluated as markdown. |
|
||||
| `customPfp`, `customBanner` | Whether this user has a custom profile picture and banner. |
|
||||
| `clan` | Currently unused. This will store a users clan tag if they are in a clan. |
|
||||
|
||||
## Example
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "test_zkldi",
|
||||
"usernameLowercase": "test_zkldi",
|
||||
"id": 1,
|
||||
"socialMedia": {
|
||||
"discord": "test_zkldi#1234",
|
||||
"steam": null
|
||||
},
|
||||
"lastSeen": null,
|
||||
"about": "test_user_not_real",
|
||||
"customPfp": true,
|
||||
"customBanner": true,
|
||||
"clan": null
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
An extension of the user document - `PrivateUserDocument`,
|
||||
is what is actually stored in the database.
|
||||
|
||||
This appends two fields - `email` and `password`,
|
||||
and is not exposed over the API for obvious reasons.
|
||||
@@ -0,0 +1,8 @@
|
||||
# About
|
||||
|
||||
As you'd expect, this section contains implementation details. Although the entire
|
||||
codebase reference is technically about implementation details, this part is
|
||||
for miscellaneous implementation details that wouldn't fit anywhere else.
|
||||
|
||||
*****
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# ESD Implementation
|
||||
|
||||
ESD uses the fact that [A binomial distribution can approximate a normal one](https://en.wikipedia.org/wiki/Central_limit_theorem) in order to derive an estimate for standard deviations.
|
||||
|
||||
*****
|
||||
|
||||
## Method Outline
|
||||
|
||||
Our overview is as follows. We are given a percent and
|
||||
judgement windows for a game.
|
||||
|
||||
From that, we want to return the standard deviation that
|
||||
would result in that percent - given that game's judgement
|
||||
windows.
|
||||
|
||||
### Deriving Percent From Standard Deviation
|
||||
|
||||
We assume that the mean of the players hits is always 0.
|
||||
|
||||
Then, we work backwards. With the knowledge of
|
||||
the judgement windows for a game, we can estimate the
|
||||
percent a standard deviation would typically give.
|
||||
|
||||
We construct a distribution with a mean of 0 and a standard
|
||||
deviation of S, and then see roughly where hits would
|
||||
end up on that distribution.
|
||||
|
||||
We multiply how many hits we'd *expect* to be within a
|
||||
certain judgement window by the value of that judgement.
|
||||
|
||||
So in our scenario of S standard deviation, we would
|
||||
expect X% of hits to be between, say, -16.67 and +16.67 (IIDX's PGREAT window).
|
||||
|
||||
!!! note
|
||||
To calculate that percent we need to use the
|
||||
cumulative distribution function. That is not covered
|
||||
here, but guides are all over the internet.
|
||||
|
||||
We can multiply that percent by the value of a PGREAT (100%).
|
||||
|
||||
Then, we repeat for the great window at 50%, and so on.
|
||||
|
||||
When we've summed all that up, we get an estimate of
|
||||
the percent this standard deviation is worth.
|
||||
|
||||
### Reversing That
|
||||
|
||||
This is good, but this is backwards!
|
||||
|
||||
Turns out, there's no algebraic way to reverse this function!
|
||||
|
||||
So, let's do a little approximating.
|
||||
|
||||
We can start with an ESD of 100, which is halfway between
|
||||
the lowest ESD (0), and the highest (200).
|
||||
|
||||
```ts
|
||||
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
||||
const estimatedPercent = StdDeviationToPercent(judgements, estSD, largestValue);
|
||||
|
||||
if (Math.abs(estimatedPercent - percent) < ACCEPTABLE_ERROR) {
|
||||
return estSD;
|
||||
}
|
||||
|
||||
if (estimatedPercent < percent) {
|
||||
maxSD = estSD;
|
||||
} else {
|
||||
minSD = estSD;
|
||||
}
|
||||
|
||||
if (estSD === (minSD + maxSD) / 2) {
|
||||
// if it isn't moving, just terminate
|
||||
break;
|
||||
}
|
||||
|
||||
estSD = (minSD + maxSD) / 2;
|
||||
}
|
||||
```
|
||||
|
||||
This code is then ran to approximate the standard deviation
|
||||
needed to get a percent *like* the one we were given.
|
||||
|
||||
`ACCEPTABLE_ERROR` is set to `0.001` by default.
|
||||
`MAX_ITERATIONS` is set to `50`.
|
||||
|
||||
With this, we can "intelligently brute force" standard deviations,
|
||||
getting closer to our provided percent until its
|
||||
within 0.001%. Then, we can return the standard deviation
|
||||
we used to get that percent!
|
||||
|
||||
!!! note
|
||||
Performance of this is incredibly fast, while
|
||||
"intelligently brute forcing" isn't ideal, 50
|
||||
iterations are almost never hit, and most ESDs
|
||||
are calculated in about 10 iterations.
|
||||
|
||||
All of this happens in significantly under 1 milisecond,
|
||||
so it is not exactly a significant performance hit.
|
||||
@@ -0,0 +1,895 @@
|
||||
# Game Configuration
|
||||
|
||||
Tachi has two sets of game configurations.
|
||||
|
||||
The first is on the game level, which contains things
|
||||
like the humanised name for the game (i.e. `iidx -> beatmania IIDX`).
|
||||
|
||||
The second is for each individual game + playtype
|
||||
combination, which contains things like the list of
|
||||
lamps for the game.
|
||||
|
||||
!!! help
|
||||
This page is currently unformatted, and is just a reference
|
||||
to the codebase at points.
|
||||
|
||||
It would be really appreciated if someone formatted
|
||||
the configurations for every game! For the time being,
|
||||
the below documentation is just the raw configuration
|
||||
for each game. Please see [Contributing to Tachi](../contributing.md).
|
||||
|
||||
*****
|
||||
|
||||
## Game Configurations
|
||||
|
||||
```ts
|
||||
const GAME_CONFIGS: GameConfigs = {
|
||||
iidx: {
|
||||
defaultPlaytype: "SP",
|
||||
name: "beatmania IIDX",
|
||||
internalName: "iidx",
|
||||
validPlaytypes: ["SP", "DP"],
|
||||
},
|
||||
museca: {
|
||||
defaultPlaytype: "Single",
|
||||
name: "MÚSECA",
|
||||
internalName: "museca",
|
||||
validPlaytypes: ["Single"],
|
||||
},
|
||||
chunithm: {
|
||||
defaultPlaytype: "Single",
|
||||
name: "CHUNITHM",
|
||||
internalName: "chunithm",
|
||||
validPlaytypes: ["Single"],
|
||||
},
|
||||
ddr: {
|
||||
defaultPlaytype: "SP",
|
||||
name: "DDR", // used to be 'Dance Dance Revolution', is now DDR for space reasons.
|
||||
internalName: "ddr",
|
||||
validPlaytypes: ["SP", "DP"],
|
||||
},
|
||||
bms: {
|
||||
defaultPlaytype: "7K",
|
||||
name: "BMS",
|
||||
internalName: "bms",
|
||||
validPlaytypes: ["7K", "14K"],
|
||||
},
|
||||
gitadora: {
|
||||
defaultPlaytype: "Dora",
|
||||
name: "GITADORA",
|
||||
internalName: "gitadora",
|
||||
validPlaytypes: ["Gita", "Dora"],
|
||||
},
|
||||
maimai: {
|
||||
defaultPlaytype: "Single",
|
||||
name: "maimai",
|
||||
internalName: "maimai",
|
||||
validPlaytypes: ["Single"],
|
||||
},
|
||||
sdvx: {
|
||||
defaultPlaytype: "Single",
|
||||
name: "SOUND VOLTEX",
|
||||
internalName: "sdvx",
|
||||
validPlaytypes: ["Single"],
|
||||
},
|
||||
usc: {
|
||||
defaultPlaytype: "Single",
|
||||
name: "unnamed_sdvx_clone",
|
||||
internalName: "usc",
|
||||
validPlaytypes: ["Single"],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Game + Playtype Configurations
|
||||
|
||||
```ts
|
||||
const GAME_PT_CONFIGS: GamePTConfigs = {
|
||||
"iidx:SP": {
|
||||
idString: "iidx:SP",
|
||||
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "ktRating",
|
||||
defaultSessionRatingAlg: "ktRating",
|
||||
defaultProfileRatingAlg: "ktRating",
|
||||
|
||||
difficulties: ["BEGINNER", "NORMAL", "HYPER", "ANOTHER", "LEGGENDARIA"],
|
||||
defaultDifficulty: "ANOTHER",
|
||||
difficultyColours: {
|
||||
BEGINNER: COLOUR_SET.paleGreen,
|
||||
NORMAL: COLOUR_SET.blue,
|
||||
HYPER: COLOUR_SET.orange,
|
||||
ANOTHER: COLOUR_SET.red,
|
||||
LEGGENDARIA: COLOUR_SET.purple,
|
||||
},
|
||||
|
||||
grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"],
|
||||
gradeColours: {
|
||||
F: COLOUR_SET.gray,
|
||||
E: COLOUR_SET.red,
|
||||
D: COLOUR_SET.maroon,
|
||||
C: COLOUR_SET.purple,
|
||||
B: COLOUR_SET.paleBlue,
|
||||
A: COLOUR_SET.green,
|
||||
AA: COLOUR_SET.blue,
|
||||
AAA: COLOUR_SET.gold,
|
||||
"MAX-": COLOUR_SET.teal,
|
||||
MAX: COLOUR_SET.white,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 22.22, 33.33, 44.44, 55.55, 66.66, 77.77, 88.88, 94.44, 100.0],
|
||||
|
||||
lamps: [
|
||||
"NO PLAY",
|
||||
"FAILED",
|
||||
"ASSIST CLEAR",
|
||||
"EASY CLEAR",
|
||||
"CLEAR",
|
||||
"HARD CLEAR",
|
||||
"EX HARD CLEAR",
|
||||
"FULL COMBO",
|
||||
],
|
||||
lampColours: {
|
||||
"NO PLAY": COLOUR_SET.gray,
|
||||
FAILED: COLOUR_SET.red,
|
||||
"ASSIST CLEAR": COLOUR_SET.purple,
|
||||
"EASY CLEAR": COLOUR_SET.green,
|
||||
CLEAR: COLOUR_SET.blue,
|
||||
"HARD CLEAR": COLOUR_SET.orange,
|
||||
"EX HARD CLEAR": COLOUR_SET.gold,
|
||||
"FULL COMBO": COLOUR_SET.teal,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: true,
|
||||
judgementWindows: [
|
||||
{ name: "PGREAT", msBorder: 16.667, value: 2 },
|
||||
{ name: "GREAT", msBorder: 33.333, value: 1 },
|
||||
{ name: "GOOD", msBorder: 116.667, value: 0 },
|
||||
],
|
||||
judgements: ["pgreat", "great", "good", "bad", "poor"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "lamp",
|
||||
},
|
||||
"iidx:DP": {
|
||||
idString: "iidx:DP",
|
||||
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "ktRating",
|
||||
defaultSessionRatingAlg: "ktRating",
|
||||
defaultProfileRatingAlg: "ktRating",
|
||||
|
||||
difficulties: ["NORMAL", "HYPER", "ANOTHER", "LEGGENDARIA"],
|
||||
defaultDifficulty: "ANOTHER",
|
||||
difficultyColours: {
|
||||
NORMAL: COLOUR_SET.blue,
|
||||
HYPER: COLOUR_SET.orange,
|
||||
ANOTHER: COLOUR_SET.red,
|
||||
LEGGENDARIA: COLOUR_SET.purple,
|
||||
},
|
||||
|
||||
grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"],
|
||||
gradeColours: {
|
||||
F: COLOUR_SET.gray,
|
||||
E: COLOUR_SET.red,
|
||||
D: COLOUR_SET.maroon,
|
||||
C: COLOUR_SET.purple,
|
||||
B: COLOUR_SET.paleBlue,
|
||||
A: COLOUR_SET.green,
|
||||
AA: COLOUR_SET.blue,
|
||||
AAA: COLOUR_SET.gold,
|
||||
"MAX-": COLOUR_SET.teal,
|
||||
MAX: COLOUR_SET.white,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 22.22, 33.33, 44.44, 55.55, 66.66, 77.77, 88.88, 94.44, 100.0],
|
||||
|
||||
lamps: [
|
||||
"NO PLAY",
|
||||
"FAILED",
|
||||
"ASSIST CLEAR",
|
||||
"EASY CLEAR",
|
||||
"CLEAR",
|
||||
"HARD CLEAR",
|
||||
"EX HARD CLEAR",
|
||||
"FULL COMBO",
|
||||
],
|
||||
lampColours: {
|
||||
"NO PLAY": COLOUR_SET.gray,
|
||||
FAILED: COLOUR_SET.red,
|
||||
"ASSIST CLEAR": COLOUR_SET.purple,
|
||||
"EASY CLEAR": COLOUR_SET.green,
|
||||
CLEAR: COLOUR_SET.blue,
|
||||
"HARD CLEAR": COLOUR_SET.orange,
|
||||
"EX HARD CLEAR": COLOUR_SET.gold,
|
||||
"FULL COMBO": COLOUR_SET.teal,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: true,
|
||||
judgementWindows: [
|
||||
{ name: "PGREAT", msBorder: 16.667, value: 2 },
|
||||
{ name: "GREAT", msBorder: 33.333, value: 1 },
|
||||
{ name: "GOOD", msBorder: 116.667, value: 0 },
|
||||
],
|
||||
judgements: ["pgreat", "great", "good", "bad", "poor"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "lamp",
|
||||
},
|
||||
"chunithm:Single": {
|
||||
idString: "chunithm:Single",
|
||||
percentMax: 101,
|
||||
|
||||
defaultScoreRatingAlg: "rating",
|
||||
defaultSessionRatingAlg: "naiveRating",
|
||||
defaultProfileRatingAlg: "naiveRating",
|
||||
|
||||
difficulties: ["BASIC", "ADVANCED", "EXPERT", "MASTER", "WORLD'S END"],
|
||||
defaultDifficulty: "MASTER",
|
||||
difficultyColours: {
|
||||
BASIC: COLOUR_SET.blue,
|
||||
ADVANCED: COLOUR_SET.orange,
|
||||
EXPERT: COLOUR_SET.red,
|
||||
MASTER: COLOUR_SET.purple,
|
||||
"WORLD'S END": COLOUR_SET.vibrantYellow,
|
||||
},
|
||||
|
||||
grades: ["D", "C", "B", "BB", "BBB", "A", "AA", "AAA", "S", "SS", "SSS"],
|
||||
gradeColours: {
|
||||
D: COLOUR_SET.red,
|
||||
C: COLOUR_SET.purple,
|
||||
B: COLOUR_SET.paleBlue,
|
||||
BB: COLOUR_SET.blue,
|
||||
BBB: COLOUR_SET.vibrantBlue,
|
||||
A: COLOUR_SET.paleGreen,
|
||||
AA: COLOUR_SET.green,
|
||||
AAA: COLOUR_SET.vibrantGreen,
|
||||
S: COLOUR_SET.vibrantOrange,
|
||||
SS: COLOUR_SET.vibrantYellow,
|
||||
SSS: COLOUR_SET.teal,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 50, 60, 70, 80, 90, 92.5, 95.0, 97.5, 100, 107.5, 101],
|
||||
|
||||
lamps: ["FAILED", "CLEAR", "FULL COMBO", "ALL JUSTICE", "ALL JUSTICE CRITICAL"],
|
||||
lampColours: {
|
||||
FAILED: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.paleGreen,
|
||||
"FULL COMBO": COLOUR_SET.paleBlue,
|
||||
"ALL JUSTICE": COLOUR_SET.gold,
|
||||
"ALL JUSTICE CRITICAL": COLOUR_SET.white,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: false,
|
||||
judgements: ["jcrit", "justice", "attack", "miss"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "grade",
|
||||
},
|
||||
"sdvx:Single": {
|
||||
idString: "sdvx:Single",
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "VF6",
|
||||
defaultSessionRatingAlg: "ProfileVF6",
|
||||
defaultProfileRatingAlg: "VF6",
|
||||
|
||||
difficulties: ["NOV", "ADV", "EXH", "INF", "GRV", "HVN", "VVD", "MXM"],
|
||||
defaultDifficulty: "EXH",
|
||||
difficultyColours: {
|
||||
NOV: COLOUR_SET.purple, // colour set dark purple
|
||||
ADV: COLOUR_SET.vibrantYellow,
|
||||
EXH: COLOUR_SET.red,
|
||||
INF: "TODO", // colour set light pink
|
||||
GRV: COLOUR_SET.orange,
|
||||
HVN: COLOUR_SET.teal,
|
||||
VVD: "TODO", // colour set pink
|
||||
MXM: COLOUR_SET.white,
|
||||
},
|
||||
|
||||
grades: ["D", "C", "B", "A", "A+", "AA", "AA+", "AAA", "AAA+", "S"],
|
||||
gradeColours: {
|
||||
D: COLOUR_SET.gray,
|
||||
C: COLOUR_SET.red,
|
||||
B: COLOUR_SET.maroon,
|
||||
A: COLOUR_SET.paleBlue,
|
||||
"A+": COLOUR_SET.blue,
|
||||
AA: COLOUR_SET.paleGreen,
|
||||
"AA+": COLOUR_SET.green,
|
||||
AAA: COLOUR_SET.gold,
|
||||
"AAA+": COLOUR_SET.vibrantYellow,
|
||||
S: COLOUR_SET.teal,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 70, 80, 87, 90, 93, 95, 97, 98, 99],
|
||||
|
||||
lamps: ["FAILED", "CLEAR", "EXCESSIVE CLEAR", "ULTIMATE CHAIN", "PERFECT ULTIMATE CHAIN"],
|
||||
lampColours: {
|
||||
FAILED: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.green,
|
||||
"EXCESSIVE CLEAR": COLOUR_SET.orange,
|
||||
"ULTIMATE CHAIN": COLOUR_SET.teal,
|
||||
"PERFECT ULTIMATE CHAIN": COLOUR_SET.gold,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: false,
|
||||
judgements: ["critical", "near", "miss"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "grade",
|
||||
},
|
||||
"usc:Single": {
|
||||
idString: "usc:Single",
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "VF6",
|
||||
defaultSessionRatingAlg: "ProfileVF6",
|
||||
defaultProfileRatingAlg: "VF6",
|
||||
|
||||
difficulties: ["NOV", "ADV", "EXH", "INF"],
|
||||
defaultDifficulty: "EXH",
|
||||
difficultyColours: {
|
||||
NOV: COLOUR_SET.purple, // colour set dark purple
|
||||
ADV: COLOUR_SET.vibrantYellow,
|
||||
EXH: COLOUR_SET.red,
|
||||
INF: "TODO", // colour set light pink
|
||||
},
|
||||
|
||||
grades: ["D", "C", "B", "A", "A+", "AA", "AA+", "AAA", "AAA+", "S"],
|
||||
gradeColours: {
|
||||
D: COLOUR_SET.gray,
|
||||
C: COLOUR_SET.red,
|
||||
B: COLOUR_SET.maroon,
|
||||
A: COLOUR_SET.paleBlue,
|
||||
"A+": COLOUR_SET.blue,
|
||||
AA: COLOUR_SET.paleGreen,
|
||||
"AA+": COLOUR_SET.green,
|
||||
AAA: COLOUR_SET.gold,
|
||||
"AAA+": COLOUR_SET.vibrantYellow,
|
||||
S: COLOUR_SET.teal,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 70, 80, 87, 90, 93, 95, 97, 98, 99],
|
||||
|
||||
lamps: ["FAILED", "CLEAR", "EXCESSIVE CLEAR", "ULTIMATE CHAIN", "PERFECT ULTIMATE CHAIN"],
|
||||
lampColours: {
|
||||
FAILED: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.green,
|
||||
"EXCESSIVE CLEAR": COLOUR_SET.orange,
|
||||
"ULTIMATE CHAIN": COLOUR_SET.teal,
|
||||
"PERFECT ULTIMATE CHAIN": COLOUR_SET.gold,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: false,
|
||||
judgements: ["critical", "near", "miss"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "grade",
|
||||
},
|
||||
"museca:Single": {
|
||||
idString: "museca:Single",
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "ktRating",
|
||||
defaultSessionRatingAlg: "ktRating",
|
||||
defaultProfileRatingAlg: "ktRating",
|
||||
|
||||
difficulties: ["Green", "Yellow", "Red"],
|
||||
defaultDifficulty: "Red",
|
||||
difficultyColours: {
|
||||
Green: COLOUR_SET.green,
|
||||
Yellow: COLOUR_SET.vibrantYellow,
|
||||
Red: COLOUR_SET.red,
|
||||
},
|
||||
|
||||
grades: ["没", "拙", "凡", "佳", "良", "優", "秀", "傑", "傑G"],
|
||||
gradeColours: {
|
||||
没: COLOUR_SET.gray,
|
||||
拙: COLOUR_SET.maroon,
|
||||
凡: COLOUR_SET.red,
|
||||
佳: COLOUR_SET.paleGreen,
|
||||
良: COLOUR_SET.paleBlue,
|
||||
優: COLOUR_SET.green,
|
||||
秀: COLOUR_SET.blue,
|
||||
傑: COLOUR_SET.teal,
|
||||
傑G: COLOUR_SET.gold,
|
||||
},
|
||||
clearGrade: "良",
|
||||
gradeBoundaries: [0, 60, 70, 80, 85, 90, 95, 97.5, 100],
|
||||
|
||||
lamps: ["FAILED", "CLEAR", "CONNECT ALL", "PERFECT CONNECT ALL"],
|
||||
lampColours: {
|
||||
FAILED: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.green,
|
||||
"CONNECT ALL": COLOUR_SET.teal,
|
||||
"PERFECT CONNECT ALL": COLOUR_SET.gold,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: true,
|
||||
judgementWindows: [
|
||||
{ name: "CRITICAL", msBorder: 33.333, value: 2 },
|
||||
{ name: "NEAR", msBorder: 66.667, value: 1 },
|
||||
],
|
||||
judgements: ["critical", "near", "miss"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "grade",
|
||||
},
|
||||
"bms:7K": {
|
||||
idString: "bms:7K",
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "sieglinde",
|
||||
defaultSessionRatingAlg: "sieglinde",
|
||||
defaultProfileRatingAlg: "sieglinde",
|
||||
|
||||
difficulties: ["CHART"],
|
||||
defaultDifficulty: "CHART",
|
||||
difficultyColours: {
|
||||
CHART: null,
|
||||
},
|
||||
|
||||
grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"],
|
||||
gradeColours: {
|
||||
F: COLOUR_SET.gray,
|
||||
E: COLOUR_SET.red,
|
||||
D: COLOUR_SET.maroon,
|
||||
C: COLOUR_SET.purple,
|
||||
B: COLOUR_SET.paleBlue,
|
||||
A: COLOUR_SET.green,
|
||||
AA: COLOUR_SET.blue,
|
||||
AAA: COLOUR_SET.gold,
|
||||
"MAX-": COLOUR_SET.teal,
|
||||
MAX: COLOUR_SET.white,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 22.22, 33.33, 44.44, 55.55, 66.66, 77.77, 88.88, 94.44, 100.0],
|
||||
|
||||
lamps: [
|
||||
"NO PLAY",
|
||||
"FAILED",
|
||||
"ASSIST CLEAR",
|
||||
"EASY CLEAR",
|
||||
"CLEAR",
|
||||
"HARD CLEAR",
|
||||
"EX HARD CLEAR",
|
||||
"FULL COMBO",
|
||||
],
|
||||
lampColours: {
|
||||
"NO PLAY": COLOUR_SET.gray,
|
||||
FAILED: COLOUR_SET.red,
|
||||
"ASSIST CLEAR": COLOUR_SET.purple,
|
||||
"EASY CLEAR": COLOUR_SET.green,
|
||||
CLEAR: COLOUR_SET.blue,
|
||||
"HARD CLEAR": COLOUR_SET.orange,
|
||||
"EX HARD CLEAR": COLOUR_SET.gold,
|
||||
"FULL COMBO": COLOUR_SET.teal,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: false,
|
||||
judgements: ["pgreat", "great", "good", "bad", "poor"],
|
||||
|
||||
defaultTable: "Insane",
|
||||
|
||||
scoreBucket: "lamp",
|
||||
},
|
||||
"bms:14K": {
|
||||
idString: "bms:14K",
|
||||
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "sieglinde",
|
||||
defaultSessionRatingAlg: "sieglinde",
|
||||
defaultProfileRatingAlg: "sieglinde",
|
||||
|
||||
difficulties: ["CHART"],
|
||||
defaultDifficulty: "CHART",
|
||||
difficultyColours: {
|
||||
CHART: null,
|
||||
},
|
||||
|
||||
grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"],
|
||||
gradeColours: {
|
||||
F: COLOUR_SET.gray,
|
||||
E: COLOUR_SET.red,
|
||||
D: COLOUR_SET.maroon,
|
||||
C: COLOUR_SET.purple,
|
||||
B: COLOUR_SET.paleBlue,
|
||||
A: COLOUR_SET.green,
|
||||
AA: COLOUR_SET.blue,
|
||||
AAA: COLOUR_SET.gold,
|
||||
"MAX-": COLOUR_SET.teal,
|
||||
MAX: COLOUR_SET.white,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 22.22, 33.33, 44.44, 55.55, 66.66, 77.77, 88.88, 94.44, 100.0],
|
||||
|
||||
lamps: [
|
||||
"NO PLAY",
|
||||
"FAILED",
|
||||
"ASSIST CLEAR",
|
||||
"EASY CLEAR",
|
||||
"CLEAR",
|
||||
"HARD CLEAR",
|
||||
"EX HARD CLEAR",
|
||||
"FULL COMBO",
|
||||
],
|
||||
lampColours: {
|
||||
"NO PLAY": COLOUR_SET.gray,
|
||||
FAILED: COLOUR_SET.red,
|
||||
"ASSIST CLEAR": COLOUR_SET.purple,
|
||||
"EASY CLEAR": COLOUR_SET.green,
|
||||
CLEAR: COLOUR_SET.blue,
|
||||
"HARD CLEAR": COLOUR_SET.orange,
|
||||
"EX HARD CLEAR": COLOUR_SET.gold,
|
||||
"FULL COMBO": COLOUR_SET.teal,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: false,
|
||||
judgements: ["pgreat", "great", "good", "bad", "poor"],
|
||||
|
||||
defaultTable: "Insane",
|
||||
|
||||
scoreBucket: "lamp",
|
||||
},
|
||||
"ddr:SP": {
|
||||
idString: "ddr:SP",
|
||||
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "ktRating",
|
||||
defaultSessionRatingAlg: "ktRating",
|
||||
defaultProfileRatingAlg: "ktRating",
|
||||
|
||||
difficulties: ["BEGINNER", "BASIC", "DIFFICULT", "EXPERT", "CHALLENGE"],
|
||||
defaultDifficulty: "EXPERT",
|
||||
difficultyColours: {
|
||||
BEGINNER: COLOUR_SET.paleBlue,
|
||||
BASIC: COLOUR_SET.orange,
|
||||
DIFFICULT: COLOUR_SET.red,
|
||||
EXPERT: COLOUR_SET.green,
|
||||
CHALLENGE: COLOUR_SET.purple,
|
||||
},
|
||||
|
||||
grades: [
|
||||
"D",
|
||||
"D+",
|
||||
"C-",
|
||||
"C",
|
||||
"C+",
|
||||
"B-",
|
||||
"B",
|
||||
"B+",
|
||||
"A-",
|
||||
"A",
|
||||
"A+",
|
||||
"AA-",
|
||||
"AA",
|
||||
"AA+",
|
||||
"AAA",
|
||||
],
|
||||
gradeColours: {
|
||||
D: COLOUR_SET.gray,
|
||||
"D+": COLOUR_SET.maroon,
|
||||
"C-": COLOUR_SET.red,
|
||||
C: COLOUR_SET.purple,
|
||||
"C+": COLOUR_SET.vibrantPurple,
|
||||
"B-": COLOUR_SET.paleBlue,
|
||||
B: COLOUR_SET.blue,
|
||||
"B+": COLOUR_SET.vibrantBlue,
|
||||
"A-": COLOUR_SET.paleGreen,
|
||||
A: COLOUR_SET.green,
|
||||
"A+": COLOUR_SET.vibrantGreen,
|
||||
"AA-": COLOUR_SET.paleOrange,
|
||||
AA: COLOUR_SET.orange,
|
||||
"AA+": COLOUR_SET.vibrantOrange,
|
||||
AAA: COLOUR_SET.gold,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 55, 59, 60, 65, 69, 70, 75, 79, 80, 85, 89, 90, 95, 99],
|
||||
|
||||
lamps: [
|
||||
"FAILED",
|
||||
"CLEAR",
|
||||
"LIFE4",
|
||||
"FULL COMBO",
|
||||
"GREAT FULL COMBO",
|
||||
"PERFECT FULL COMBO",
|
||||
"MARVELOUS FULL COMBO",
|
||||
],
|
||||
lampColours: {
|
||||
FAILED: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.paleGreen,
|
||||
LIFE4: COLOUR_SET.orange,
|
||||
"FULL COMBO": COLOUR_SET.paleBlue,
|
||||
"GREAT FULL COMBO": COLOUR_SET.green,
|
||||
"PERFECT FULL COMBO": COLOUR_SET.gold,
|
||||
"MARVELOUS FULL COMBO": COLOUR_SET.teal,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: true,
|
||||
judgementWindows: [
|
||||
{ name: "MARVELOUS", msBorder: 15, value: 3 },
|
||||
{ name: "PERFECT", msBorder: 30, value: 2 },
|
||||
{ name: "GREAT", msBorder: 59, value: 1 },
|
||||
{ name: "GOOD", msBorder: 89, value: 0 },
|
||||
{ name: "BAD", msBorder: 119, value: 0 },
|
||||
],
|
||||
judgements: ["marvelous", "perfect", "great", "good", "boo", "miss", "ok", "ng"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "lamp",
|
||||
},
|
||||
"ddr:DP": {
|
||||
idString: "ddr:DP",
|
||||
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "ktRating",
|
||||
defaultSessionRatingAlg: "ktRating",
|
||||
defaultProfileRatingAlg: "ktRating",
|
||||
|
||||
difficulties: ["BASIC", "DIFFICULT", "EXPERT", "CHALLENGE"],
|
||||
defaultDifficulty: "EXPERT",
|
||||
difficultyColours: {
|
||||
BASIC: COLOUR_SET.orange,
|
||||
DIFFICULT: COLOUR_SET.red,
|
||||
EXPERT: COLOUR_SET.green,
|
||||
CHALLENGE: COLOUR_SET.purple,
|
||||
},
|
||||
|
||||
grades: [
|
||||
"D",
|
||||
"D+",
|
||||
"C-",
|
||||
"C",
|
||||
"C+",
|
||||
"B-",
|
||||
"B",
|
||||
"B+",
|
||||
"A-",
|
||||
"A",
|
||||
"A+",
|
||||
"AA-",
|
||||
"AA",
|
||||
"AA+",
|
||||
"AAA",
|
||||
],
|
||||
gradeColours: {
|
||||
D: COLOUR_SET.gray,
|
||||
"D+": COLOUR_SET.maroon,
|
||||
"C-": COLOUR_SET.red,
|
||||
C: COLOUR_SET.purple,
|
||||
"C+": COLOUR_SET.vibrantPurple,
|
||||
"B-": COLOUR_SET.paleBlue,
|
||||
B: COLOUR_SET.blue,
|
||||
"B+": COLOUR_SET.vibrantBlue,
|
||||
"A-": COLOUR_SET.paleGreen,
|
||||
A: COLOUR_SET.green,
|
||||
"A+": COLOUR_SET.vibrantGreen,
|
||||
"AA-": COLOUR_SET.paleOrange,
|
||||
AA: COLOUR_SET.orange,
|
||||
"AA+": COLOUR_SET.vibrantOrange,
|
||||
AAA: COLOUR_SET.gold,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 55, 59, 60, 65, 69, 70, 75, 79, 80, 85, 89, 90, 95, 99],
|
||||
|
||||
lamps: [
|
||||
"FAILED",
|
||||
"CLEAR",
|
||||
"LIFE4",
|
||||
"FULL COMBO",
|
||||
"GREAT FULL COMBO",
|
||||
"PERFECT FULL COMBO",
|
||||
"MARVELOUS FULL COMBO",
|
||||
],
|
||||
lampColours: {
|
||||
FAILED: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.paleGreen,
|
||||
LIFE4: COLOUR_SET.orange,
|
||||
"FULL COMBO": COLOUR_SET.paleBlue,
|
||||
"GREAT FULL COMBO": COLOUR_SET.green,
|
||||
"PERFECT FULL COMBO": COLOUR_SET.gold,
|
||||
"MARVELOUS FULL COMBO": COLOUR_SET.teal,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: true,
|
||||
judgementWindows: [
|
||||
{ name: "MARVELOUS", msBorder: 15, value: 3 },
|
||||
{ name: "PERFECT", msBorder: 30, value: 2 },
|
||||
{ name: "GREAT", msBorder: 59, value: 1 },
|
||||
{ name: "GOOD", msBorder: 89, value: 0 },
|
||||
{ name: "BAD", msBorder: 119, value: 0 },
|
||||
],
|
||||
judgements: ["marvelous", "perfect", "great", "good", "boo", "miss", "ok", "ng"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "lamp",
|
||||
},
|
||||
"maimai:Single": {
|
||||
idString: "maimai:Single",
|
||||
|
||||
percentMax: 120, // a safe estimate?
|
||||
|
||||
defaultScoreRatingAlg: "ktRating",
|
||||
defaultSessionRatingAlg: "ktRating",
|
||||
defaultProfileRatingAlg: "ktRating",
|
||||
|
||||
difficulties: ["Easy", "Basic", "Advanced", "Expert", "Master", "Re:Master"],
|
||||
defaultDifficulty: "Master",
|
||||
difficultyColours: {
|
||||
Easy: COLOUR_SET.blue,
|
||||
Basic: COLOUR_SET.green,
|
||||
Advanced: COLOUR_SET.orange,
|
||||
Expert: COLOUR_SET.red,
|
||||
Master: COLOUR_SET.purple,
|
||||
"Re:Master": COLOUR_SET.white,
|
||||
},
|
||||
|
||||
grades: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "S", "S+", "SS", "SS+", "SSS", "SSS+"],
|
||||
gradeColours: {
|
||||
F: COLOUR_SET.gray,
|
||||
E: COLOUR_SET.red,
|
||||
D: COLOUR_SET.maroon,
|
||||
C: COLOUR_SET.purple,
|
||||
B: COLOUR_SET.paleGreen,
|
||||
A: COLOUR_SET.green,
|
||||
AA: COLOUR_SET.paleBlue,
|
||||
AAA: COLOUR_SET.blue,
|
||||
S: COLOUR_SET.gold,
|
||||
"S+": COLOUR_SET.vibrantYellow,
|
||||
SS: COLOUR_SET.paleOrange,
|
||||
"SS+": COLOUR_SET.orange,
|
||||
SSS: COLOUR_SET.teal,
|
||||
"SSS+": COLOUR_SET.white,
|
||||
},
|
||||
clearGrade: "A",
|
||||
// @hack Maimai's top grade depends on the chart's maximum percent
|
||||
// we just set it at percentMax, but it's not technically correct
|
||||
gradeBoundaries: [0, 10, 20, 40, 60, 80, 90, 94, 97, 98, 99, 99.5, 100, 120],
|
||||
|
||||
lamps: ["FAILED", "CLEAR", "FULL COMBO", "ALL PERFECT", "ALL PERFECT+"],
|
||||
lampColours: {
|
||||
FAILED: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.green,
|
||||
"FULL COMBO": COLOUR_SET.blue,
|
||||
"ALL PERFECT": COLOUR_SET.gold,
|
||||
"ALL PERFECT+": COLOUR_SET.teal,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: false,
|
||||
judgements: ["perfect", "great", "good", "miss"],
|
||||
|
||||
defaultTable: "Levels",
|
||||
|
||||
scoreBucket: "grade",
|
||||
},
|
||||
"gitadora:Gita": {
|
||||
idString: "gitadora:Gita",
|
||||
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "skill",
|
||||
defaultSessionRatingAlg: "skill",
|
||||
defaultProfileRatingAlg: "skill",
|
||||
|
||||
difficulties: [
|
||||
"BASIC",
|
||||
"ADVANCED",
|
||||
"EXTREME",
|
||||
"MASTER",
|
||||
"BASS BASIC",
|
||||
"BASS ADVANCED",
|
||||
"BASS EXTREME",
|
||||
"BASS MASTER",
|
||||
],
|
||||
defaultDifficulty: "EXTREME",
|
||||
difficultyColours: {
|
||||
BASIC: COLOUR_SET.blue,
|
||||
ADVANCED: COLOUR_SET.orange,
|
||||
EXTREME: COLOUR_SET.red,
|
||||
MASTER: COLOUR_SET.purple,
|
||||
"BASS BASIC": COLOUR_SET.vibrantBlue,
|
||||
"BASS ADVANCED": COLOUR_SET.vibrantOrange,
|
||||
"BASS EXTREME": "todo", // colourset vibrant red
|
||||
"BASS MASTER": COLOUR_SET.vibrantPurple,
|
||||
},
|
||||
|
||||
grades: ["C", "B", "A", "S", "SS", "MAX"],
|
||||
gradeColours: {
|
||||
C: COLOUR_SET.purple,
|
||||
B: COLOUR_SET.blue,
|
||||
A: COLOUR_SET.green,
|
||||
S: COLOUR_SET.orange,
|
||||
SS: COLOUR_SET.gold,
|
||||
MAX: COLOUR_SET.white,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 63, 73, 80, 95, 100],
|
||||
|
||||
lamps: ["FAILED", "CLEAR", "FULL COMBO", "EXCELLENT"],
|
||||
lampColours: {
|
||||
FAILED: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.blue,
|
||||
"FULL COMBO": COLOUR_SET.teal,
|
||||
EXCELLENT: COLOUR_SET.gold,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: false,
|
||||
judgements: ["perfect", "great", "good", "ok", "miss"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "grade",
|
||||
},
|
||||
"gitadora:Dora": {
|
||||
idString: "gitadora:Dora",
|
||||
|
||||
percentMax: 100,
|
||||
|
||||
defaultScoreRatingAlg: "skill",
|
||||
defaultSessionRatingAlg: "skill",
|
||||
defaultProfileRatingAlg: "skill",
|
||||
|
||||
difficulties: ["BASIC", "ADVANCED", "EXTREME", "MASTER"],
|
||||
defaultDifficulty: "EXTREME",
|
||||
difficultyColours: {
|
||||
BASIC: COLOUR_SET.blue,
|
||||
ADVANCED: COLOUR_SET.orange,
|
||||
EXTREME: COLOUR_SET.red,
|
||||
MASTER: COLOUR_SET.purple,
|
||||
},
|
||||
|
||||
grades: ["C", "B", "A", "S", "SS", "MAX"],
|
||||
gradeColours: {
|
||||
C: COLOUR_SET.purple,
|
||||
B: COLOUR_SET.blue,
|
||||
A: COLOUR_SET.green,
|
||||
S: COLOUR_SET.orange,
|
||||
SS: COLOUR_SET.gold,
|
||||
MAX: COLOUR_SET.white,
|
||||
},
|
||||
clearGrade: "A",
|
||||
gradeBoundaries: [0, 63, 73, 80, 95, 100],
|
||||
|
||||
lamps: ["FAILED", "CLEAR", "FULL COMBO", "EXCELLENT"],
|
||||
lampColours: {
|
||||
FAILED: COLOUR_SET.red,
|
||||
CLEAR: COLOUR_SET.blue,
|
||||
"FULL COMBO": COLOUR_SET.teal,
|
||||
EXCELLENT: COLOUR_SET.gold,
|
||||
},
|
||||
clearLamp: "CLEAR",
|
||||
|
||||
supportsESD: false,
|
||||
judgements: ["perfect", "great", "good", "ok", "miss"],
|
||||
|
||||
defaultTable: "Levels (N-1)",
|
||||
|
||||
scoreBucket: "grade",
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,26 @@
|
||||
# Search Implementation
|
||||
|
||||
Tachi's search implementation uses MongoDB's $text index. This breaks a query into words
|
||||
and compares each of them to the provided text fields.
|
||||
|
||||
*****
|
||||
|
||||
## __textScore
|
||||
|
||||
For our code, we mutate the documents we want to return with a special field: `__textScore`.
|
||||
|
||||
This field declares how 'close' the provided query was to the $text fields in this document.
|
||||
|
||||
This is sometimes exposed in the API for sorting reasons.
|
||||
|
||||
!!! bug
|
||||
MongoDB's $text matching algorithm isn't great for fuzzy matches - It doesn't
|
||||
like song titles like 'A', as it thinks 'a' is an article, and doesn't match it properly as
|
||||
a word.
|
||||
|
||||
!!! info
|
||||
Why not regex for fuzzy matches?
|
||||
|
||||
Regex has performance issues on larger datasets and we
|
||||
want to avoid it. Most regexes cannot use indexes, and therefore invoke a COLLSCAN, which
|
||||
we want to avoid.
|
||||
@@ -0,0 +1,137 @@
|
||||
# Songs And Charts
|
||||
|
||||
Tachi structures its song and chart data in a specific
|
||||
manner in order to avoid copying properties all over
|
||||
the place.
|
||||
|
||||
*****
|
||||
|
||||
## Songs
|
||||
|
||||
Songs look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "5.1.1.",
|
||||
"artist": "dj nagureo",
|
||||
"id": 1,
|
||||
"firstVersion": "0",
|
||||
"alt-titles": [],
|
||||
"search-titles": [],
|
||||
"data": {
|
||||
"genre": "PIANO AMBIENT"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
They're relatively small documents, and contain some metadata.
|
||||
|
||||
Depending on the game, the `data` prop will contain
|
||||
game-specific properties (i.e. not all games have
|
||||
song genres!)
|
||||
|
||||
Songs, however, don't contain any information about
|
||||
their *charts*. The charts are what people actually play.
|
||||
|
||||
In short, songs are *just* a collection of metadata that
|
||||
parents a chart document!
|
||||
|
||||
*****
|
||||
|
||||
## Chart Documents
|
||||
|
||||
Chart Documents **MUST** belong to a song. In the below
|
||||
chart, `songID` refers to the above song document:
|
||||
|
||||
```json
|
||||
{
|
||||
"rgcID": null,
|
||||
"chartID": "c2311194e3897ddb5745b1760d2c0141f933e683",
|
||||
"difficulty": "ANOTHER",
|
||||
"songID": 1,
|
||||
"playtype": "SP",
|
||||
"levelNum": 10,
|
||||
"level": "10",
|
||||
"flags": {
|
||||
"IN BASE GAME": true,
|
||||
"OMNIMIX": true,
|
||||
"N-1": true,
|
||||
"2dxtra": false
|
||||
},
|
||||
"data": {
|
||||
"inGameID": 1000,
|
||||
"notecount": 786,
|
||||
"arcChartID": "CYjwAuz7Yq9"
|
||||
},
|
||||
"isPrimary": true,
|
||||
"versions": [
|
||||
"27-omni",
|
||||
"26-omni",
|
||||
"27",
|
||||
"26",
|
||||
"inf",
|
||||
"16-cs",
|
||||
"12-cs",
|
||||
"10-cs",
|
||||
"8-cs",
|
||||
"7-cs",
|
||||
"bmus"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Songs can have multiple charts, but charts can only have
|
||||
one song.
|
||||
|
||||
## Primary
|
||||
|
||||
Sometimes, games like to rechart things and release them
|
||||
under the exact same song. Sometimes, they even do this
|
||||
under the *exact* same internal songID!
|
||||
|
||||
A 'Primary' chart refers to a chart that is the *current*
|
||||
variant of that chart for this game. Non-Primary charts
|
||||
are not eligible for any rating calculations, nor do they
|
||||
contribute to profile rating.
|
||||
|
||||
To check if a chart is primary or not, the `isPrimary` property
|
||||
handles that.
|
||||
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"rgcID": null,
|
||||
"chartID": "103ff8bb004e1a8a005f808c025c3feb",
|
||||
"difficulty": "ANOTHER",
|
||||
"songID": 1,
|
||||
"playtype": "SP",
|
||||
"levelNum": 5,
|
||||
"level": "5",
|
||||
"flags": {
|
||||
"IN BASE GAME": true,
|
||||
"OMNIMIX": true,
|
||||
"N-1": true,
|
||||
"2dxtra": false
|
||||
},
|
||||
"data": {
|
||||
"inGameID": 1000,
|
||||
"notecount": 433,
|
||||
"arcChartID": "foobar"
|
||||
},
|
||||
"isPrimary": false,
|
||||
"versions": [
|
||||
"1"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Notice that the chartID is different, and `isPrimary`
|
||||
is set to false.
|
||||
|
||||
Even though this chart is "5.1.1 (SP ANOTHER)", it isn't
|
||||
the primary chart for this songID + playtype + difficulty.
|
||||
|
||||
For the UI, Tachi will hide non-primary charts by default.
|
||||
Realistically, they only exist to support legacy scores
|
||||
without having to throw them away when a rechart occurs.
|
||||
@@ -0,0 +1,178 @@
|
||||
# Statistic Implementation
|
||||
|
||||
This page documents the maths behind various algorithms
|
||||
in Tachi.
|
||||
|
||||
*****
|
||||
|
||||
## BPI
|
||||
|
||||
Our implementation of Poyashi BPI is leveraged from [here](https://github.com/potakusan/iidx_score_manager/blob/f21ba6b85fcc0bf8b7ca888fa2239a3951a9c9c2/src/components/bpi/index.tsx#L120).
|
||||
|
||||
To be honest, I do not really understand *why* BPI looks
|
||||
like this. I couldn't justify basically any line of this
|
||||
function, nor any of the magic numbers it references.
|
||||
|
||||
## MFCP
|
||||
|
||||
MFCP is implemented as follows.
|
||||
|
||||
If the score is not an MFC, it is worth `null`.
|
||||
|
||||
If the score on a BEGINNER or BASIC chart, it is worth `null`.
|
||||
|
||||
If the level of the chart is worth less than 8, it is worth `null`.
|
||||
|
||||
Else, it follows this table:
|
||||
|
||||
| Levels | MFCP |
|
||||
| :: | :: |
|
||||
| 8, 9, 10 | 1 |
|
||||
| 11, 12 | 2 |
|
||||
| 13 | 4 |
|
||||
| 14 | 8 |
|
||||
| 15 | 15 |
|
||||
| 16, 17, 18, 19, 20 | 25 |
|
||||
|
||||
## VF6
|
||||
|
||||
VF6 is calculated as follows.
|
||||
|
||||
The grade of the score is converted into a coefficent
|
||||
according to this table.
|
||||
|
||||
```ts
|
||||
const VF5GradeCoefficients = {
|
||||
S: 1.05,
|
||||
"AAA+": 1.02,
|
||||
AAA: 1.0,
|
||||
"AA+": 0.97,
|
||||
AA: 0.94,
|
||||
"A+": 0.91, // everything below this point (incl. this) is marked with a (?) in bemaniwiki.
|
||||
A: 0.88,
|
||||
B: 0.85,
|
||||
C: 0.82,
|
||||
D: 0.8,
|
||||
};
|
||||
```
|
||||
|
||||
Lamps are converted into a coefficent similarly.
|
||||
|
||||
```ts
|
||||
const VF5LampCoefficients = {
|
||||
"PERFECT ULTIMATE CHAIN": 1.1,
|
||||
"ULTIMATE CHAIN": 1.05,
|
||||
"EXCESSIVE CLEAR": 1.02,
|
||||
CLEAR: 1.0,
|
||||
FAILED: 0.5,
|
||||
};
|
||||
```
|
||||
|
||||
Then, we perform the following calculation:
|
||||
|
||||
$$
|
||||
f(l, p, c1, c2) = 2l * p * c1 * c2 * 0.01
|
||||
$$
|
||||
|
||||
Where L is the chart's level, P is the percent of the score,
|
||||
C1 is the grade coefficent, and C2 is the lamp coefficient.
|
||||
|
||||
For VF6, this result is returned floored to 3 decimal places.
|
||||
|
||||
For VF5, this result is returned floored to **2** decimal places.
|
||||
|
||||
## KtRating
|
||||
|
||||
KtRating is a generic exponential algorithm that takes
|
||||
three tunable parameters. These are changed depending
|
||||
on the game that uses this algorithm.
|
||||
|
||||
!!! note
|
||||
This is not meant to be a perfect algorithm for all
|
||||
scenarios. It's meant to cover for games that don't
|
||||
have a sensible default rating algorithm.
|
||||
|
||||
The three parameters are as follows:
|
||||
|
||||
| Parameter | Description |
|
||||
| :: | :: |
|
||||
| `pivotPercent` | The percent below which a score is considered a 'fail', and should be negatively punished. |
|
||||
| `failHarshnessMultiplier` | By how much fails should be punished. A higher value implies fails are worth less. |
|
||||
| `clearExpMultiplier` | How much to exponentially reward clears. A higher value implies that timing at the highest level is more difficult. |
|
||||
|
||||
If the score's percent is below the `pivotPercent`, the
|
||||
fail calculator is invoked, which uses the following
|
||||
function:
|
||||
|
||||
```ts
|
||||
percentDiv100 ** (parameters.failHarshnessMultiplier * levelNum) *
|
||||
(levelNum / parameters.pivotPercent ** (parameters.failHarshnessMultiplier * levelNum))
|
||||
```
|
||||
|
||||
In mathematical notation, this is represented as:
|
||||
|
||||
$$
|
||||
f(x, f, l, c) = \left(x^{f}\cdot\left(\frac{l}{c^{f}}\right)\right)
|
||||
$$
|
||||
|
||||
Where X is the percent divided by 100,
|
||||
F is the `failHarshnessMultiplier` multiplied by L,
|
||||
L is the level of the chart and
|
||||
C is the `pivotPercent`.
|
||||
|
||||
If the score is above the `pivotPercent`, then the
|
||||
clear calculator is invoked, which uses the following
|
||||
function:
|
||||
|
||||
```ts
|
||||
Math.cosh(
|
||||
parameters.clearExpMultiplier * levelNum * (percentDiv100 - parameters.pivotPercent)
|
||||
) +
|
||||
(levelNum - 1);
|
||||
```
|
||||
|
||||
In mathematical notation, this is expressed as:
|
||||
|
||||
$$
|
||||
f(x, c, l) = \cosh\left(n\left(x-c\right)\right)+l-1
|
||||
$$
|
||||
|
||||
Where X is the percent divided by 100,
|
||||
L is the level of the chart and
|
||||
C is the `pivotPercent`.
|
||||
|
||||
Cosh is used because it's essentially e^x, and easier
|
||||
to work with in this (contrived) scenario.
|
||||
|
||||
!!! note
|
||||
L will be replaced with the timing difficulty
|
||||
declared for that chart in the tierlists.
|
||||
|
||||
If one does not exist, it will fall back to
|
||||
the provided level as a number.
|
||||
|
||||
## KtLampRating
|
||||
|
||||
Unlike KTRating, KTLampRating is a fairly sensible
|
||||
function.
|
||||
|
||||
It has no tunable parameters.
|
||||
|
||||
It starts by getting the tierlist information for the
|
||||
given chart.
|
||||
|
||||
If there is tierlist information, then we iterate
|
||||
over the lamp ratings they declare.
|
||||
|
||||
We select the largest lamp rating that the lamp meets
|
||||
the requirements of.
|
||||
This is to fix things like Hard Clears sometimes being worth
|
||||
less than Normal Clears (especially in IIDX). This also
|
||||
means that we safely fall back to lower values if the
|
||||
users lamp was unsupported - i.e. a FULL COMBO will fall
|
||||
down to an EX HARD CLEAR.
|
||||
|
||||
If there is no tierlist data available for this chart,
|
||||
we fall to a simple question of whether the score was
|
||||
considered a clear or not. If it is, give the level
|
||||
of the chart as a number as points.
|
||||
@@ -12,7 +12,7 @@ This function can be found at `src/lib/score-import/framework/score-import-main.
|
||||
| Argument | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `user` | PublicUserDocument | The user that is making this import request. |
|
||||
| `userIntent` | boolean | Whether this import was performed with User Intent - See [Import Types](./import-types.md#User%20Intent) |
|
||||
| `userIntent` | boolean | Whether this import was performed with User Intent - See [Import Types](./import-types.md#user-intent) |
|
||||
| `importType` | ImportType | What kind of import "type" this is. For more on this, see [Import Types](./import-types.md)
|
||||
| `InputParser` | Function | The parser function to call. For more info, see [Parsing and Converting](./parse-conv.md)
|
||||
| `providedImportObjects` (Optional) | { logger, importID } | Optionally, a logger and existing importID can be passed here. This is used for scenarios where the logger and importID have already been created before importMain was called. |
|
||||
|
||||
@@ -51,7 +51,7 @@ to.
|
||||
|
||||
### Implementation
|
||||
|
||||
As an example, lets say we had a CSV being sent to us as a file.
|
||||
As an example, let's say we had a CSV being sent to us as a file.
|
||||
|
||||
We could write a parser for that as follows:
|
||||
|
||||
@@ -256,7 +256,7 @@ function CreateIIDXFileParser(request) {
|
||||
|
||||
## Converters
|
||||
|
||||
Lets say we have a parser for SDVX that returns:
|
||||
let's say we have a parser for SDVX that returns:
|
||||
```js
|
||||
[{
|
||||
score: 9000000,
|
||||
|
||||
@@ -75,7 +75,7 @@ You may recall that in [Parsers and Converters](./parse-conv.md) it was document
|
||||
ClassHandler. The ClassHandler is intended to handle those
|
||||
explicit changes.
|
||||
|
||||
For example, lets say we have an import type that tells us:
|
||||
For example, let's say we have an import type that tells us:
|
||||
```js
|
||||
{
|
||||
scores: [{score: 1000, songID: 1, diff: "spa"}],
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Versioning
|
||||
|
||||
*****
|
||||
|
||||
## Semver
|
||||
|
||||
`tachi-server` and `tachi-client` follow [Semantic Versioning](https://semver.org).
|
||||
|
||||
The two repositories have completely separate versioning, but generally will move in
|
||||
lockstep with one-another.
|
||||
|
||||
!!! example
|
||||
For quick reference, MAJOR, MINOR and PATCH correspond as follows:
|
||||
|
||||
```2.3.1 -> MAJOR.MINOR.PATCH```
|
||||
|
||||
## `master` Branch
|
||||
|
||||
Every push to `master` must involve a change to the versioning of that repository. If
|
||||
it's a hotfix, it should bump the PATCH version. If it's a feature, it should update the
|
||||
MINOR version.
|
||||
|
||||
The MAJOR version will only be bumped if there are significant changes to almost everything,
|
||||
which I don't anticipate.
|
||||
|
||||
## Version Names
|
||||
|
||||
`tachi-server` and `tachi-client` have version names that change in correspondence with
|
||||
their `MINOR` versions.
|
||||
|
||||
The version names follow the song titles of an album. In `tachi-server`'s case, it follows
|
||||
[Portishead - Dummy](https://en.wikipedia.org/wiki/Dummy_(album))
|
||||
|
||||
For `tachi-client`, we follow [The Cure - Disintegration](https://en.wikipedia.org/wiki/Disintegration_(The_Cure_album))
|
||||
|
||||
### Why?
|
||||
|
||||
Versions following album songs saves the hassle of having to come up with nice sounding version
|
||||
names. It's also an excuse to show off albums I really like.
|
||||
|
||||
As for why these specific albums:
|
||||
|
||||
Portishead's Dummy was chosen because Tachi V1 followed
|
||||
[Massive Attack - Mezzanine](https://en.wikipedia.org/wiki/Mezzanine_(album)). The two albums
|
||||
are both standout albums in the same genre, so it was fitting to follow it up.
|
||||
|
||||
The Cure's Disintegration was chosen because I wanted another album with
|
||||
the same amount of tracks as Dummy, and it's also a great album.
|
||||
|
||||
!!! tip
|
||||
These albums are great. Do yourself a favour and check them out.
|
||||
@@ -21,11 +21,12 @@ correspond to, how `thing` works, etc.
|
||||
|
||||
## Repos and Licenses
|
||||
|
||||
Tachi is made up of three components:
|
||||
Tachi is made up of four components:
|
||||
|
||||
- `tachi-common`: Common types and values for Tachi. [GitHub](https://github.com/zkldi/tachi-common). This is licensed under MIT.
|
||||
- `tachi-server`: The API, IR implementations and 'business logic' behind Tachi. [GitHub](https://github.com/zkldi/tachi-server). This is licensed under AGPLv3.
|
||||
- `tachi-client`: The front-end code for Tachi. This is closed source.
|
||||
- `tachi-docs`: The documentation you're reading right now! [GitHub](https://github.com/zkldi/tachi-docs). This is licensed under MIT.
|
||||
|
||||
### What's with `tachi-client`?
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ the main benefits for us are as follows:
|
||||
|
||||
```js
|
||||
{
|
||||
MONGO_BASE_URL: "127.0.0.1",
|
||||
MONGO_CONNECTION_URL: "127.0.0.1:27017",
|
||||
MONGO_DATABASE_NAME: "somedb",
|
||||
LOG_LEVEL: "info",
|
||||
CAPTCHA_SECRET_KEY: "google_given_secret_key",
|
||||
SESSION_SECRET: "some_secret_key",
|
||||
@@ -43,16 +44,22 @@ the main benefits for us are as follows:
|
||||
|
||||
All properties are required.
|
||||
|
||||
### MONGO_BASE_URL
|
||||
### MONGO_CONNECTION_URL
|
||||
|
||||
- Type: String
|
||||
|
||||
Where your MongoDB server is located. For most cases, this
|
||||
is `127.0.0.1` or `localhost`.
|
||||
is `127.0.0.1:27017` or `localhost:27017`.
|
||||
|
||||
For more creative scenarios such as running through WSL, you
|
||||
might need to change this.
|
||||
|
||||
### MONGO_DATABASE_NAME
|
||||
|
||||
- Type: String
|
||||
|
||||
What collection to use for your database.
|
||||
|
||||
### LOG_LEVEL
|
||||
|
||||
- Type: Log Level
|
||||
|
||||
@@ -34,6 +34,14 @@ Various scripts for interacting with `tachi-server`, such
|
||||
as single-use scripts for importing some data, or
|
||||
frequently used scripts such as updating BMS tables.
|
||||
|
||||
!!! danger
|
||||
The scripts in here are not regularly maintained,
|
||||
**especially** the ones inside `single-use`. You should
|
||||
**ABSOLUTELY NOT** run those if you do not know what
|
||||
they do.
|
||||
|
||||
Seriously, you could destroy your server.
|
||||
|
||||
## TypeScript Source Code
|
||||
|
||||
All of these are inside `/src`.
|
||||
@@ -127,8 +135,4 @@ in cases where an API call needs to do a lot of things.
|
||||
|
||||
## Test Files
|
||||
|
||||
Test files are to be located in the same folder as the file
|
||||
they're testing, and should only ever test the exports
|
||||
of one file.
|
||||
|
||||
They should have the filename of the file they're testing, with the extension `.test.ts`.
|
||||
This documentation has been moved to [its own page](./testing.md)!
|
||||
|
||||
@@ -17,9 +17,12 @@ ESLint is set up to automatically perform all of these changes when ran.
|
||||
|
||||
## Prettier Rules
|
||||
|
||||
- 4 Spaces Indenting.
|
||||
- Tab Indenting.
|
||||
|
||||
I'd prefer to use tabs, honestly, but Prettier and JSDoc like to align things with spaces and it messes with them.
|
||||
<del>I'd prefer to use tabs, honestly, but Prettier and JSDoc like to align things with spaces and it messes with them.</del>
|
||||
|
||||
It turns out there's only one rare scenario where prettier mixes tabs and spaces (Rare as in, it happens
|
||||
once in an obscure place in the entire codebase), so we've switched to tabs.
|
||||
|
||||
- Semicolons.
|
||||
|
||||
@@ -27,7 +30,8 @@ No-Semicolons causes issues with IIFEs.
|
||||
|
||||
- Try to keep things under 100 characters.
|
||||
|
||||
Absolutely do not insert random line breaks to keep stuff under 100 characters. It's fine for things to go a bit over.
|
||||
Absolutely **DO NOT** insert random line breaks to keep stuff under 100 characters. It's fine for things to go a bit over.
|
||||
Seriously, your editor is definitely capable of wrapping text if it goes too far.
|
||||
|
||||
Prettier has its own opinions on where these line breaks should happen, just trust them.
|
||||
|
||||
@@ -35,11 +39,11 @@ Prettier has its own opinions on where these line breaks should happen, just tru
|
||||
|
||||
JSON does it and that's pretty much the only reason why.
|
||||
|
||||
- Line Break is LF, not CRLF
|
||||
- Line Break is LF, **not CRLF**
|
||||
|
||||
Your editor will handle this properly. If it does not
|
||||
automatically set, check the bottom right of your editor.
|
||||
For Atom, VSCode and some others it will let you switch between
|
||||
For Atom, VSCode and most others it will let you switch between
|
||||
CRLF and LF.
|
||||
|
||||
## Commenting Style
|
||||
@@ -70,10 +74,10 @@ function sd(arr: number[]) {
|
||||
This is bad code. Very bad code. It is not at all clear what
|
||||
this code does from any of the variable names, and the function signature barely helps.
|
||||
|
||||
Lets try and make this code more self documenting.
|
||||
First, let's try and make this code more self documenting.
|
||||
|
||||
We'll give everything proper variable names, and then
|
||||
expand the second `reduce` call into a simpler for loop.
|
||||
expand the second `reduce` call into a simpler for loop.[^1]
|
||||
|
||||
```ts
|
||||
function CalculateStandardDeviation(dataset: number[]) {
|
||||
@@ -137,3 +141,8 @@ The list of directives and their meaning is here:
|
||||
Don't worry about this too much, At the end of the day, as
|
||||
long as the code is understandable and the linter is happy,
|
||||
it's good.
|
||||
|
||||
[^1]: JS's ES6 array methods are the devil if used improperly. For some reason, lots of people in
|
||||
react and react-adjacent scenes seem to love (ab)using these array methods for everything. Complex
|
||||
`reduce` operations should always be turned into a `for loop`, and that's to say nothing of my opinions
|
||||
on `forEach`.
|
||||
|
||||
+10
@@ -18,6 +18,16 @@ pnpm fulltest
|
||||
This will execute every test, and also perform coverage
|
||||
analysis.
|
||||
|
||||
!!! bug
|
||||
This may or may not impact you depending on how
|
||||
dependencies install, but `tap` requires `ts-node`
|
||||
and `typescript` to also be installed.
|
||||
|
||||
Otherwise, you will get a
|
||||
`cannot use import outside of module`
|
||||
error, which indicates that our typescript hasn't
|
||||
been transpiled.
|
||||
|
||||
## Parallel Tests
|
||||
|
||||
You can run tests in parallel with:
|
||||
+1
-1
@@ -42,7 +42,7 @@ If you want to contribute to this documentation, you can find the repository [he
|
||||
|
||||
If you want to contribute to the Tachi backend code, you can find the repository [here!](https://github.com/zkldi/tachi-server)
|
||||
!!! note
|
||||
You should read the [Contributing To Tachi-Server](#) page beforehand.
|
||||
You should read the [Contributing To Tachi-Server](./codebase/contributing.md) page beforehand.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
# Feature List
|
||||
|
||||
This page lists all of the features Tachi has.
|
||||
Some games may utilise these better than others.
|
||||
|
||||
*****
|
||||
|
||||
## Sessions
|
||||
|
||||
Sessions are a feature in Tachi that group up your
|
||||
scores depending on *when* they were achieved.
|
||||
|
||||
Sessions are meant to mimic the colloquial use of
|
||||
the term - Players typically refer to their scores
|
||||
as being part of a session, so why don't we show
|
||||
their scores in that fashion aswell?
|
||||
|
||||
### What is a session?
|
||||
|
||||
A session is a group of scores that all happened
|
||||
around the same time.
|
||||
|
||||
When you get a new score (and don't have a session nearby),
|
||||
a session is automatically created!
|
||||
Then, if you get a score within two hours of that last score,
|
||||
that score is added to the session - this will repeat
|
||||
until eventually you spend more than two hours between
|
||||
your score and the last score.
|
||||
|
||||
In practice, the only time you're spending more than
|
||||
two hours between a score is when you're either not playing
|
||||
anymore, or taking a significant enough break that you're
|
||||
probably no longer warm.
|
||||
|
||||
!!! info
|
||||
The reason we use the two-hour rule instead of just
|
||||
splitting on a day is that not everyone lives in the
|
||||
same timezone, and their session may get unexpectedly
|
||||
split in two!
|
||||
|
||||
### Why bother?
|
||||
|
||||
Since players use the term "sessions" so frequently, it
|
||||
makes sense to let them view scores how they already
|
||||
think about them.
|
||||
|
||||
Advantages of sessions also include being able to name and
|
||||
categorise them - so you can search back on them in the
|
||||
future.
|
||||
|
||||
You can also share a single link to your finished session,
|
||||
instead of having to squish all your great scores into one
|
||||
long twitter thread!
|
||||
|
||||
Another small advantages include statistics - we can
|
||||
calculate your "ability" that session, and graph it
|
||||
over time - so you can see your real time improvement!
|
||||
|
||||
### What if I play multiple games?
|
||||
|
||||
**Sessions can only store scores of the same game and playtype**.
|
||||
|
||||
That is, if you have an IIDX SP session "ongoing", and get
|
||||
an IIDX DP score - you will have two sessions ongoing at
|
||||
the same time!
|
||||
|
||||
Your IIDX DP score will **not** be added to your SP session.
|
||||
|
||||
!!! note
|
||||
For home players, this is likely to not ever be an
|
||||
issue, but for arcade players - where people typically
|
||||
throw a couple credits into different games every day,
|
||||
it can become a problem!
|
||||
|
||||
### What does this performance statistic mean?
|
||||
|
||||
You can read all about session statistics [here](./stats/tachi.md#session-ratings).
|
||||
|
||||
### Summary
|
||||
|
||||
That's it for sessions! As a quick summary:
|
||||
|
||||
- Sessions group your scores based on when they've happened.
|
||||
- Sessions are easy to share with other players.
|
||||
- Sessions are a nice way of displaying all the scores you did!
|
||||
|
||||
*****
|
||||
|
||||
## Goals
|
||||
|
||||
!!! note
|
||||
This feature goes hand-in-hand with [Milestones](#milestones).
|
||||
|
||||
Goals are a built-in way of setting targets for yourself.
|
||||
|
||||
A lot of the time, people have goals they're aiming for in
|
||||
rhythm games, but at the moment, people generally have to
|
||||
keep said goals in their head.
|
||||
|
||||
Tachi lets you set and track your own goals - so you don't
|
||||
have to remember them!
|
||||
|
||||
### Advantages
|
||||
|
||||
With your goals set and stored, we can see other users with
|
||||
similar goals - which might give you good ideas for other
|
||||
goals to aim for.
|
||||
|
||||
You can also be automatically notified when a goal is
|
||||
achieved, or when you've made some progress towards it.
|
||||
|
||||
If you've ever been in the middle of a session and not
|
||||
known what to play, you could always look at your list of
|
||||
goals and try to check off some older ones, too!
|
||||
|
||||
### What kind of goals can I set?
|
||||
|
||||
There are two different parameters that control a goal.
|
||||
|
||||
#### Criteria
|
||||
|
||||
The criteria determines what our goal actually is.
|
||||
|
||||
You can set goals for Lamps, Grades, Percents and score.
|
||||
|
||||
!!! example
|
||||
Get 950'000 on FREEDOM DiVE.
|
||||
SS FREEDOM DiVE.
|
||||
Clear FREEDOM DiVE
|
||||
Get 95% on FREEDOM DiVE.
|
||||
|
||||
We can also control *how many* scores need to match the
|
||||
given criteria. This makes more sense when goals apply
|
||||
to more than one chart.
|
||||
|
||||
!!! example
|
||||
AAA 50 Charts in the Level 12 folder.
|
||||
|
||||
FULL COMBO 10% of the Level 11 folder.
|
||||
|
||||
Clear either FREEDOM DiVE or Blue Zenith.
|
||||
|
||||
#### Charts
|
||||
|
||||
You can also control what charts your goal applies to.
|
||||
|
||||
The most simple option is to only select one chart.
|
||||
|
||||
!!! example
|
||||
Full Combo xi - FREEDOM DiVE (FOUR DIMENSIONS)
|
||||
|
||||
You can also select multiple, fixed charts.
|
||||
|
||||
!!! example
|
||||
Full Combo xi - FREEDOM DiVE or NOMA - BRAIN POWER.
|
||||
|
||||
Get 95% on 2 of the following, FREEDOM DiVE, Elemental Creation, BRAIN POWER or Blue Zenith.
|
||||
|
||||
Alternatively, you can set goals on folders.
|
||||
|
||||
!!! example
|
||||
Full combo any chart in the Level 12 folder.
|
||||
|
||||
Clear 50% of the Level 12 folder.
|
||||
|
||||
AAA 100 charts in the Level 12 folder.
|
||||
|
||||
Or, remove the chart restriction entirely!
|
||||
|
||||
!!! example
|
||||
AAA any chart.
|
||||
|
||||
AAA 100 charts.
|
||||
|
||||
Full Combo Every Chart.
|
||||
|
||||
### Summary
|
||||
|
||||
That's it for goals! As a quick summary:
|
||||
|
||||
- Goals can be set for charts, folders, or anything!
|
||||
- You can set goals for lamps, scores, percents and grades.
|
||||
- You can share your goals with other users, and see what your rivals have set!
|
||||
|
||||
This is nice, but there's something missing...
|
||||
|
||||
## Milestones
|
||||
|
||||
As mentioned above, Goals were designed to go hand-in-hand
|
||||
with this feature.
|
||||
|
||||
### Issues with Just Goals.
|
||||
|
||||
The main issue with goals on their own is that we're not
|
||||
good at setting our own goals.
|
||||
|
||||
What if someone doesn't *really* know what they should be
|
||||
aiming for, or is just lazy and doesn't like setting goals?
|
||||
|
||||
In general, we're bad at setting our own goals. How can
|
||||
we fix that?
|
||||
|
||||
### Why Milestones?
|
||||
|
||||
Milestones are **pre-made groups of goals**. As an example,
|
||||
we might bundle together some goals aimed at SOUND VOLTEX
|
||||
11 dan players. You can then subscribe to that milestone,
|
||||
and all those goals will be merged with your list of goals!
|
||||
|
||||
This solves the problem of having to set and manage *loads*
|
||||
of goals on your own - since other people can come up with
|
||||
and debate good goals for your skill level, and you can
|
||||
just seamlessly integrate them with your play!
|
||||
|
||||
### Who makes the Milestones?
|
||||
|
||||
The community! People are free to create their own
|
||||
milestones and share them.
|
||||
|
||||
### Why are they called Milestones?
|
||||
|
||||
Milestones work as - well - milestones!
|
||||
|
||||
They're achievable, just like goals, which means the
|
||||
milestone creator can set criteria.
|
||||
|
||||
A basic example would be counting the milestone as
|
||||
achieved when you've achieved *all* of the goals in that
|
||||
milestone.
|
||||
|
||||
More advanced options include achieving X goals inside a
|
||||
milestone, or X% of the goals inside a milestone.
|
||||
|
||||
### Summary
|
||||
|
||||
That's all for milestones! As a quick summary:
|
||||
|
||||
- Since milestones are achievables, you'll be automatically notified when you tick another milestone off!
|
||||
- You can also see other user's milestones and their progress.
|
||||
- Milestones reduce the pain of having to set your goals yourself!
|
||||
@@ -34,8 +34,8 @@ that display your scores. I think that scores are integral
|
||||
to the rhythm game experience, and that displaying them
|
||||
properly is *just* as important.
|
||||
|
||||
The benefits of Tachi include features like [Sessions](./features/sessions.md), which break your scores up into
|
||||
groups of when they were played, and [Goals](./features/goals.md) which let you set automatically updating targets for yourself!
|
||||
The benefits of Tachi include features like [Sessions](./features.md#sessions), which break your scores up into
|
||||
groups of when they were played, and [Goals](./features.md#goals) which let you set automatically updating targets for yourself!
|
||||
|
||||
There are way more features that Tachi has, and you can
|
||||
read about all of them [here](./features.md).
|
||||
@@ -57,7 +57,7 @@ Tachi's analytics!
|
||||
Yes.
|
||||
|
||||
You should familiarise yourself with the rules before
|
||||
using any variant of Tachi.
|
||||
using any distribution of Tachi.
|
||||
|
||||
The rules can be found [here](./rules.md).
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ interactions **outside of Tachi**, which includes, **but is not limited to**:
|
||||
## You are only allowed one account.
|
||||
|
||||
Do not make multiple accounts, we track account IPs and it
|
||||
lets us know. This is to keep the leaderboards fair
|
||||
let's us know. This is to keep the leaderboards fair
|
||||
and avoid one player from taking multiple spaces.
|
||||
|
||||
## Do not set NSFW artwork as your avatar or banner.
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# Tachi Statistics
|
||||
|
||||
This page serves as a glossary for all the statistics
|
||||
used in Tachi, alongside their pros and cons.
|
||||
|
||||
*****
|
||||
|
||||
## IIDX
|
||||
|
||||
### SP
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# What is ESD?
|
||||
|
||||
ESD is short for "Estimated Standard Deviation".
|
||||
|
||||
Standard deviation is a way of measuring how *dispersed*
|
||||
data is. [Wikipedia](https://en.wikipedia.org/wiki/Standard_deviation) explains what this is and why it works.
|
||||
|
||||
*****
|
||||
|
||||
## Motivation
|
||||
|
||||
Standard Deviation is an interesting statistic for rhythm
|
||||
games, as the dispersion of a users hits is quite interesting.
|
||||
|
||||
That is, if a user is hitting all over the place timing wise,
|
||||
they probably aren't doing very good. Vice versa,
|
||||
if a user has incredibly close together hits timing wise,
|
||||
they're probably good.
|
||||
|
||||
ESD let's us derive standard deviation from just the *percent*
|
||||
of a score, and the judgement windows for a game!
|
||||
|
||||
It's surprisingly accurate, and is a useful statistic
|
||||
to derive other statistics from.
|
||||
|
||||
!!! note
|
||||
Only some games support ESD. These are only games that
|
||||
have strict hit windows that correlate perfectly with
|
||||
percent.
|
||||
|
||||
An example would be IIDX, where percent is only
|
||||
derived from EX Score, and hit windows are constant.
|
||||
|
||||
BMS cannot support ESD as it has dynamic hit windows,
|
||||
depending on the chart.
|
||||
|
||||
GITADORA cannot support ESD as it's percent is influenced
|
||||
by combo-based scoring.
|
||||
|
||||
SDVX cannot support ESD because things like holds
|
||||
count as multiple repeated hits, but you do not have
|
||||
to time those repeated hits!
|
||||
|
||||
In short, one hit needs to correspond to one judgement,
|
||||
and every hit has to involve a timing window.
|
||||
|
||||
For details on the implementation of ESD, you can see
|
||||
[here](../../codebase/implementation-details/esd.md).
|
||||
|
||||
!!! warning
|
||||
Implementation details about ESD require some
|
||||
external knowledge about statistics and distributions.
|
||||
@@ -0,0 +1,585 @@
|
||||
# Tachi Statistics
|
||||
|
||||
Tachi has a lot of statistics involved. This page documents all of the
|
||||
statistics used for each game, and what they mean.
|
||||
|
||||
!!! note
|
||||
These explainations brush over the technical details a bit. If you're interested
|
||||
in that, you might want to see the [Implementation Details](../../codebase/implementation-details/statistics.md).
|
||||
|
||||
*****
|
||||
|
||||
## Statistics Overview
|
||||
|
||||
Tachi needs statistics in three main places. The first
|
||||
place is on each *individual* score.
|
||||
|
||||
As an example, if I get a score, it should have a rating
|
||||
attached onto it.
|
||||
|
||||
The second place it needs a rating algorithm is for
|
||||
a user's profile. This is likely to be combined from
|
||||
individual score statistics, either by averaging or
|
||||
totalling.
|
||||
|
||||
The third place it needs a rating algorithm is for
|
||||
a user's sessions. This is also likely to be combined
|
||||
from individual score statistics.
|
||||
|
||||
## Score Statistics
|
||||
|
||||
The below statistics apply to individual scores.
|
||||
|
||||
!!! note
|
||||
Every game needs to have a default rating algorithm. A default rating
|
||||
algorithm needs to work well on all scores from all skill levels.
|
||||
|
||||
This means that some more 'pro' oriented statistics cannot be the default.
|
||||
|
||||
Some games might not have *any* rating algorithms. If we dont have
|
||||
a good built-in contender for a default rating algorithm, we have to invent our own.
|
||||
|
||||
*****
|
||||
|
||||
### ktRating (IIDX)
|
||||
|
||||
- **Default for IIDX SP and IIDX DP**
|
||||
|
||||
Pros:
|
||||
|
||||
- Works on all scores.
|
||||
- Takes tierlists into account.
|
||||
- Accuracy Based.
|
||||
|
||||
Cons:
|
||||
|
||||
- Has a poor understanding of high acc levels (MAX- and beyond).
|
||||
|
||||
KtRating (Kamaitachi Rating) is our custom rolled rating algorithm for IIDX.
|
||||
The issue we have is that IIDX doesn't have any metrics that are a good contender
|
||||
for our default rating algorithm.
|
||||
|
||||
DJ Points is worthless because it depends on the notecount of the chart instead of its level
|
||||
and BPI only really has any meaning to kaidens, so we're stuck having to roll our own statistic.
|
||||
|
||||
KtRating was designed to be a generic **timing** rating algorithm for IIDX. It isn't perfect
|
||||
by any means, and is generally worse than BPI at 12s. It also struggles with things like
|
||||
MAX- and stronger scores.
|
||||
|
||||
Regardless, it works decently for most charts and most players. It uses a curve such that
|
||||
an AA on a chart rated X will be worth X points.
|
||||
|
||||
!!! note
|
||||
X will be influenced by the tierlist. For example, mosaic SPA is rated a 12 internally,
|
||||
but has a lower tierlist timing rating value of around 10.5.
|
||||
|
||||
*****
|
||||
|
||||
### BPI (IIDX)
|
||||
|
||||
Pros:
|
||||
|
||||
- Estimates timing difficulty for a chart reasonably.
|
||||
- Is an understood standard by Kaiden and Post-Kaiden players.
|
||||
|
||||
Cons:
|
||||
|
||||
- Depends on Kaiden Average and World Record, which can be highly fluctuative.
|
||||
- Not able to accurately cross-compare values (i.e. 20BPI on one song is often not equivalent in 'skill' to 20BPI on another.)
|
||||
- Only practically works on 12s. It can be extended to 11s, but it doesn't work as well. It does not work at all below 11.
|
||||
|
||||
There are two implementations of BPI - We'll call them Nori BPI and Poyashi BPI, Tachi uses the
|
||||
more recent Poyashi BPI.
|
||||
|
||||
!!! info
|
||||
As mentioned above in the cons of BPI, Kaiden Average and WR can be rather unreliable estimates
|
||||
of difficulty. When HV came out, 120hz caused almost all WRs to jump up significantly, which
|
||||
pretty much completely broke Nori's BPI. To fix this, some parameters were adjusted for
|
||||
poyashi's BPI, which is implemented [here](https://bpi.poyashi.me).
|
||||
Namely, reducing the impact of high WRs on average BPI.
|
||||
|
||||
As a consequence, Poyashi BPI is significantly easier than Nori BPI in every circumstance.
|
||||
Whether this is an issue or not is up to you, and I think most players enjoy seeing the
|
||||
larger number.
|
||||
|
||||
BPI looks at the kaiden average and world record for a chart, and constructs an exponential
|
||||
graph between the two points. A BPI of 0 is equivalent to Kaiden Average, a BPI of 100 is
|
||||
equivalent to the world record.
|
||||
|
||||
!!! info
|
||||
For scores less than the Kaiden Average, Poyashi BPI uses a negative extension that caps
|
||||
at -15. This appears to be arbitrary.
|
||||
|
||||
For Nori BPI, scores less than the Kaiden Average become [Complex Numbers](https://en.wikipedia.org/wiki/Complex_number).
|
||||
|
||||
BPI is intended for use for kaidens and players significantly beyond kaiden. For that, it works
|
||||
decently. As mentioned above in the cons, BPI is not very cross-comparable. 20BPI on one song
|
||||
is not necessarily as good as 20BPI on another.
|
||||
|
||||
!!! example
|
||||
20BPI on Verflucht Leggendaria is AAA+66. 20BPI on FAKE TIME is also AAA+66.
|
||||
|
||||
*****
|
||||
|
||||
### ktLampRating (IIDX)
|
||||
|
||||
Pros:
|
||||
|
||||
- Corresponds 100% with well-agreed-upon tierlists
|
||||
- Works on all charts, and uses tierlists down to SP10.
|
||||
|
||||
Cons:
|
||||
|
||||
- Does not support additional points for Full Combos (will give EXHard points) or Easy Clears (will give 0).
|
||||
|
||||
KtLampRating (Kamaitachi Lamp Rating) is a generic algorithm that gives points based
|
||||
on the quality of the lamp.
|
||||
|
||||
This is entirely done with tierlists that are converted into decimal form. So, if a chart
|
||||
is marked as 11.3 for Hard Clear, HCing it will give 11.3 points.
|
||||
|
||||
BP is not taken into account, and the only lamps that give rating are Normal, Hard and EXHard.
|
||||
|
||||
!!! note
|
||||
In the scenario where, say, a NC is worth more than a HC, HCing the chart will give the NC rating.
|
||||
|
||||
*****
|
||||
|
||||
### VF6 (SDVX, USC)
|
||||
|
||||
- **Default for SDVX and USC**
|
||||
|
||||
Pros:
|
||||
|
||||
- Built-in to the game, and understood by all players.
|
||||
- Unlike VF4, doesn't massively reward fails on high level charts
|
||||
- Unlike VF5, has more than 33 unique values.
|
||||
|
||||
Cons:
|
||||
|
||||
- Values on an individual score are small decimals, which can be difficult to parse.
|
||||
|
||||
VF6 (Volforce 6) is the Volforce algorithm used in SDVX 6.
|
||||
This algorithm is identical to VF5, but with the addition
|
||||
of another decimal place. This fixes a long standing issue
|
||||
with VF5 where there were only 33 possible values for a
|
||||
given score, which made the function painfully discrete.
|
||||
|
||||
!!! note
|
||||
VF5 and VF4 are deprecated in Tachi, and not displayed
|
||||
anywhere.
|
||||
|
||||
VF5 is deprecated because VF6 is strictly better.
|
||||
|
||||
VF4 is deprecated because it's 4 years old at this
|
||||
point, and its flaws make it incredibly abusable.
|
||||
|
||||
An implementation of them is still in the
|
||||
codebase, but is unused and commented out.
|
||||
|
||||
*****
|
||||
|
||||
### ktRating (DDR)
|
||||
|
||||
- **Default for DDR SP and DDR DP**
|
||||
|
||||
Pros:
|
||||
|
||||
- Works on all scores.
|
||||
- Takes tierlists into account.
|
||||
|
||||
Cons:
|
||||
|
||||
- Isn't good at evaluating scores at all.
|
||||
|
||||
In a similar vein to [IIDX's KTRating](#ktrating-iidx),
|
||||
we need a generic rating algorithm for DDR's scores.
|
||||
|
||||
However, I don't play DDR at all, and have no idea
|
||||
of how to properly rate scores.
|
||||
|
||||
!!! help
|
||||
If you want to help out with Tachi, have a bit
|
||||
of maths knowledge and also play DDR, feel free
|
||||
to make an issue describing a new default DDR
|
||||
algorithm.
|
||||
|
||||
*****
|
||||
|
||||
### MFCP (DDR)
|
||||
|
||||
Pros:
|
||||
|
||||
- Understood by the community because of its inclusion in [LIFE4](https://life4ddr.com/).
|
||||
|
||||
Cons:
|
||||
|
||||
- Too Discrete.
|
||||
- Only applies to a very small subset of scores.
|
||||
|
||||
MFCP (Marvelous Full Combo Points) are a scoring system
|
||||
used by LIFE4 for its challenges. As the name implies,
|
||||
you only get points for MFCs on a chart. And of that, the
|
||||
chart must be DIFFICULT or higher, and rated higher than
|
||||
level 8.
|
||||
|
||||
As such, it only applies to a rather small subset of
|
||||
players and an even smaller subset of their scores.
|
||||
|
||||
*****
|
||||
|
||||
### KtRating (maimai)
|
||||
|
||||
- **Default for maimai**
|
||||
|
||||
Pros:
|
||||
|
||||
- Works for all scores.
|
||||
- Doesn't reward weak passes on high rated charts.
|
||||
- Takes tierlists into account.
|
||||
|
||||
Cons:
|
||||
|
||||
- Not well understood by players.
|
||||
- Not amazingly accurate when it comes to high acc scores.
|
||||
- Hypothetically broken by charts with lots (lots) of breaks.
|
||||
|
||||
In a similar vein to [IIDX's KTRating](#ktrating-iidx),
|
||||
we need a generic rating algorithm for maimai's scores.
|
||||
|
||||
Maimai has a built-in rating algorithm, but it is not
|
||||
implemented, nor is it known to me how it works.
|
||||
|
||||
This is an adapted version of IIDX's KTRating to work
|
||||
for maimai. Since maimai is a very accuracy oriented game,
|
||||
it punishes low-accuracy scores heavily.
|
||||
|
||||
Scores below 90% are heavily nerfed. Timing is rewarded
|
||||
significantly. Clear type is ignored.
|
||||
|
||||
### KtRating (MÚSECA)
|
||||
|
||||
- **Default for MÚSECA**
|
||||
|
||||
Pros:
|
||||
|
||||
- Works on all scores.
|
||||
- Doesn't reward weak passes on high rated charts.
|
||||
- Takes tierlists into account.
|
||||
|
||||
Cons:
|
||||
|
||||
- Not well understood by players.
|
||||
|
||||
In a similar vein to [IIDX's KTRating](#ktrating-iidx),
|
||||
we need a generic rating algorithm for MÚSECA's scores.
|
||||
|
||||
MÚSECA's built-in CURATOR RANK is built in a similar
|
||||
vein to Volforce, but is broken by some
|
||||
questionable chart rating decisions. This makes it
|
||||
undesirable for score comparison.
|
||||
|
||||
This is an adapted version of the IIDX KTRating
|
||||
algorithm (again!), with parameters tuned for MÚSECA.
|
||||
|
||||
Scores below 900k are heavily nerfed. Timing is rewarded
|
||||
significantly. Clear type is ignored.
|
||||
|
||||
### Sieglinde (BMS)
|
||||
|
||||
- **Default for BMS 7K and BMS 14K**
|
||||
|
||||
Pros:
|
||||
|
||||
- Derives how difficult a lamp is to get by scores on LR2IR and Mocha IR.
|
||||
- An update to walkure without certain vulnerabilities.
|
||||
- Only gives rating for popular tables.
|
||||
|
||||
Cons:
|
||||
|
||||
- Does not support Groove Clears, EX Hard Clears or FCs.
|
||||
- Likely contentious for individual difference stuff. (What algorithm isn't!)
|
||||
|
||||
Sieglinde is a modern implementation of Walkure which
|
||||
aims to fix a couple of issues with Walkure as an
|
||||
individual score rating algorithm.
|
||||
|
||||
It uses data from IRs to derive an EC and HC value for
|
||||
a chart, which is then given if you get EC or HC
|
||||
respectively.
|
||||
|
||||
!!! note
|
||||
Due to poor data on LR2IR, and complete lack of support
|
||||
in LR2, Groove Clears and EX Hard Clears will be
|
||||
treated as Easy Clears and Hard Clears, respectively.
|
||||
|
||||
Full Combos are similarly removed due to incredibly
|
||||
poor data. Grinding Full Combos on low level insane
|
||||
charts was the best way to raise your walkure!
|
||||
|
||||
!!! info
|
||||
For 7K, the currently supported tables are:
|
||||
|
||||
- Insane1
|
||||
- Insane2
|
||||
- Normal1
|
||||
- Normal2
|
||||
- Satellite
|
||||
- Stella
|
||||
- Overjoy
|
||||
|
||||
For 14K, the currently supported tables are:
|
||||
|
||||
- Normal
|
||||
- Insane
|
||||
- Satellite
|
||||
|
||||
### Rating (CHUNITHM)
|
||||
|
||||
- **Default for CHUNITHM**
|
||||
|
||||
Pros:
|
||||
|
||||
- Built-in to the game.
|
||||
- Universally understood by players.
|
||||
|
||||
Cons:
|
||||
|
||||
- ???
|
||||
|
||||
Rating refers to CHUNITHM's built-in rating algorithm.
|
||||
This takes into account an internal tierlist, and looks
|
||||
at the accuracy of the provided score.
|
||||
|
||||
!!! note
|
||||
I don't play CHUNITHM, so I don't really know the
|
||||
implementation flaws of this algorithm for individual
|
||||
scores.
|
||||
|
||||
I imagine it probably overrates fails on high level
|
||||
charts.
|
||||
|
||||
### Skill (Gitadora)
|
||||
|
||||
- **Default for GITADORA**
|
||||
|
||||
Pros:
|
||||
|
||||
- Built-in to the game.
|
||||
- Universally understood by players.
|
||||
|
||||
Cons:
|
||||
|
||||
- Algorithm is very naive, and doesn't increase exponentially with respect to accuracy.
|
||||
|
||||
Gitadora's Skill algorithm is the in-game algorithm
|
||||
for determining a players 'skill' on a given chart.
|
||||
|
||||
The algorithm itself is incredibly simple, and likely
|
||||
breaks horribly for a lot of scenarios, but, it works
|
||||
decently for what it is.
|
||||
|
||||
!!! note
|
||||
Unlike almost every other algorithm on this page,
|
||||
the GITADORA Skill algorithm can be expressed in a single line.
|
||||
|
||||
$$
|
||||
f(p,l) = 0.01p * 20l
|
||||
$$
|
||||
|
||||
Where P is the users percent, and L is the level
|
||||
of the chart.
|
||||
|
||||
*****
|
||||
|
||||
## Profile Statistics
|
||||
|
||||
This section explains the statistics on a user's profile.
|
||||
These are almost always derived from individual score
|
||||
statistics as listed above.
|
||||
|
||||
*****
|
||||
|
||||
### IIDX
|
||||
|
||||
The below statistics apply to SP and DP.
|
||||
|
||||
- BPI
|
||||
|
||||
The average of your highest 20 BPIs.
|
||||
|
||||
- KtRating **(Default)**
|
||||
|
||||
The average of your highest 20 ktRatings.
|
||||
|
||||
- KtLampRating
|
||||
|
||||
The average of your highest 20 ktLampRatings.
|
||||
|
||||
*****
|
||||
|
||||
### SDVX, USC
|
||||
|
||||
The below statistics apply to both SDVX and USC.
|
||||
|
||||
- VF6 **(Default)**
|
||||
|
||||
Your highest 50 VF6s added together. This has the benefit
|
||||
of making VF6 not a small decimal, and is generally how
|
||||
people talk about their volforce.
|
||||
|
||||
*****
|
||||
|
||||
### DDR
|
||||
|
||||
The below statistics apply to both SP and DP.
|
||||
|
||||
- MFCP
|
||||
|
||||
**ALL** of your MFCP added together.
|
||||
|
||||
- KtRating **(Default)**
|
||||
|
||||
The average of your highest 20 ktRatings.
|
||||
|
||||
*****
|
||||
|
||||
### maimai, museca
|
||||
|
||||
The below statistics apply to maimai and museca.
|
||||
|
||||
- KtRating **(Default)**
|
||||
|
||||
The average of your highest 20 ktRatings.
|
||||
|
||||
*****
|
||||
|
||||
### BMS
|
||||
|
||||
- Sieglinde **(Default)**
|
||||
|
||||
The average of your best 50 Sieglinde scores.
|
||||
|
||||
*****
|
||||
### CHUNITHM
|
||||
|
||||
- Naive Rating **(Default)**
|
||||
|
||||
The average of your highest 20 CHUNITHM Ratings.
|
||||
|
||||
!!! warning
|
||||
This is *different* to what you'd expect! CHUNITHM
|
||||
has a built-in profile rating mechanism, but it has
|
||||
some awful flaws with respect to losing rating after
|
||||
playing poorly, and is generally *very* hard to
|
||||
implement.
|
||||
|
||||
*****
|
||||
|
||||
### GITADORA
|
||||
|
||||
- Skill **(Default)**
|
||||
|
||||
Your profile skill as it appears in game. This is the
|
||||
sum of all your skills on 50 HOT songs and 50 'NOT HOT'
|
||||
songs.
|
||||
|
||||
!!! info
|
||||
This might be slightly different to your in-game
|
||||
skill. This may be due to rerates or things like
|
||||
certain songs no longer being hot.
|
||||
|
||||
*****
|
||||
|
||||
## Session Ratings
|
||||
|
||||
This section explains the statistics on a session.
|
||||
These are almost always derived from individual score
|
||||
statistics as listed above.
|
||||
|
||||
If a session has less than 10 scores, all of the below
|
||||
statistics are marked as N/A[^1] except for MFCP.
|
||||
|
||||
*****
|
||||
|
||||
### IIDX
|
||||
|
||||
The below information applies to SP and DP.
|
||||
|
||||
- BPI, KtRating, KtLampRating
|
||||
|
||||
The average of your highest 10 values for that statistic.
|
||||
|
||||
*****
|
||||
|
||||
### SDVX, USC
|
||||
|
||||
- VF6
|
||||
|
||||
The average of your highest 10 VF6's that session.
|
||||
|
||||
- Profile VF6 **(Default)**
|
||||
|
||||
The above statistic, but multiplied by 50. This is
|
||||
to scale it up to what you'd normally see on a profile.
|
||||
|
||||
The reason for this multiplication is that, generally,
|
||||
people don't like dealing with decimals. Furthermore,
|
||||
SDVX players generally talk about their profile volforce,
|
||||
rather than their individual volforce.
|
||||
|
||||
*****
|
||||
|
||||
### DDR
|
||||
|
||||
The below information applies to SP and DP.
|
||||
|
||||
- MFCP
|
||||
|
||||
The total MFCP you achieved this session.
|
||||
|
||||
!!! warning
|
||||
Unlike all other statistics, this is not marked
|
||||
as N/A if you have less than 10 scores.
|
||||
|
||||
- KtRating **(Default)**
|
||||
|
||||
The average of the highest 10 KtRatings that session.
|
||||
|
||||
*****
|
||||
|
||||
### maimai, MÚSECA
|
||||
|
||||
- KtRating **(Default)**
|
||||
|
||||
The average of the highest 10 KtRatings that session.
|
||||
|
||||
### BMS
|
||||
|
||||
The below information applies to both 7K and 14K.
|
||||
|
||||
- Sieglinde **(Default)**
|
||||
|
||||
The average of the highest 10 Sieglinde ratings that session.
|
||||
|
||||
### CHUNITHM
|
||||
|
||||
- Naive Rating **(Default)**
|
||||
|
||||
The average of the highest 10 ratings that session.
|
||||
|
||||
### GITADORA
|
||||
|
||||
- Skill **(Default)**
|
||||
|
||||
The average of the highest 10 skills achieved that session.
|
||||
|
||||
!!! note
|
||||
Unlike SDVX, we do not have a ProfileSkill here.
|
||||
There's no good reason for this, though.
|
||||
|
||||
If people want it, it can be added! Feel
|
||||
free to report it as an issue if you think it
|
||||
should be added.
|
||||
|
||||
[^1]: Internally, they are marked as `null`.
|
||||
@@ -0,0 +1,20 @@
|
||||
# What are Lamps?
|
||||
|
||||
Lamps are a term borrowed mainly from Arcade rhythm games.
|
||||
|
||||
Their name is derived from the visual implementation in
|
||||
IIDX and DDR, where a lamp would light up a specific
|
||||
colour depending on your, well, lamp.
|
||||
|
||||
A synonym for this that might make more sense is "clear type", and some games use this name internally.
|
||||
|
||||
*****
|
||||
|
||||
## Examples
|
||||
|
||||
In most games, you have a "FULL COMBO", which means you
|
||||
didn't miss. That would be a different lamp to a "CLEAR",
|
||||
which would be - well - a clear.
|
||||
|
||||
Another common lamp is "FAILED", which is exactly what
|
||||
you think it would be.
|
||||
@@ -0,0 +1,16 @@
|
||||
window.MathJax = {
|
||||
tex: {
|
||||
inlineMath: [["\\(", "\\)"]],
|
||||
displayMath: [["\\[", "\\]"]],
|
||||
processEscapes: true,
|
||||
processEnvironments: true,
|
||||
},
|
||||
options: {
|
||||
ignoreHtmlClass: ".*|",
|
||||
processHtmlClass: "arithmatex",
|
||||
},
|
||||
};
|
||||
|
||||
document$.subscribe(() => {
|
||||
MathJax.typesetPromise();
|
||||
});
|
||||
+38
-1
@@ -29,6 +29,14 @@ nav:
|
||||
- "user/overview.md"
|
||||
- "user/rules.md"
|
||||
- "user/games.md"
|
||||
- "user/features.md"
|
||||
|
||||
- Terminology:
|
||||
- "user/terminology/lamps.md"
|
||||
|
||||
- Statistics:
|
||||
- "user/stats/tachi.md"
|
||||
- "user/stats/esd.md"
|
||||
|
||||
- API Reference:
|
||||
- "api/overview.md"
|
||||
@@ -39,6 +47,9 @@ nav:
|
||||
- "api/routes/status.md"
|
||||
- "api/routes/import.md"
|
||||
- "api/routes/auth.md"
|
||||
- "api/routes/users.md"
|
||||
- "api/routes/user-gamept.md"
|
||||
|
||||
- Codebase Reference:
|
||||
- "codebase/overview.md"
|
||||
- "codebase/contributing.md"
|
||||
@@ -51,11 +62,15 @@ nav:
|
||||
- "codebase/infrastructure/toolchain.md"
|
||||
- "codebase/infrastructure/logging.md"
|
||||
- "codebase/infrastructure/branches.md"
|
||||
- "codebase/infrastructure/testing.md"
|
||||
- "codebase/infrastructure/versions.md"
|
||||
|
||||
- Structure:
|
||||
- "codebase/structure/style.md"
|
||||
- "codebase/structure/filesystem.md"
|
||||
- "codebase/structure/testing.md"
|
||||
|
||||
- BATCH-MANUAL:
|
||||
- "codebase/batch-manual/overview.md"
|
||||
|
||||
- Score Importing:
|
||||
- "codebase/import/overview.md"
|
||||
@@ -73,6 +88,19 @@ nav:
|
||||
- "codebase/import/milestones.md"
|
||||
- "codebase/import/import-doc-time.md"
|
||||
|
||||
- Implementation Details:
|
||||
- "codebase/implementation-details/details.md"
|
||||
- "codebase/implementation-details/search.md"
|
||||
- "codebase/implementation-details/statistics.md"
|
||||
- "codebase/implementation-details/songs-charts.md"
|
||||
- "codebase/implementation-details/game-configuration.md"
|
||||
- "codebase/implementation-details/esd.md"
|
||||
|
||||
- Documents:
|
||||
- "codebase/documents/overview.md"
|
||||
- "codebase/documents/user.md"
|
||||
- "codebase/documents/score.md"
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.highlight
|
||||
@@ -80,3 +108,12 @@ markdown_extensions:
|
||||
- abbr
|
||||
- pymdownx.snippets
|
||||
- footnotes
|
||||
- toc:
|
||||
toc_depth: 2
|
||||
permalink: true
|
||||
- pymdownx.arithmatex:
|
||||
generic: true
|
||||
|
||||
extra_javascript:
|
||||
- https://polyfill.io/v3/polyfill.min.js?features=es6
|
||||
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
|
||||
Reference in New Issue
Block a user