mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-28 18:08:15 +03:00
feat: better arcaea judgement cell + ptt calculation fixes (#1589)
* feat: PM judgements + correct rating calc * apparently, I forgot SQL syntax * rg-stats test case
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
INSERT INTO score_rederive (chart_id)
|
||||
SELECT id as chart_id
|
||||
FROM chart
|
||||
WHERE chart.game = 'arcaea';
|
||||
@@ -1,36 +0,0 @@
|
||||
import { IsNullish } from "#util/misc";
|
||||
import React from "react";
|
||||
import { COLOUR_SET, type PBScoreDocument, type ScoreDocument } from "tachi-common";
|
||||
|
||||
export default function ArcaeaJudgementCell({
|
||||
score,
|
||||
}: {
|
||||
score: PBScoreDocument<"arcaea"> | ScoreDocument<"arcaea">;
|
||||
}) {
|
||||
// even if we dont have judgement data, we know what they got.
|
||||
if (score.scoreData.lamp === "PURE MEMORY") {
|
||||
return (
|
||||
<td>
|
||||
<strong>
|
||||
<span style={{ color: COLOUR_SET.vibrantYellow }}>0</span>-
|
||||
<span style={{ color: COLOUR_SET.red }}>0</span>
|
||||
</strong>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
const judgements = score.scoreData.judgements;
|
||||
|
||||
if (IsNullish(judgements.far) || IsNullish(judgements.lost)) {
|
||||
return <td>No Data.</td>;
|
||||
}
|
||||
|
||||
return (
|
||||
<td>
|
||||
<strong>
|
||||
<span style={{ color: COLOUR_SET.vibrantYellow }}>{judgements.far}</span>-
|
||||
<span style={{ color: COLOUR_SET.red }}>{judgements.lost}</span>
|
||||
</strong>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
import ArcaeaJudgementCell from "#components/tables/cells/ArcaeaJudgementCell";
|
||||
import QuickTooltip from "#components/layout/misc/QuickTooltip";
|
||||
import LampCell from "#components/tables/cells/LampCell";
|
||||
import MillionsScoreCell from "#components/tables/cells/MillionsScoreCell";
|
||||
import RatingCell from "#components/tables/cells/RatingCell";
|
||||
import { GetEnumColour } from "#lib/game-implementations";
|
||||
import { type GPTClientImplementation } from "#lib/types";
|
||||
import { FormatScoreRating, IsNullish } from "#util/misc";
|
||||
import { NumericSOV } from "#util/sorts";
|
||||
import React from "react";
|
||||
import { COLOUR_SET, type GamesForGroup } from "tachi-common";
|
||||
import {
|
||||
COLOUR_SET,
|
||||
type GamesForGroup,
|
||||
type PBScoreDocument,
|
||||
type ScoreDocument,
|
||||
} from "tachi-common";
|
||||
|
||||
import { bgc } from "./_util";
|
||||
|
||||
@@ -67,12 +72,62 @@ const ARCAEA_COLORS: GPTClientImplementation<GamesForGroup["arcaea"]>["classColo
|
||||
},
|
||||
};
|
||||
|
||||
// Returns 0 for MAX PM, -10 for MAX-10, and -10k if undefined/not applicable
|
||||
const MaxPureDelta = (sc: PBScoreDocument<"arcaea"> | ScoreDocument<"arcaea">) =>
|
||||
IsNullish(sc.scoreData.judgements.pure) || sc.scoreData.lamp !== "PURE MEMORY"
|
||||
? -10000
|
||||
: sc.scoreData.score - sc.scoreData.judgements.pure - 10_000_000;
|
||||
|
||||
const ARCAEA_SCORE_HEADERS: GPTClientImplementation<GamesForGroup["arcaea"]>["scoreHeaders"] = [
|
||||
["Score", "Score", NumericSOV((x) => x.scoreData.score)],
|
||||
["Far - Lost", "Far - Lost", NumericSOV((x) => x.scoreData.score)],
|
||||
[
|
||||
"Judgements",
|
||||
"Judgements",
|
||||
NumericSOV((sc) =>
|
||||
sc.scoreData.lamp !== "PURE MEMORY"
|
||||
? sc.scoreData.score - 20_000_000
|
||||
: MaxPureDelta(sc),
|
||||
),
|
||||
],
|
||||
["Lamp", "Lamp", NumericSOV((x) => x.scoreData.enumIndexes.lamp)],
|
||||
];
|
||||
|
||||
const ArcaeaJudgementCell = ({
|
||||
score,
|
||||
}: {
|
||||
score: PBScoreDocument<"arcaea"> | ScoreDocument<"arcaea">;
|
||||
}) => {
|
||||
const judgements = score.scoreData.judgements;
|
||||
|
||||
if (IsNullish(judgements.far) || IsNullish(judgements.lost)) {
|
||||
return <td>No Data.</td>;
|
||||
}
|
||||
|
||||
if (score.scoreData.lamp === "PURE MEMORY") {
|
||||
const delta = MaxPureDelta(score);
|
||||
return (
|
||||
<td>
|
||||
<strong>
|
||||
{delta < 0 ? (
|
||||
<span style={{ color: COLOUR_SET.blue }}>MAX{delta}</span>
|
||||
) : (
|
||||
<span style={{ color: COLOUR_SET.vibrantBlue }}>MAX</span>
|
||||
)}
|
||||
</strong>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<td>
|
||||
<strong>
|
||||
<span style={{ color: COLOUR_SET.vibrantYellow }}>{judgements.far}</span>-
|
||||
<span style={{ color: COLOUR_SET.red }}>{judgements.lost}</span>
|
||||
</strong>
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
const ArcaeaCoreCells: GPTClientImplementation<GamesForGroup["arcaea"]>["scoreCoreCells"] = ({
|
||||
sc,
|
||||
}) => (
|
||||
@@ -90,7 +145,16 @@ const ArcaeaCoreCells: GPTClientImplementation<GamesForGroup["arcaea"]>["scoreCo
|
||||
const ArcaeaRatingCell: GPTClientImplementation<GamesForGroup["arcaea"]>["ratingCell"] = ({
|
||||
sc,
|
||||
rating,
|
||||
}) => <RatingCell rating={rating} score={sc} />;
|
||||
}) => {
|
||||
const ptt = sc.calculatedData.potential ?? 0;
|
||||
return (
|
||||
<QuickTooltip tooltipContent={<div className="fs-5">{ptt.toFixed(6)}</div>}>
|
||||
<td>
|
||||
<div className="underline-on-hover">{FormatScoreRating("arcaea", rating, ptt)}</div>
|
||||
</td>
|
||||
</QuickTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ARCAEA_TOUCH_IMPL: GPTClientImplementation<"arcaea"> = {
|
||||
sessionImportantScoreCount: 30,
|
||||
|
||||
@@ -90,7 +90,7 @@ export const GAME_ARCAEA_CONF = {
|
||||
naivePotential: {
|
||||
description:
|
||||
"The average of your best 30 Potential values. This is different to the in-game algorithm, as it does not take your recent scores into account in any way.",
|
||||
formatter: ToDecimalPlaces(2),
|
||||
formatter: (v) => (Math.round(v * 100.0) / 100.0).toFixed(2),
|
||||
associatedScoreAlgs: ["potential"],
|
||||
},
|
||||
},
|
||||
@@ -117,6 +117,7 @@ export const GAME_ARCAEA_CONF = {
|
||||
badge: {
|
||||
type: "DERIVED",
|
||||
values: ArcaeaBadges,
|
||||
minimumRelevantValue: "RED",
|
||||
},
|
||||
courseBanner: {
|
||||
type: "PROVIDED",
|
||||
|
||||
@@ -17,6 +17,7 @@ test("Arcaea Potential Tests", () => {
|
||||
MakeTestCase(9_977_755, 11.3, 13.19),
|
||||
MakeTestCase(9_934_498, 11.1, 12.77),
|
||||
MakeTestCase(9_932_746, 10.9, 12.56),
|
||||
MakeTestCase(9_805_015, 11.6, 12.63),
|
||||
|
||||
MakeTestCase(10_000_000, 7.0, 9.0),
|
||||
MakeTestCase(9_900_000, 8.0, 9.5),
|
||||
|
||||
@@ -12,16 +12,15 @@ export function calculate(score: number, internalChartLevel: number) {
|
||||
level: internalChartLevel,
|
||||
});
|
||||
|
||||
const iclInt = Math.round(internalChartLevel * 100);
|
||||
let potential = 0;
|
||||
|
||||
if (score >= 10_000_000) {
|
||||
potential = iclInt + 200;
|
||||
potential = internalChartLevel + 2;
|
||||
} else if (score >= 9_800_000) {
|
||||
potential = iclInt + 100 + Math.floor((score - 9_800_000) / 2_000);
|
||||
potential = internalChartLevel + 1 + (score - 9_800_000) / 200_000;
|
||||
} else {
|
||||
potential = iclInt + Math.floor((score - 9_500_000) / 3_000);
|
||||
potential = internalChartLevel + (score - 9_500_000) / 300_000;
|
||||
}
|
||||
|
||||
return Math.max(potential / 100, 0);
|
||||
return Math.max(potential, 0);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ describe("ARCAEA_IMPL", () => {
|
||||
expect(
|
||||
ARCAEA_IMPL.scoreCalcs(scoreData, ARCAEA_IMPL.scoreDeriver(scoreData, chart), chart)
|
||||
.potential,
|
||||
).toBe(11.99);
|
||||
).toBe(11.99683);
|
||||
});
|
||||
|
||||
describe("classDerivers (naivePotential → badge)", () => {
|
||||
@@ -330,5 +330,27 @@ describe("ARCAEA_IMPL", () => {
|
||||
ARCAEA_IMPL.chartSpecificValidators.score(10_001_152, chartWithoutNotecount),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects inconsistent pure counts", () => {
|
||||
expect(
|
||||
runVal({
|
||||
scoreData: {
|
||||
lamp: "PURE MEMORY",
|
||||
score: 10_001_151,
|
||||
judgements: { pure: 1150, far: 0, lost: 0 },
|
||||
},
|
||||
}),
|
||||
).toEqual([`Impossible PURE MEMORY. Got ${10_001_151} with ${1150} pures.`]);
|
||||
|
||||
expect(
|
||||
runVal({
|
||||
scoreData: {
|
||||
lamp: "PURE MEMORY",
|
||||
score: 10_001_151,
|
||||
judgements: { pure: 1151, far: 0, lost: 0 },
|
||||
},
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,5 +125,19 @@ export const ARCAEA_IMPL: GameImplementation<"arcaea"> = {
|
||||
return "Cannot have a FULL RECALL with non-zero lost count.";
|
||||
}
|
||||
},
|
||||
(s) => {
|
||||
const { pure, far, lost } = s.scoreData.judgements;
|
||||
|
||||
if (
|
||||
!IsNullish(pure) &&
|
||||
(far ?? 0) + (lost ?? 0) === 0 &&
|
||||
s.scoreData.lamp === "PURE MEMORY"
|
||||
) {
|
||||
const shinyPure = s.scoreData.score - 10_000_000;
|
||||
if (shinyPure > pure) {
|
||||
return `Impossible PURE MEMORY. Got ${s.scoreData.score} with ${pure} pures.`;
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user