fix(api-cg/jb): use clearFlag bitfield to generate Lamp (#1276)

This commit is contained in:
Daniel THIRION
2025-04-18 23:04:28 +01:00
committed by GitHub
parent b485982f1a
commit 0b7de99583
3 changed files with 197 additions and 94 deletions
@@ -1,3 +1,4 @@
/* eslint-disable no-bitwise */
import { ConverterAPICGJubeat } from "./converter";
import CreateLogCtx from "lib/logger/logger";
import { ParseDateFromString } from "lib/score-import/framework/common/score-utils";
@@ -8,6 +9,11 @@ import type { DryScore } from "lib/score-import/framework/common/types";
const logger = CreateLogCtx(__filename);
const BIT_FAILED = 1 << 0;
const BIT_CLEAR = 1 << 1;
const BIT_FULL_COMBO = 1 << 2;
const BIT_EXCELLENT = 1 << 3;
function mkInput(modifant: Partial<CGJubeatScore> = {}) {
const validInput: CGJubeatScore = {
internalId: 10000001,
@@ -22,7 +28,7 @@ function mkInput(modifant: Partial<CGJubeatScore> = {}) {
poorCount: 0,
missCount: 0,
musicRate: 952,
clearFlag: 0, // unknown, unused
clearFlag: BIT_FULL_COMBO | BIT_CLEAR | BIT_FAILED,
};
return dmf(validInput, modifant);
@@ -34,9 +40,6 @@ function mkOutput(modifant: any = {}): DryScore<"jubeat:Single"> {
comment: null,
game: "jubeat",
importType: "api/cg-dev-jubeat",
// we handle it like this because -- with no timezone info
// this will fail in CI; it has a different timezone there!
timeAchieved: ParseDateFromString("2019-06-06 08:14:22"),
service: "CG Dev",
scoreData: {
@@ -47,6 +50,7 @@ function mkOutput(modifant: any = {}): DryScore<"jubeat:Single"> {
great: 50,
good: 25,
poor: 0,
miss: 0,
},
musicRate: 95.2,
optional: {},
@@ -62,7 +66,6 @@ t.test("#ConverterAPICGJubeat", (t) => {
service: "dev",
userID: 1,
};
const convert = (modifant: Partial<CGJubeatScore> = {}) =>
ConverterAPICGJubeat(mkInput(modifant), context, "api/cg-dev-jubeat", logger);
@@ -85,81 +88,181 @@ t.test("#ConverterAPICGJubeat", (t) => {
t.end();
});
t.test("Lamps", async (t) => {
t.hasStrict(
await convert({
score: 690_000,
missCount: 1, // at least a miss otherwise it's an FC
}),
{
dryScore: mkOutput({
scoreData: {
lamp: "FAILED",
score: 690_000,
judgements: {
miss: 1,
},
},
}),
}
);
t.hasStrict(
await convert({
score: 700_000,
missCount: 1,
}),
{
dryScore: mkOutput({
scoreData: {
lamp: "CLEAR",
score: 700_000,
judgements: {
miss: 1,
},
},
}),
}
);
t.hasStrict(
await convert({
missCount: 0,
poorCount: 0,
}),
{
dryScore: mkOutput({
scoreData: {
lamp: "FULL COMBO",
judgements: {
poor: 0,
miss: 0,
},
},
}),
}
);
t.hasStrict(
await convert({
missCount: 0,
poorCount: 0,
goodCount: 0,
t.test("Lamp Interpretation from Bitfield", (t) => {
t.test("should interpret EXCELLENT bitfield correctly", async (t) => {
const clearFlag = BIT_EXCELLENT | BIT_FULL_COMBO | BIT_CLEAR | BIT_FAILED;
const score = 1_000_000;
const judgements = {
perfectCount: 200,
greatCount: 0,
score: 1_000_000,
}),
{
dryScore: mkOutput({
scoreData: {
lamp: "EXCELLENT",
score: 1_000_000,
judgements: {
great: 0,
good: 0,
poor: 0,
miss: 0,
},
},
goodCount: 0,
poorCount: 0,
missCount: 0,
};
t.hasStrict(
await convert({
clearFlag,
score,
...judgements,
}),
}
);
{
dryScore: mkOutput({
scoreData: {
lamp: "EXCELLENT",
score,
judgements: {
perfect: 200,
great: 0,
good: 0,
poor: 0,
miss: 0,
},
},
}),
}
);
});
t.test("should interpret FULL COMBO bitfield correctly", async (t) => {
const clearFlag = BIT_FULL_COMBO | BIT_CLEAR | BIT_FAILED;
const judgements = {
perfectCount: 100,
greatCount: 50,
goodCount: 25,
poorCount: 0,
missCount: 0,
};
const score = 947_184;
t.hasStrict(
await convert({
clearFlag,
score,
...judgements,
}),
{
dryScore: mkOutput({
scoreData: {
lamp: "FULL COMBO",
score,
judgements: {
perfect: 100,
great: 50,
good: 25,
poor: 0,
miss: 0,
},
},
}),
}
);
});
t.test("should interpret CLEAR bitfield correctly", async (t) => {
const clearFlag = BIT_CLEAR | BIT_FAILED;
const score = 750_000;
const judgements = {
perfectCount: 90,
greatCount: 40,
goodCount: 20,
poorCount: 5,
missCount: 1,
};
t.hasStrict(
await convert({
clearFlag,
score,
...judgements,
}),
{
dryScore: mkOutput({
scoreData: {
lamp: "CLEAR",
score,
judgements: {
perfect: 90,
great: 40,
good: 20,
poor: 5,
miss: 1,
},
},
}),
}
);
});
t.test("should interpret FAILED bitfield correctly (standard)", async (t) => {
const clearFlag = BIT_FAILED;
const score = 650_000;
const judgements = {
perfectCount: 80,
greatCount: 30,
goodCount: 15,
poorCount: 10,
missCount: 5,
};
t.hasStrict(
await convert({
clearFlag,
score,
...judgements,
}),
{
dryScore: mkOutput({
scoreData: {
lamp: "FAILED",
score,
judgements: {
perfect: 80,
great: 30,
good: 15,
poor: 10,
miss: 5,
},
},
}),
}
);
});
t.test("should interpret FAILED bitfield (case of challenge fail)", async (t) => {
const clearFlag = BIT_FAILED;
const score = 920_000;
const judgements = {
perfectCount: 110,
greatCount: 55,
goodCount: 30,
poorCount: 0,
missCount: 0,
};
t.hasStrict(
await convert({
clearFlag,
score,
...judgements,
}),
{
dryScore: mkOutput({
scoreData: {
lamp: "FAILED",
score,
judgements: {
perfect: 110,
great: 55,
good: 30,
poor: 0,
miss: 0,
},
},
}),
}
);
});
t.end();
});
@@ -28,7 +28,7 @@ export const ConverterAPICGJubeat: ConverterFunction<CGJubeatScore, CGContext> =
poor: data.poorCount,
miss: data.missCount,
};
const lamp = GuessLamp(judgements, data.score);
const lamp = GetLamp(data.clearFlag);
const chart = await FindChartOnInGameID("jubeat", data.internalId, "Single", difficulty);
@@ -125,23 +125,23 @@ function ConvertMusicRate(rate: number): number {
return rate / 10;
}
function GuessLamp(judgments: Record<Judgements["jubeat:Single"], integer | null>, score: number) {
if (
judgments.good === 0 &&
judgments.great === 0 &&
judgments.miss === 0 &&
judgments.poor === 0
) {
if (score !== 1_000_000) {
throw new InvalidScoreFailure(`Score is an EXC, yet score !== 1_000_000.`);
}
/* eslint-disable no-bitwise */
function GetLamp(clearFlag: integer) {
if ((clearFlag & (1 << 3)) !== 0) {
return "EXCELLENT";
} else if (judgments.miss === 0 && judgments.poor === 0) {
}
if ((clearFlag & (1 << 2)) !== 0) {
return "FULL COMBO";
} else if (score >= 700_000) {
}
if ((clearFlag & (1 << 1)) !== 0) {
return "CLEAR";
}
return "FAILED";
if ((clearFlag & (1 << 0)) !== 0) {
return "FAILED";
}
throw new InvalidScoreFailure("Failed to decode Lamp using bitfield clearFlag.");
}
@@ -71,7 +71,7 @@ export interface CGJubeatScore {
internalId: integer;
difficulty: integer;
version: integer;
clearFlag: unknown; // Weird bitwise field, unused.
clearFlag: integer; // Weird bitwise field, unused.
score: integer;
hardMode: boolean;