feat: folder goal exemptions for metrics with chartDependentMax (#1591)

* feat: conditional chartDependentMax

* requested fixes

* run fix again
This commit is contained in:
nairobi
2026-05-26 22:27:55 +01:00
committed by GitHub
parent f7a973ca28
commit 63a7a5d43b
5 changed files with 51 additions and 3 deletions
+1 -1
View File
@@ -355,7 +355,7 @@ export function ValidateMetric(gameConfig: GameConfig, metricName: string, metri
return "Cannot validate a graph or nullable graph metric.";
}
if (conf.chartDependentMax) {
if (conf.chartDependentMax === true) {
return `This metric is chart dependent and not appropriate to check in this context.`;
}
@@ -1,3 +1,4 @@
import { p } from "prudence";
import { z } from "zod";
import type { INTERNAL_GAME_CONFIG, INTERNAL_GAME_GROUP_CONFIG } from "../../types/internals";
@@ -48,6 +49,8 @@ export const GAME_ARCAEA_CONF = {
score: {
type: "INTEGER",
chartDependentMax: true,
validate: p.isPositiveInteger,
allowFolderGoalsIf: (v: number) => v < 10_000_000,
formatter: FmtNum,
description:
"The score value. This is between 0 and 10 million, plus bonus points dependent on how many shiny PUREs you get.",
@@ -1,3 +1,4 @@
import { p } from "prudence";
import { z } from "zod";
import type { INTERNAL_GAME_CONFIG, INTERNAL_GAME_GROUP_CONFIG } from "../../types/internals";
@@ -62,6 +63,8 @@ export const GAME_MAIMAI_CONF = {
percent: {
type: "DECIMAL",
chartDependentMax: true,
allowFolderGoalsIf: (v) => v >= 0 && v < 100.0,
validate: p.isPositive,
formatter: FmtPercent,
description:
"The percent this score was worth. Sometimes called 'rate' in game. This is upper-bounded by how many BREAK notes the chart has.",
+38
View File
@@ -45,6 +45,7 @@ interface ConfDecimalScoreMetricChartDependent {
* @example: IIDX's EX Score is upperbounded at 2x the chart's notecount.
*/
chartDependentMax: true;
allowFolderGoalsIf?: never;
}
interface ConfIntegerScoreMetricChartDependent {
@@ -57,14 +58,51 @@ interface ConfIntegerScoreMetricChartDependent {
* @example: IIDX's EX Score is upperbounded at 2x the chart's notecount.
*/
chartDependentMax: true;
allowFolderGoalsIf?: never;
}
interface ConfDecimalScoreMetricChartDependentWithExemption {
type: "DECIMAL";
formatter: (v: number) => string;
validate: (v: number) => string | true;
/**
* When the value is chart dependent,
* should folder-wide goals be allowed conditionally?
*
* @example:
* Arcaea's score is upperbounded at 10M+notecount, but scores below 10M
* behave as if the max was 10M.
*/
allowFolderGoalsIf: (v: number) => boolean;
chartDependentMax: true;
}
interface ConfIntegerScoreMetricChartDependentWithExemption {
type: "INTEGER";
formatter: (v: number) => string;
validate: (v: number) => string | true;
/**
* When the value is chart dependent,
* should folder-wide goals be allowed conditionally?
*
* @example:
* Arcaea's score is upperbounded at 10M+notecount, but scores below 10M
* behave as if the max was 10M.
*/
allowFolderGoalsIf: (v: number) => boolean;
chartDependentMax: true;
}
export type ConfDecimalScoreMetric =
| ConfDecimalScoreMetricChartDependent
| ConfDecimalScoreMetricChartDependentWithExemption
| ConfDecimalScoreMetricNormal;
export type ConfIntegerScoreMetric =
| ConfIntegerScoreMetricChartDependent
| ConfIntegerScoreMetricChartDependentWithExemption
| ConfIntegerScoreMetricNormal;
/**
@@ -268,7 +268,10 @@ export async function ValidateGoalChartsAndCriteria(
switch (config.type) {
case "DECIMAL":
case "INTEGER": {
if (config.chartDependentMax && charts.type !== "single") {
const allowFolderGoals =
config.chartDependentMax !== true || config.allowFolderGoalsIf?.(criteria.value);
if (!allowFolderGoals && charts.type !== "single") {
throw new Error(
`Creating ${criteria.key} goals on multiple charts where the maximum value is relative to the chart is a terrible idea, and has been disabled.`,
);
@@ -276,7 +279,7 @@ export async function ValidateGoalChartsAndCriteria(
let err;
if (config.chartDependentMax) {
if (!allowFolderGoals) {
const chart = await GetChartByIdForGame(game, charts.data as string);
if (!chart) {
@@ -288,6 +291,7 @@ export async function ValidateGoalChartsAndCriteria(
// @ts-expect-error this is fine leave me alone
err = gptImpl.chartSpecificValidators[criteria.key](criteria.value, chart);
} else {
// @ts-expect-error if allowFolderGoals is true, validate has to exist, and tsc's opinion has no weight here.
err = config.validate(criteria.value);
}