diff --git a/docs/docs/api/routes/user-gamept.md b/docs/docs/api/routes/user-gamept.md index af6761e09..ad7cfd356 100644 --- a/docs/docs/api/routes/user-gamept.md +++ b/docs/docs/api/routes/user-gamept.md @@ -225,7 +225,7 @@ GET /api/v1/users/zkldi/games/iidx/SP/pbs?search=Verfl `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. +to the [Default Rating Algorithm](../../codebase/implementation-details/game-configuration) for this game. This is returned in descending sorted order. diff --git a/docs/docs/codebase/documents/goal.md b/docs/docs/codebase/documents/goal.md new file mode 100644 index 000000000..686c9298b --- /dev/null +++ b/docs/docs/codebase/documents/goal.md @@ -0,0 +1,103 @@ +# Goal Document + +Goals are stored in `goals`. + +!!! warning + This document describes a goal, but does not describe + a user's "subscription" to that goal. For that, see + [User Goal Document](./user-goal.md). + +***** + +## Definition + +A base goal document is defined as follows. + +```ts +interface BaseGoalDocument { + game: Game; + playtype: Playtypes[Game]; + timeAdded: integer; + createdBy: integer; + title: string; + goalID: string; + criteria: GoalSingleCriteria | GoalCountCriteria; +} +``` + +| Property | Description | +| :: | :: | +| `game` | The game this goal is for. | +| `playtype` | The playtype this goal is for. Must be a valid playtype for the above game. | +| `timeAdded` | The time this goal was added to the database. | +| `createdBy` | The ID of the user that originally made this goal. | +| `title` | A humanised name for this goal. | +| `goalID` | A hash of the criteria and chart set for this goal. Used to de-dupe goals. | + +### Criteria + +Criteria is defined as follows: + +```ts +interface GoalCriteria { + key: "scoreData.percent" | "scoreData.lampIndex" | "scoreData.gradeIndex" | "scoreData.score"; + value: number; +} + +interface GoalSingleCriteria extends GoalCriteria { + mode: "single"; +} + +interface GoalCountCriteria extends GoalCriteria { + mode: "abs" | "proportion"; + countNum: number; +} +``` + +| Property | Description | +| :: | :: | +| `key` | Defines the key this goal is for - these correspond to keys in a [Score Document](./score.md). | +| `value` | The value this goal must hit in order for it to be achieved. What this value is interpreted as depends on `key`. | +| `mode` | If single, this just means the user only has to have one score that meets the above criteria on the defined set of charts. If abs or proportion, the user must hit `countNum` amount of scores on the set of charts (or that percent). | +| `countNum` | If mode is abs, this defines an absolute amount of scores the user must get. If proportion, the user must get this percent of the set of charts achieved. | + +### Charts + +The set of charts this goal applies to is added onto the +goal document under the `charts` key. + +There are four types of goal. + +```ts +interface GoalDocumentSingle extends BaseGoalDocument { + charts: { + type: "single"; + data: string; + }; +} + +interface GoalDocumentMulti extends BaseGoalDocument { + charts: { + type: "multi"; + data: string[]; + }; +} + +interface GoalDocumentFolder extends BaseGoalDocument { + charts: { + type: "folder"; + data: string; + }; +} + +interface GoalDocumentAny extends BaseGoalDocument { + charts: { + type: "any"; + }; +} +``` + +For single and multi, the data field is a chartID, or an +array of chartIDs respectively. + +For the folder type, the data field is a folderID. \ No newline at end of file diff --git a/docs/docs/codebase/documents/overview.md b/docs/docs/codebase/documents/overview.md index b98de63d1..5f697a259 100644 --- a/docs/docs/codebase/documents/overview.md +++ b/docs/docs/codebase/documents/overview.md @@ -12,6 +12,11 @@ shape of those documents. involve frequent links to this part of the codebase documentation. +!!! help + This section is a bit sparse, as I don't have the + time to populate it all. If you want to contribute + to this, please see [Contributing](../contributing.md). + ***** ## Definition diff --git a/docs/docs/codebase/documents/score.md b/docs/docs/codebase/documents/score.md index 5485286e4..de42a3e98 100644 --- a/docs/docs/codebase/documents/score.md +++ b/docs/docs/codebase/documents/score.md @@ -64,7 +64,7 @@ The base score document is structured as follows: | `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). | +| `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](../implementation-details/score-id.md). | | `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 diff --git a/docs/docs/codebase/documents/user-goal.md b/docs/docs/codebase/documents/user-goal.md new file mode 100644 index 000000000..c7d540cf9 --- /dev/null +++ b/docs/docs/codebase/documents/user-goal.md @@ -0,0 +1,41 @@ +# User Goal Document + +The user goal document represents a user's subscription +to a goal. + +!!! warning + This does not describe the goal or its criteria, + for that - see [Goal Document](./goal.md). + +***** + +## Definition + +```ts +interface UserGoalDocument { + goalID: string; + userID: integer; + game: Game; + playtype: Playtypes[Game]; + achieved: boolean; + timeSet: integer; + timeAchieved: integer | null; + lastInteraction: integer | null; + progress: number | null; + progressHuman: string; + outOf: number; + outOfHuman: string; +} +``` + +| Property | Description | +| :: | :: | +| `goalID` | This is the goal the user is subscribed to in this document. | +| `userID` | This is the user this goal subscription belongs to. | +| `game`, `playtype` | These fields are both *technically* redundant. However, for optimisation reasons, they are copied over from the goal document field. | +| `achieved` | Whether this goal has been achieved or not. | +| `timeSet` | The time the user set this goal. | +| `timeAchieved` | The time this user achieved this goal. If the user has not achieved this goal, it is set as `null`. | +| `progress` | The user's raw progress towards this goal. This is a number, and should not be displayed to the user. | +| `outOf` | The value this goal is out of - this is a number, and should not be displayed to the user. | +| `progressHuman`, `outOfHuman` | These are humanised, stringified versions of the above two fields. These convert things like the enum value of lamps to their string equivalents. | diff --git a/docs/docs/codebase/implementation-details/game-configuration.md b/docs/docs/codebase/implementation-details/game-configuration.md index 3c8c6b34c..586a2d3f2 100644 --- a/docs/docs/codebase/implementation-details/game-configuration.md +++ b/docs/docs/codebase/implementation-details/game-configuration.md @@ -10,8 +10,9 @@ 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. + This page is currently unformatted, + and is pretty much just a copy-paste of the + local configuration files. It would be really appreciated if someone formatted the configurations for every game! For the time being, diff --git a/docs/docs/codebase/implementation-details/goal-id.md b/docs/docs/codebase/implementation-details/goal-id.md new file mode 100644 index 000000000..66aa35f31 --- /dev/null +++ b/docs/docs/codebase/implementation-details/goal-id.md @@ -0,0 +1,18 @@ +# Goal ID implementation. + +Goal IDs exist to dedupe goals when a user creates +a new goal. For example, if a user wants to create a +HARD CLEAR Mei goal, but one already exists, we should +not insert two [Goal Documents](../documents/goal.md) +representing the same thing. + +***** + +## Hashing + +To create the Goal ID, we perform a [json stable hash](https://github.com/zkldi/fast-stable-json-hash) on the +`game`, `playtype`, `charts` and `criteria` of this field. + +This enforces that goals are unique on those fields. + +The above hash is then returned, prefixed with `G`. diff --git a/docs/docs/codebase/implementation-details/score-id.md b/docs/docs/codebase/implementation-details/score-id.md new file mode 100644 index 000000000..9c7a3a40f --- /dev/null +++ b/docs/docs/codebase/implementation-details/score-id.md @@ -0,0 +1,44 @@ +# Score ID implementation. + +Score IDs exist to dedupe scores when a user re-submits +the same scores. This happens frequently with `file/` +and `api/` [Import Types](../import/import-types.md), +as they typically resubmit the same scores. + +If a user only got a score once, we don't want to store +it twice. + +***** + +## Hashing + +The score ID is created by hashing the following template string: + +```ts +`${userID}|${chartID}|${dryScore.scoreData.lamp}|${dryScore.scoreData.grade}|${dryScore.scoreData.score}|${dryScore.scoreData.percent}` +``` + +with SHA256. + +This is then prefixed with `R`, and returned. + +## Why those fields? + +Games do a lot of wacky things. We'd rather not discard +non-duplicates, so we're a little more strict than we +maybe should be. + +UserID and chartID are so we don't accidentally collide +with other user's scores or charts. + +Score and Lamp make sense - since if those change we no +longer really have the same score. + +Grade and Percent are the slightly more strict ones. Some +games have very strange external grade requirements, such +as osu!standard enforcing a Full Combo for an S rank. + +Percent may not correlate with score. This was in anticipation +to support BMS's `#RANDOM` instruction - where charts may +have a dynamic amount of notes - but I've decided it +wasn't worth it. Still, this is left in for futureproofing. diff --git a/docs/docs/codebase/import/goals.md b/docs/docs/codebase/import/goals.md index 20397f3bc..cbae3038c 100644 --- a/docs/docs/codebase/import/goals.md +++ b/docs/docs/codebase/import/goals.md @@ -29,8 +29,6 @@ as a result of the import. This is calculated as follows: For every goal matched, we iterate over it and evaluate it using `EvaluateGoalForUser`. -The details of how this algorithm works (and goals in general work) can be found at [Goals](todo). - We then need to convert the returns of that function into the expected goal format for ImportDocuments. diff --git a/docs/docs/codebase/import/sessions.md b/docs/docs/codebase/import/sessions.md index b8e83b448..f061e3320 100644 --- a/docs/docs/codebase/import/sessions.md +++ b/docs/docs/codebase/import/sessions.md @@ -2,7 +2,7 @@ This page documents how Sessions are constructed. For a humanised explaination of sessions, see -TODO. +[this page](../../user/features.md#sessions). ***** diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8d2a169ec..fd5ccbba8 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -95,11 +95,15 @@ nav: - "codebase/implementation-details/songs-charts.md" - "codebase/implementation-details/game-configuration.md" - "codebase/implementation-details/esd.md" + - "codebase/implementation-details/score-id.md" + - "codebase/implementation-details/goal-id.md" - Documents: - "codebase/documents/overview.md" - "codebase/documents/user.md" - "codebase/documents/score.md" + - "codebase/documents/goal.md" + - "codebase/documents/user-goal.md" markdown_extensions: - admonition