mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-22 23:18:05 +03:00
fix: handle lamp re-arrangement from cg (#1624)
This commit is contained in:
+105
-27
@@ -1,6 +1,8 @@
|
||||
import type { DryScore } from "#lib/score-import/framework/common/types";
|
||||
import type { Versions } from "tachi-common";
|
||||
|
||||
import { log } from "#lib/log/log";
|
||||
import { InvalidScoreFailure } from "#lib/score-import/framework/common/converter-failures";
|
||||
import { ParseDateFromString } from "#lib/score-import/framework/common/score-utils";
|
||||
import DB from "#services/pg/db";
|
||||
import { dmf } from "#test-utils/misc";
|
||||
@@ -9,7 +11,7 @@ import { beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import type { CGContext, CGSDVXScore } from "../types";
|
||||
|
||||
import { ConverterAPICGSDVX } from "./converter";
|
||||
import { ConvertCGSDVXLamp, ConverterAPICGSDVX } from "./converter";
|
||||
|
||||
function mkInput(modifant: Partial<CGSDVXScore> = {}) {
|
||||
const validInput: CGSDVXScore = {
|
||||
@@ -56,7 +58,7 @@ function mkOutput(modifant: Partial<DryScore<"sdvx">> = {}): DryScore<"sdvx"> {
|
||||
return dmf(validOutput, modifant);
|
||||
}
|
||||
|
||||
async function seedSdvxCgFixture() {
|
||||
async function seedSdvxCgFixture(chartVersions: Versions["sdvx"][] = TestingAlbidaADV.versions) {
|
||||
await DB.insertInto("song")
|
||||
.values({
|
||||
id: TestingSDVXAlbidaSong.id,
|
||||
@@ -81,23 +83,92 @@ async function seedSdvxCgFixture() {
|
||||
level: TestingAlbidaADV.level,
|
||||
level_num: TestingAlbidaADV.levelNum,
|
||||
is_primary: TestingAlbidaADV.isPrimary,
|
||||
versions: TestingAlbidaADV.versions,
|
||||
versions: chartVersions,
|
||||
data: TestingAlbidaADV.data,
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
/** CG clearType → lamp; v6 (exceed) and v7 (nabla) permute UC/PUC/MAXXIVE (4/5/6). */
|
||||
const cgSdvxLampByVersion = [
|
||||
{ gameVersion: "exceed", clearType: 1, lamp: "FAILED" },
|
||||
{ gameVersion: "exceed", clearType: 2, lamp: "CLEAR" },
|
||||
{ gameVersion: "exceed", clearType: 3, lamp: "EXCESSIVE CLEAR" },
|
||||
{ gameVersion: "exceed", clearType: 4, lamp: "ULTIMATE CHAIN" },
|
||||
{ gameVersion: "exceed", clearType: 5, lamp: "PERFECT ULTIMATE CHAIN" },
|
||||
{ gameVersion: "exceed", clearType: 6, lamp: "MAXXIVE CLEAR" },
|
||||
{ gameVersion: "nabla", clearType: 1, lamp: "FAILED" },
|
||||
{ gameVersion: "nabla", clearType: 2, lamp: "CLEAR" },
|
||||
{ gameVersion: "nabla", clearType: 3, lamp: "EXCESSIVE CLEAR" },
|
||||
{ gameVersion: "nabla", clearType: 4, lamp: "MAXXIVE CLEAR" },
|
||||
{ gameVersion: "nabla", clearType: 5, lamp: "ULTIMATE CHAIN" },
|
||||
{ gameVersion: "nabla", clearType: 6, lamp: "PERFECT ULTIMATE CHAIN" },
|
||||
] as const satisfies ReadonlyArray<{
|
||||
clearType: number;
|
||||
gameVersion: Versions["sdvx"];
|
||||
lamp: DryScore<"sdvx">["scoreData"]["lamp"];
|
||||
}>;
|
||||
|
||||
/**
|
||||
* v6→v7 clearType remap for UC/PUC/MAXXIVE: 4↔5↔6 cycle (same lamp, different enum id).
|
||||
*/
|
||||
const cgSdvxCrossVersionLampEquivalence = [
|
||||
{ exceedClearType: 4, nablaClearType: 5, lamp: "ULTIMATE CHAIN" },
|
||||
{ exceedClearType: 5, nablaClearType: 6, lamp: "PERFECT ULTIMATE CHAIN" },
|
||||
{ exceedClearType: 6, nablaClearType: 4, lamp: "MAXXIVE CLEAR" },
|
||||
] as const satisfies ReadonlyArray<{
|
||||
exceedClearType: number;
|
||||
lamp: DryScore<"sdvx">["scoreData"]["lamp"];
|
||||
nablaClearType: number;
|
||||
}>;
|
||||
|
||||
describe("ConvertCGSDVXLamp", () => {
|
||||
it.each(cgSdvxLampByVersion)("maps clearType $clearType at $gameVersion to $lamp", ({
|
||||
gameVersion,
|
||||
clearType,
|
||||
lamp,
|
||||
}) => {
|
||||
expect(ConvertCGSDVXLamp(gameVersion, clearType)).toBe(lamp);
|
||||
});
|
||||
|
||||
it.each(
|
||||
cgSdvxCrossVersionLampEquivalence,
|
||||
)("exceed clearType $exceedClearType and nabla clearType $nablaClearType both mean $lamp", ({
|
||||
exceedClearType,
|
||||
nablaClearType,
|
||||
lamp,
|
||||
}) => {
|
||||
expect(ConvertCGSDVXLamp("exceed", exceedClearType)).toBe(lamp);
|
||||
expect(ConvertCGSDVXLamp("nabla", nablaClearType)).toBe(lamp);
|
||||
});
|
||||
|
||||
it("rejects unknown clearType", () => {
|
||||
expect(() => ConvertCGSDVXLamp("exceed", 99)).toThrow(InvalidScoreFailure);
|
||||
expect(() => ConvertCGSDVXLamp("nabla", 0)).toThrow(InvalidScoreFailure);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ConverterAPICGSDVX", () => {
|
||||
const context: CGContext = {
|
||||
service: "dev",
|
||||
userID: 1,
|
||||
};
|
||||
|
||||
beforeEach(seedSdvxCgFixture);
|
||||
beforeEach(() => seedSdvxCgFixture([...TestingAlbidaADV.versions, "nabla"]));
|
||||
|
||||
const convert = (modifant: Partial<CGSDVXScore> = {}) =>
|
||||
ConverterAPICGSDVX(mkInput(modifant), context, "api/cg-dev-sdvx", log);
|
||||
|
||||
const lampOnly = (lamp: DryScore<"sdvx">["scoreData"]["lamp"]) =>
|
||||
mkOutput({
|
||||
scoreData: {
|
||||
score: 9_123_000,
|
||||
lamp,
|
||||
judgements: { critical: 100, near: 50, miss: 10 },
|
||||
optional: { maxCombo: 300, exScore: 1234 },
|
||||
},
|
||||
});
|
||||
|
||||
it("converts valid input", async () => {
|
||||
const res = await convert();
|
||||
|
||||
@@ -111,31 +182,38 @@ describe("ConverterAPICGSDVX", () => {
|
||||
expect(res.dryScore).toStrictEqual(mkOutput());
|
||||
});
|
||||
|
||||
it("maps clearType to lamp", async () => {
|
||||
const lampOnly = (lamp: DryScore<"sdvx">["scoreData"]["lamp"]) =>
|
||||
mkOutput({
|
||||
scoreData: {
|
||||
score: 9_123_000,
|
||||
lamp,
|
||||
judgements: { critical: 100, near: 50, miss: 10 },
|
||||
optional: { maxCombo: 300, exScore: 1234 },
|
||||
},
|
||||
});
|
||||
it.each(
|
||||
cgSdvxLampByVersion.filter((c) => c.gameVersion === "exceed"),
|
||||
)("maps exceed (v6) clearType $clearType to $lamp end-to-end", async ({ clearType, lamp }) => {
|
||||
await expect(convert({ version: 6, clearType })).resolves.toMatchObject({
|
||||
dryScore: lampOnly(lamp),
|
||||
});
|
||||
});
|
||||
|
||||
await expect(convert({ clearType: 1 })).resolves.toMatchObject({
|
||||
dryScore: lampOnly("FAILED"),
|
||||
describe("nabla (v7) scores", () => {
|
||||
it.each(
|
||||
cgSdvxLampByVersion.filter((c) => c.gameVersion === "nabla"),
|
||||
)("maps nabla clearType $clearType to $lamp end-to-end", async ({ clearType, lamp }) => {
|
||||
await expect(convert({ version: 7, clearType })).resolves.toMatchObject({
|
||||
dryScore: lampOnly(lamp),
|
||||
});
|
||||
});
|
||||
await expect(convert({ clearType: 2 })).resolves.toMatchObject({
|
||||
dryScore: lampOnly("CLEAR"),
|
||||
});
|
||||
await expect(convert({ clearType: 3 })).resolves.toMatchObject({
|
||||
dryScore: lampOnly("EXCESSIVE CLEAR"),
|
||||
});
|
||||
await expect(convert({ clearType: 4 })).resolves.toMatchObject({
|
||||
dryScore: lampOnly("ULTIMATE CHAIN"),
|
||||
});
|
||||
await expect(convert({ clearType: 5 })).resolves.toMatchObject({
|
||||
dryScore: lampOnly("PERFECT ULTIMATE CHAIN"),
|
||||
|
||||
it.each(
|
||||
cgSdvxCrossVersionLampEquivalence,
|
||||
)("v7 clearType $nablaClearType matches exceed clearType $exceedClearType lamp $lamp", async ({
|
||||
exceedClearType,
|
||||
nablaClearType,
|
||||
lamp,
|
||||
}) => {
|
||||
const fromExceed = await convert({ version: 6, clearType: exceedClearType });
|
||||
const fromNabla = await convert({ version: 7, clearType: nablaClearType });
|
||||
|
||||
expect((fromExceed.dryScore as DryScore<"sdvx">).scoreData.lamp).toBe(lamp);
|
||||
expect((fromNabla.dryScore as DryScore<"sdvx">).scoreData.lamp).toBe(lamp);
|
||||
expect((fromNabla.dryScore as DryScore<"sdvx">).scoreData.lamp).toBe(
|
||||
(fromExceed.dryScore as DryScore<"sdvx">).scoreData.lamp,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+22
-2
@@ -43,7 +43,7 @@ export const ConverterAPICGSDVX: ConverterFunction<CGSDVXScore, CGContext> = asy
|
||||
throw new InternalFailure(`Song-Chart desync with song ID ${chart.song.id} (sdvx).`);
|
||||
}
|
||||
|
||||
const lamp = ConvertCGSDVXLamp(data.clearType);
|
||||
const lamp = ConvertCGSDVXLamp(version, data.clearType);
|
||||
|
||||
const timeAchieved = ParseDateFromString(data.dateTime);
|
||||
|
||||
@@ -116,7 +116,27 @@ function ConvertVersion(ver: number): Versions["sdvx"] {
|
||||
* Convert CG's clearType enum into a Tachi lamp. Note that what numbers mean what are
|
||||
* dependent on what version of the game we're listening for.
|
||||
*/
|
||||
function ConvertCGSDVXLamp(clearType: number): GetEnumValue<"sdvx", "lamp"> {
|
||||
export function ConvertCGSDVXLamp(
|
||||
gameVersion: Versions["sdvx"],
|
||||
clearType: number,
|
||||
): GetEnumValue<"sdvx", "lamp"> {
|
||||
if (gameVersion === "nabla") {
|
||||
switch (clearType) {
|
||||
case 1:
|
||||
return "FAILED";
|
||||
case 2:
|
||||
return "CLEAR";
|
||||
case 3:
|
||||
return "EXCESSIVE CLEAR";
|
||||
case 4:
|
||||
return "MAXXIVE CLEAR";
|
||||
case 5:
|
||||
return "ULTIMATE CHAIN";
|
||||
case 6:
|
||||
return "PERFECT ULTIMATE CHAIN";
|
||||
}
|
||||
}
|
||||
|
||||
switch (clearType) {
|
||||
case 1:
|
||||
return "FAILED";
|
||||
|
||||
Reference in New Issue
Block a user