diff --git a/common/src/config/config-utils.ts b/common/src/config/config-utils.ts index 613db940b..e4842bf1b 100644 --- a/common/src/config/config-utils.ts +++ b/common/src/config/config-utils.ts @@ -2,7 +2,7 @@ * Utilities *specifically* for game/gpt configuration. */ import type { integer } from "../types"; -import type { ClassInfo } from "../types/_TEMPNAME-game-config-inner"; +import type { ClassInfo } from "../types/game-config"; export function NoDecimalPlace(value: number): string { return value.toFixed(0); diff --git a/common/src/config/game-support/bms.ts b/common/src/config/game-support/bms.ts index f9234473c..b0bf97f44 100644 --- a/common/src/config/game-support/bms.ts +++ b/common/src/config/game-support/bms.ts @@ -162,7 +162,19 @@ export const BMS_7K_CONF = { additionalMetrics: { ...FAST_SLOW_MAXCOMBO, - // TODO + bp: { type: "INTEGER" }, + gauge: { type: "DECIMAL" }, + gaugeHistory: { type: "GRAPH" }, + epg: { type: "INTEGER" }, + egr: { type: "INTEGER" }, + egd: { type: "INTEGER" }, + ebd: { type: "INTEGER" }, + epr: { type: "INTEGER" }, // + lpg: { type: "INTEGER" }, + lgr: { type: "INTEGER" }, + lgd: { type: "INTEGER" }, + lbd: { type: "INTEGER" }, + lpr: { type: "INTEGER" }, }, scoreRatingAlgs: { @@ -218,7 +230,7 @@ export const BMS_7K_CONF = { orderedJudgements: ["pgreat", "great", "good", "bad", "poor"], - supportedVersions: [], + chartSets: [], supportedTierlists: { "sgl-EC": { description: "Sieglinde Easy Clear ratings." }, diff --git a/common/src/config/game-support/chunithm.ts b/common/src/config/game-support/chunithm.ts index 7702a05c3..083a88f2c 100644 --- a/common/src/config/game-support/chunithm.ts +++ b/common/src/config/game-support/chunithm.ts @@ -90,7 +90,7 @@ export const CHUNITHM_SINGLE_CONF = { orderedJudgements: ["jcrit", "justice", "attack", "miss"], - supportedVersions: ["paradiselost"], + chartSets: ["Paradise Lost"], supportedTierlists: {}, diff --git a/common/src/config/game-support/gitadora.ts b/common/src/config/game-support/gitadora.ts index af8780a15..1161d44e1 100644 --- a/common/src/config/game-support/gitadora.ts +++ b/common/src/config/game-support/gitadora.ts @@ -102,7 +102,7 @@ export const GITADORA_GITA_CONF = { orderedJudgements: ["perfect", "great", "good", "ok", "miss"], - supportedVersions: ["Konaste"], + chartSets: ["Konaste"], supportedTierlists: {}, diff --git a/common/src/config/game-support/iidx.ts b/common/src/config/game-support/iidx.ts index 1bad6c74e..e2067140b 100644 --- a/common/src/config/game-support/iidx.ts +++ b/common/src/config/game-support/iidx.ts @@ -1,10 +1,9 @@ import { FAST_SLOW_MAXCOMBO } from "./_common"; -import { ExtractMetrics } from "../../types/metrics"; -import { ClassValue } from "../config-utils"; -import type { +import { INTERNAL_GAME_CONFIG, - INTERNAL_GPT_CONFIG as INTERNAL_GPT_CONFIG, + INTERNAL_GPT_CONFIG, } from "../../types/internals"; +import { ClassValue } from "../config-utils"; export const IIDX_CONF = { defaultPlaytype: "SP", @@ -159,7 +158,7 @@ export const IIDX_SP_CONF = { orderedJudgements: ["pgreat", "great", "good", "bad", "poor"], - supportedVersions: [ + chartSets: [ "3rd Style CS", "4th Style CS", "5th Style CS", diff --git a/common/src/config/game-support/itg.ts b/common/src/config/game-support/itg.ts index d05ffc80b..eedc7a732 100644 --- a/common/src/config/game-support/itg.ts +++ b/common/src/config/game-support/itg.ts @@ -92,7 +92,7 @@ export const ITG_STAMINA_CONF = { orderedJudgements: ["15ms", "fantastic", "excellent", "great", "decent", "wayoff", "miss"], - supportedVersions: [], + chartSets: [], supportedTierlists: {}, diff --git a/common/src/config/game-support/jubeat.ts b/common/src/config/game-support/jubeat.ts index 78751fd9f..deb005ff4 100644 --- a/common/src/config/game-support/jubeat.ts +++ b/common/src/config/game-support/jubeat.ts @@ -86,14 +86,14 @@ export const JUBEAT_SINGLE_CONF = { orderedJudgements: ["perfect", "great", "good", "poor", "miss"], - supportedVersions: [ + chartSets: [ "jubeat", "ripples", "knit", "copious", "saucer", "prop", - "qubell", + "Qubell", "clan", "festo", ], diff --git a/common/src/config/game-support/maimai-dx.ts b/common/src/config/game-support/maimai-dx.ts index 6f4543888..1b57f8b79 100644 --- a/common/src/config/game-support/maimai-dx.ts +++ b/common/src/config/game-support/maimai-dx.ts @@ -155,7 +155,7 @@ export const MAIMAI_DX_SINGLE_CONF = { orderedJudgements: ["pcrit", "perfect", "great", "good", "miss"], - supportedVersions: ["universeplus"], + chartSets: ["universeplus"], supportedTierlists: {}, diff --git a/common/src/config/game-support/museca.ts b/common/src/config/game-support/museca.ts index dedf4f24a..df70e3354 100644 --- a/common/src/config/game-support/museca.ts +++ b/common/src/config/game-support/museca.ts @@ -67,18 +67,13 @@ export const MUSECA_SINGLE_CONF = { difficultyOrder: ["Green", "Yellow", "Red"], difficultyShorthand: { Green: "G", Yellow: "Y", Red: "R" }, defaultDifficulty: "Red", - difficultyColours: { - Green: COLOUR_SET.green, - Yellow: COLOUR_SET.vibrantYellow, - Red: COLOUR_SET.red, - }, }, supportedClasses: {}, orderedJudgements: ["critical", "near", "miss"], - supportedVersions: ["1 + 1/2", "1 + 1/2 Rev. B"], + chartSets: ["1 + 1/2", "1 + 1/2 Rev. B"], supportedTierlists: {}, diff --git a/common/src/config/game-support/pms.ts b/common/src/config/game-support/pms.ts index 3c8614589..67dfd5646 100644 --- a/common/src/config/game-support/pms.ts +++ b/common/src/config/game-support/pms.ts @@ -112,7 +112,7 @@ export const PMS_CONTROLLER_CONF = { orderedJudgements: ["cool", "great", "good", "bad", "poor"], - supportedVersions: [], + chartSets: [], supportedTierlists: { "sgl-EC": { description: "Sieglinde Easy Clear ratings." }, diff --git a/common/src/config/game-support/popn.ts b/common/src/config/game-support/popn.ts index df7061022..bb35ad324 100644 --- a/common/src/config/game-support/popn.ts +++ b/common/src/config/game-support/popn.ts @@ -110,7 +110,7 @@ export const POPN_9B_CONF = { orderedJudgements: ["cool", "great", "good", "bad"], - supportedVersions: ["peace", "kaimei"], + chartSets: ["peace", "Kaimei Riddles"], supportedTierlists: {}, diff --git a/common/src/config/game-support/sdvx.ts b/common/src/config/game-support/sdvx.ts index 2c9205902..9d7899927 100644 --- a/common/src/config/game-support/sdvx.ts +++ b/common/src/config/game-support/sdvx.ts @@ -145,7 +145,7 @@ export const SDVX_SINGLE_CONF = { orderedJudgements: ["critical", "near", "miss"], - supportedVersions: [ + chartSets: [ "BOOTH", "infinite infection", "GRAVITY WARS", diff --git a/common/src/config/game-support/usc.ts b/common/src/config/game-support/usc.ts index 35a363566..65445a160 100644 --- a/common/src/config/game-support/usc.ts +++ b/common/src/config/game-support/usc.ts @@ -78,7 +78,7 @@ export const USC_CONTROLLER_CONF = { orderedJudgements: ["critical", "near", "miss"], - supportedVersions: [], + chartSets: [], supportedTierlists: {}, diff --git a/common/src/config/game-support/wacca.ts b/common/src/config/game-support/wacca.ts index ea6385c80..39471c501 100644 --- a/common/src/config/game-support/wacca.ts +++ b/common/src/config/game-support/wacca.ts @@ -123,7 +123,7 @@ export const WACCA_SINGLE_CONF = { orderedJudgements: ["marvelous", "great", "good", "miss"], - supportedVersions: ["REVERSE"], + chartSets: ["REVERSE"], supportedTierlists: {}, diff --git a/common/src/config/old-config.ts b/common/src/config/old-config.ts index ef6cdc559..9bfc984de 100644 --- a/common/src/config/old-config.ts +++ b/common/src/config/old-config.ts @@ -1,6 +1,10 @@ /* eslint-disable lines-around-comment */ -import { FormatSieglindeBMS, FormatSieglindePMS, FormatMaimaiDXRating } from "./config-utils"; import { COLOUR_SET } from "../constants/colour-set"; +import { + FormatSieglindeBMS, + FormatSieglindePMS, + FormatMaimaiDXRating, +} from "./config-utils"; import type { GPTTierlists } from ".."; import type { ClassInfo } from "./game-classes"; diff --git a/common/src/config/versions.ts b/common/src/config/versions.ts deleted file mode 100644 index 39f5ecec9..000000000 --- a/common/src/config/versions.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { GetGameConfig } from "./old-config"; -import type { IDStrings, GPTSupportedVersions, Game, Playtype } from "../types"; - -export type Versions = { - [I in IDStrings]: { - [K in GPTSupportedVersions[I]]: string; - }; -}; - -const prettyGitadoraVersions: Versions["gitadora:Dora"] = { - konaste: "Konaste", -}; - -// WHEN YOU UPDATE THIS, **MAKE SURE** YOU UPDATE CONFIG.TS ACCORDINGLY. -export const PrettyVersions: Versions = { - "popn:9B": { peace: "peace", kaimei: "Kaimei Riddles" }, - "bms:7K": {}, - "bms:14K": {}, - "chunithm:Single": { - paradiselost: "Paradise Lost", - }, - "gitadora:Gita": prettyGitadoraVersions, - "gitadora:Dora": prettyGitadoraVersions, - "museca:Single": { - 1.5: "1 + 1/2", - "1.5-b": "1 + 1/2 Rev. B", - }, - "sdvx:Single": { - booth: "BOOTH", - inf: "Infinite Infection", - gw: "GRAVITY WARS", - heaven: "HEAVENLY HAVEN", - vivid: "VIVID WAVE", - exceed: "EXCEED GEAR", - konaste: "Konaste", - }, - "usc:Keyboard": {}, - "usc:Controller": {}, - "wacca:Single": { - reverse: "REVERSE", - }, - "jubeat:Single": { - festo: "festo", - clan: "clan", - qubell: "Qubell", - saucer: "saucer", - prop: "prop", - copious: "copious", - knit: "knit", - ripples: "ripples", - jubeat: "jubeat", - }, - "pms:Controller": {}, - "pms:Keyboard": {}, - "itg:Stamina": {}, - "maimaidx:Single": { - universeplus: "UNiVERSE PLUS", - }, -}; - -export function PrettyFormatGameVersion( - idString: I, - version: GPTSupportedVersions[I] -) { - // lol - const [game] = idString.split(":") as [Game, Playtype]; - - const ver = PrettyVersions[idString][version]; - - const gameConfig = GetGameConfig(game); - - return `${gameConfig.name} (${ver})`; -} diff --git a/common/src/lib/esd.test.ts b/common/src/lib/esd.test.ts deleted file mode 100644 index 4e3a9ad78..000000000 --- a/common/src/lib/esd.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { CalculateESD, ESDCompare, PercentCompare } from "./esd"; -import { GetGamePTConfig } from "../config/old-config"; -import t from "tap"; - -function approx(t: Tap.Test, given: number, recieved: number, acc = 0.01) { - if (Math.abs(given - recieved) < acc) { - return t.pass(); - } - - return t.fail(`Expected ${given} to be within ${acc} of ${recieved}.`); -} - -const gptConfig = GetGamePTConfig("iidx", "SP"); - -if (!gptConfig.supportsESD) { - throw new Error(`IIDX:SP no longer supports esd, these tests won't work.`); -} - -const judgementWindows = gptConfig.judgementWindows; - -t.test("#CalculateESD", (t) => { - const jdg = judgementWindows; - - approx(t, CalculateESD(jdg, 0.8), 17.58); - approx(t, CalculateESD(jdg, 0), 200); - t.throws(() => CalculateESD(jdg, 0, true)); - - t.end(); -}); - -t.test("#ESDCompare", (t) => { - approx(t, ESDCompare(16, 14), 6.42); - approx(t, ESDCompare(16, 14, 1), ESDCompare(16, 14)); - approx(t, ESDCompare(0, 200, 1), -68.27); - - t.end(); -}); - -t.test("#PercentCompare", (t) => { - const jdg = judgementWindows; - - approx(t, PercentCompare(jdg, 0.99, 0.98), -5.53); - approx(t, PercentCompare(jdg, 0.99, 0.98), PercentCompare(jdg, 0.99, 0.98, 1)); - - t.end(); -}); diff --git a/common/src/lib/esd.ts b/common/src/lib/esd.ts deleted file mode 100644 index c7cf95488..000000000 --- a/common/src/lib/esd.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Cumulative Distribution Function - * https://en.wikipedia.org/wiki/Cumulative_distribution_function - */ -function cdf(x: number, mean: number, variance: number) { - return 0.5 * (1 + erf((x - mean) / Math.sqrt(2 * variance))); -} - -/** - * Error Function - * https://en.wikipedia.org/wiki/Error_function - */ -function erf(x: number) { - // save the sign of x - const sign = x >= 0 ? 1 : -1; - - const absX = Math.abs(x); - - // constants - const a1 = 0.254829592; - const a2 = -0.284496736; - const a3 = 1.421413741; - const a4 = -1.453152027; - const a5 = 1.061405429; - const p = 0.3275911; - - // A&S formula 7.1.26 - const t = 1.0 / (1.0 + p * absX); - const y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-absX * absX); - - return sign * y; // erf(-x) = -erf(x); -} - -function CDFBetween(lowBound: number, highBound: number, mean: number, variance: number) { - // Since the normal distribution is symmetrical, we want to double this, as the cut will only get one tail. - return 2 * (cdf(highBound, mean, variance) - cdf(lowBound, mean, variance)); -} - -// This is a direct port of ESD-JS to typescript. Maybe this should be released as its own module some day. - -const MEAN = 0; - -/** - * Gets the percent this score would roughly be, given a standard deviation. - * @param judgements - The judgements this game uses. - * @param stddev - The standard deviation to estimate the percent of. - * @param largestValue - The largest "value" of a judgement in this game. This lets us normalise results. - * @returns The percent this std. deviation would produce. - */ -function StdDeviationToPercent( - judgements: Array, - stddev: number, - largestValue: number -) { - let lastJudgeMSBorder = 0; - let prbSum = 0; - - for (const judge of judgements) { - const nVal = CDFBetween(lastJudgeMSBorder, judge.msBorder, MEAN, stddev ** 2) * judge.value; - - lastJudgeMSBorder = judge.msBorder; - prbSum = prbSum + nVal; - } - - prbSum = prbSum / largestValue; - - return prbSum; -} - -const ACCEPTABLE_ERROR = 0.001; -const MAX_ITERATIONS = 50; - -export interface ESDJudgementFormat { - name: string; - msBorder: number; - value: number; -} - -/** - * Given judgements and a percent, estimate the standard deviation needed to get a score - * with that percent. - * @param judgements - The judgements for this game. - * @param percent - The percent to estimate SD needed to get. - * @param errOnInaccuracy - Whether or whether not to throw if the estimate is not accurate enough. - * @returns - */ -export function CalculateESD( - judgements: Array, - percent: number, - errOnInaccuracy = false -): number { - if (percent > 1 || percent < 0 || Number.isNaN(percent)) { - throw new Error( - "(ESD) Invalid percent. Percent must be between 0 and 1, and also a number." - ); - } - - const largestValue = judgements.slice(0).sort((a, b) => b.value - a.value)[0]?.value; - - if (largestValue === undefined) { - throw new Error(`Empty array of judgements passed?`); - } - - // massive optimisation possible here by using better initial estimates with precalc'd table of values. - // until then, it's just kinda slow. - let minSD = 0; - let maxSD = 200; - let estSD = (minSD + maxSD) / 2; - - // So, fundamentally the function that takes SD and returns estimated percent is NOT invertible. - // as in, it is very literally not invertible. - // if you figure it out, let me know. - // until then; we make MAX_ITERATIONS attempts at finding a value within the acceptable range of error. - // the defaults for these are 100 for iterations, and 0.001 for error. - // for most of what i've tested, this has been fine. - 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; - } - - if (errOnInaccuracy) { - throw new Error(`(ESD-JS) Did not reach value within MAX_ITERATIONS (${MAX_ITERATIONS})`); - } - - return estSD; -} - -/** - * Compares two ESD values such that 1->2 produces a larger value than 101->102. - * @param baseESD - The first ESD to compare. - * @param compareESD - The second ESD to compare. - * @param cdeg - The degrees of confidence to use. This should be 1. - * @returns A number between -100 and 100. - */ -export function ESDCompare(baseESD: number, compareESD: number, cdeg = 1): number { - const CONFIDENCE_DEGREE = cdeg; - const BASE_CASE = CDFBetween(-1 * CONFIDENCE_DEGREE, CONFIDENCE_DEGREE, 0, 1) / 2; - - let inv = false; - let variance; - let bound; - - if (compareESD > baseESD) { - inv = true; - variance = compareESD ** 2; - bound = CONFIDENCE_DEGREE * baseESD; - } else { - variance = baseESD ** 2; - bound = CONFIDENCE_DEGREE * compareESD; - } - - const esdc = CDFBetween(-1 * bound, bound, 0, variance) / 2; - - let besdc = BASE_CASE - esdc; - - if (inv) { - besdc = besdc * -1; - } - - return besdc * 100; -} - -/** - * Converts two percents to ESD, then runs ESDCompare. - */ -export function PercentCompare( - judgements: Array, - baseP: number, - compareP: number, - cdeg = 1 -): number { - const e1 = CalculateESD(judgements, baseP); - const e2 = CalculateESD(judgements, compareP); - - return ESDCompare(e1, e2, cdeg); -} diff --git a/common/src/types/_TEMPNAME-game-config-inner.ts b/common/src/types/game-config.ts similarity index 95% rename from common/src/types/_TEMPNAME-game-config-inner.ts rename to common/src/types/game-config.ts index e974427e3..53a8757ad 100644 --- a/common/src/types/_TEMPNAME-game-config-inner.ts +++ b/common/src/types/game-config.ts @@ -1,3 +1,6 @@ +// Types related to game configuration. These are used by our `internal` game config +// types + export interface RatingAlgorithmConfig { /** * Write a short descrption for this rating algorithm. @@ -12,14 +15,6 @@ export interface RatingAlgorithmConfig { formatter?: (value: number) => string; } -export interface TierlistConfig { - /** - * Write a short description explaining what this tierlist is and what the values - * generally mean. - */ - description: string; -} - export interface ClassInfo { display: string; id: string; diff --git a/common/src/types/game-support.ts b/common/src/types/game-support.ts index 0c616b159..97f7f2cf2 100644 --- a/common/src/types/game-support.ts +++ b/common/src/types/game-support.ts @@ -1,5 +1,6 @@ import type { GAME_CONFIGS, GAME_PT_CONFIGS } from "../config/config"; -import type { FixedDifficulties } from "./_TEMPNAME-game-config-inner"; +import type { MatchTypes } from "./batch-manual"; +import type { ClassConfig, FixedDifficulties, RatingAlgorithmConfig } from "./game-config"; import type { ExtractEnumMetricNames, ExtractMetrics } from "./metrics"; import type { ExtractArrayElementType } from "./utils"; @@ -81,6 +82,10 @@ export type Judgements = { [G in GPTString]: ExtractArrayElementType; }; +export type ChartSets = { + [G in GPTString]: ExtractArrayElementType; +}; + export type ScoreRatingAlgorithms = { [G in GPTString]: keyof typeof GAME_PT_CONFIGS[G]["scoreRatingAlgs"]; }; @@ -186,7 +191,7 @@ export interface GamePTConfig { * The idea of additionalMetrics allow us to store useful metrics about scores * without necessitating that they exist on arrival. Incredibly convenient. */ - additionalMetrics: Record; + additionalMetrics: AdditionalMetrics[GPT]; /** * What rating algorithms may a score have attached onto it for this GPT? @@ -195,7 +200,7 @@ export interface GamePTConfig { * server config. By defining them here, the typesystem will enforce that you * implement them elsewhere. */ - scoreRatingAlgs: Record; + scoreRatingAlgs: Record; /** * What rating algorithms may a session have attached onto it for this GPT? @@ -204,7 +209,7 @@ export interface GamePTConfig { * server config. By defining them here, the typesystem will enforce that you * implement them elsewhere. */ - sessionRatingAlgs: Record; + sessionRatingAlgs: Record; /** * What rating algorithms may a profile have attached onto it for this GPT? @@ -224,7 +229,7 @@ export interface GamePTConfig { * server config. By defining them here, the typesystem will enforce that you * implement them elsewhere. */ - profileRatingAlgs: Record; + profileRatingAlgs: Record; /** * What classes may a profile have attached onto it for this GPT? @@ -242,21 +247,21 @@ export interface GamePTConfig { * * @note This should be one of the keys in scoreRatingAlgs. */ - defaultScoreRatingAlg: string; + defaultScoreRatingAlg: ScoreRatingAlgorithms[GPT]; /** * What's the default session rating algorithm for this GPT? * * @note This should be one of the keys in sessionRatingAlgs. */ - defaultSessionRatingAlg: string; + defaultSessionRatingAlg: SessionRatingAlgorithms[GPT]; /** * What's the default profile rating algorithm for this GPT1? * * @note This should be one of the keys in sessionRatingAlgs. */ - defaultProfileRatingAlg: string; + defaultProfileRatingAlg: ProfileRatingAlgorithms[GPT]; /** * How does this GPT handle difficulties? @@ -276,39 +281,27 @@ export interface GamePTConfig { * * These should be ordered from **best to worst**. */ - orderedJudgements: ReadonlyArray; + orderedJudgements: ReadonlyArray; /** - * What versions do we support for this GPT? + * What sets of charts do we have for this GPT? * - * Versions are the way tachi disambiguates cases (typically in arcade games) where + * Chart Sets are the way tachi disambiguates cases (typically in arcade games) where * a chart is modified. * For example, Rising in the Sun * (https://remywiki.com/Rising_in_the_Sun(original_mix)) - * was removed in IIDX 21 ("spada"), and revived in IIDX 27 with entirely different + * was removed in IIDX 21, and revived in IIDX 27 with entirely different * charts. Although these charts are completely different, * they use the same song and difficulty * so Rising in the Sun SP ANOTHER could mean two things!. * - * We need to handle these cases, so we disambiguate by attaching "versions" onto - * every chart. These "versions" indicate what sets of chart states they appeared in - * for this GPT. Then, when a score is coming in, it can indicate what version this - * score was from. That way, we can make sure they resolve to the right chart. + * We need to handle these cases, so we disambiguate by attaching "chart sets" onto + * every chart. These "chart sets" indicate what sets of chart states they + * appeared in for this GPT. Then, when a score is coming in, it can indicate what + * chartSet this score was on. That way, we can make sure they resolve to the right + * chart. */ - supportedVersions: ReadonlyArray; - - /** - * What tierlists does this GPT have? - * - * Tierlists are a way of attaching custom rating values onto a chart. - * Unlike "chart.data", which may be overloaded with anything, defined tierlists - * *must* fit a known schema, allowing the UI to visually render any tierlists - * defined here. - * - * This feature is a bit redundant given "chart.data", and should be further - * investigated. My bad. - */ - supportedTierlists: Record; + chartSets: ReadonlyArray; /** * What "matchTypes" should this game support for batch-manual imports? This @@ -317,8 +310,3 @@ export interface GamePTConfig { */ supportedMatchTypes: ReadonlyArray; } - -const gptConf: ExtractEnumMetricNames = { - lamp: "a", - asdf: 213, -}; diff --git a/common/src/types/internals.ts b/common/src/types/internals.ts index 3a91fc6fa..026923132 100644 --- a/common/src/types/internals.ts +++ b/common/src/types/internals.ts @@ -1,10 +1,5 @@ -import type { - RatingAlgorithmConfig, - ClassConfig, - DifficultyConfig, - TierlistConfig, -} from "./_TEMPNAME-game-config-inner"; import type { MatchTypes } from "./batch-manual"; +import type { RatingAlgorithmConfig, ClassConfig, DifficultyConfig } from "./game-config"; import type { ScoreMetric } from "./metrics"; /** @@ -15,197 +10,37 @@ import type { ScoreMetric } from "./metrics"; * * @see {GamePTConfig} for the intended-to-use type. This is an *outline* for a GPT * config, and has significantly less typesafety. + * + * Documentation for this type can be found in `game-support.ts`, which has the + * GamePTConfig type, which actually has documentation. */ export type INTERNAL_GPT_CONFIG = Readonly<{ - /** - * What metrics **must** be provided in order for this score to be usable by - * Tachi? - * - * This is intended for things like Score, Lamp, etc. Things that quite fundamentally - * *are* the metrics of the score. - */ providedMetrics: Record; - /** - * What metrics do we want to exist on score documents, but don't need to be - * provided? - * - * In simple terms, all of these metrics **MUST** be derivable by a DETERMINISTIC - * function of f(mandatoryMetrics, chartThisScoreWasOn). - * - * This is for convenience/efficiency mainly. A good example would be "percent" for - * IIDX. Technically, we could recalculate it every single time we want to display - * it by dividing score by chart.data.notecount * 2, but that's horrendously - * inefficient. - * - * Furthermore, since these things are derived deterministically, they only ever - * need to be recalculated in extreme circumstances (an IIDX chart has changed its - * notecounts!!!). If mandatory metrics were to change, it's just now a different - * score. - * - * Another good example would be "Grade" for most games, as a grade is often just - * cutoffs applied on score values. - */ derivedMetrics: Record; - /** - * What's the default metric for this GPT? - * - * This will be used to order leaderboard rankings. - * - * @note This **MUST** be one of the mandatory or derived keys. - */ defaultMetric: string; - /** - * What's the preferred default enum for this GPT? - * - * Enum types are used across the UI (think folder breakdown charts), and the game - * should generally declare a default. - * - * @note This **MUST** be one of the mandatory or derived keys. - */ preferredDefaultEnum: string; - /** - * What metrics *can* we store about scores, but don't necessarily *need*? - * - * Of course, in a perfect world we'd store all the metrics always all the time! - * But a lot of import methods (eamusement CSV, etc) would be filtered out by - * mandating the existence of a lot of these metrics. - * - * The idea of additionalMetrics allow us to store useful metrics about scores - * without necessitating that they exist on arrival. Incredibly convenient. - */ additionalMetrics: Record; - /** - * What rating algorithms may a score have attached onto it for this GPT? - * - * @note The implementations for these rating algorithms are handled in the - * server config. By defining them here, the typesystem will enforce that you - * implement them elsewhere. - */ scoreRatingAlgs: Record; - - /** - * What rating algorithms may a session have attached onto it for this GPT? - * - * @note The implementations for these rating algorithms are handled in the - * server config. By defining them here, the typesystem will enforce that you - * implement them elsewhere. - */ sessionRatingAlgs: Record; - - /** - * What rating algorithms may a profile have attached onto it for this GPT? - * - * @note This is **SPECIFICALLY** for numeric, calculatable metrics. This means - * that the metric *must* be calculatable *at all times* from the set of all - * scores this user has on this GPT. - * - * This is intended for numeric, continous data. - * If you want to store something with a fixed set of values, such as a user's - * "rating colour", use `supportedClasses`. - * - * If you want to store something that cannot be derived from the user's scores, - * such as their "Dan", use `supportedClasses`. - * - * @note The implementations for these rating algorithms are handled in the - * server config. By defining them here, the typesystem will enforce that you - * implement them elsewhere. - */ profileRatingAlgs: Record; - /** - * What classes may a profile have attached onto it for this GPT? - * - * Classes are a *fixed*, *ordered* set of values. - * They may be a function of existing state (like "rating colours", where a user - * gets a new discrete colour when they go up certain ratings), - * or they may be provided by score imports, such as "dans", which cannot be - * derived from a player's scores or profile ratings. - */ supportedClasses: Record; - /** - * What's the default score rating algorithm for this GPT? - * - * @note This should be one of the keys in scoreRatingAlgs. - */ defaultScoreRatingAlg: string; - - /** - * What's the default session rating algorithm for this GPT? - * - * @note This should be one of the keys in sessionRatingAlgs. - */ defaultSessionRatingAlg: string; - - /** - * What's the default profile rating algorithm for this GPT1? - * - * @note This should be one of the keys in sessionRatingAlgs. - */ defaultProfileRatingAlg: string; - /** - * How does this GPT handle difficulties? - * - * "Difficulties" are used to allow one song to have multiple charts. Some games - * may have a known set of possible difficulties, such as "Easy", "Normal" and - * "Hard". - * - * Other games may have an unknown set of possible difficulties, such as osu! - * allowing any string (as long as its unique.) - * - */ difficultyConfig: DifficultyConfig; - /** - * What judgements does this GPT have? These are typically timing-window names. - * - * These should be ordered from **best to worst**. - */ orderedJudgements: ReadonlyArray; - /** - * What versions do we support for this GPT? - * - * Versions are the way tachi disambiguates cases (typically in arcade games) where - * a chart is modified. - * For example, Rising in the Sun - * (https://remywiki.com/Rising_in_the_Sun(original_mix)) - * was removed in IIDX 21 ("spada"), and revived in IIDX 27 with entirely different - * charts. Although these charts are completely different, - * they use the same song and difficulty - * so Rising in the Sun SP ANOTHER could mean two things!. - * - * We need to handle these cases, so we disambiguate by attaching "versions" onto - * every chart. These "versions" indicate what sets of chart states they appeared in - * for this GPT. Then, when a score is coming in, it can indicate what version this - * score was from. That way, we can make sure they resolve to the right chart. - */ - supportedVersions: ReadonlyArray; + chartSets: ReadonlyArray; - /** - * What tierlists does this GPT have? - * - * Tierlists are a way of attaching custom rating values onto a chart. - * Unlike "chart.data", which may be overloaded with anything, defined tierlists - * *must* fit a known schema, allowing the UI to visually render any tierlists - * defined here. - * - * This feature is a bit redundant given "chart.data", and should be further - * investigated. My bad. - */ - supportedTierlists: Record; - - /** - * What "matchTypes" should this game support for batch-manual imports? This - * allows us to disable things like "songTitle" resolutions for games like BMS, - * where song titles are absolutely not guaranteed to be unique. - */ supportedMatchTypes: ReadonlyArray; }>; diff --git a/common/src/types/metrics.ts b/common/src/types/metrics.ts index e07fa0c21..42c2c14df 100644 --- a/common/src/types/metrics.ts +++ b/common/src/types/metrics.ts @@ -69,13 +69,9 @@ export type ExtractMetricType = M extends DecimalScoreMet * ExtractEnumMetricNames<{ score: { type: "INTEGER" } }, grade: { type: "ENUM" ... }> * would return a type of "grade" */ -export type ExtractEnumMetricNames> = Omit< - R, - keyof { - // vvvv this is stupid - [K in keyof R]: R[K] extends EnumScoreMetric ? never : K; - } ->; +export type ExtractEnumMetricNames> = { + [K in keyof R]: R[K] extends EnumScoreMetric ? K : never; +}[keyof R]; type PossibleMetrics = ExtractMetricType;