mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-02 20:08:17 +03:00
emerg: scoreID must use legacyChartID to maintain stability
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update \
|
||||
# postgres client tools (psql, pg_dump, pg_restore, pg_isready, etc.)
|
||||
postgresql-client-18 \
|
||||
# nice to haves
|
||||
gh fish just fzf curl wget parallel neovim fd-find bat rsync \
|
||||
gh fish just fzf curl wget parallel neovim fd-find bat ripgrep rsync \
|
||||
# MinIO CLI (binary is `minio-client`, not Midnight Commander's `mc`)
|
||||
minio-client \
|
||||
# uninstall lynx so people don't get a CLI browser that can't load anything when they do `gh auth login`
|
||||
|
||||
@@ -353,6 +353,8 @@ export interface ChartTierlistInfo {
|
||||
export interface ChartDocument<TGame extends V3Game = V3Game> {
|
||||
game: TGame;
|
||||
chartID: string;
|
||||
/** Mongo-era 40-character chart identifier; used for score deduplication. */
|
||||
legacyChartID: string;
|
||||
level: string;
|
||||
levelNum: number;
|
||||
isPrimary: boolean;
|
||||
|
||||
@@ -61,6 +61,7 @@ describe("GetChartById", () => {
|
||||
expect(c).toBeDefined();
|
||||
expect(c!.game).toBe("iidx-sp");
|
||||
expect(c!.chartID).toBe(CHART_ID);
|
||||
expect(c!.legacyChartID).toBe(CHART_LEGACY_ID);
|
||||
expect(c!.song.id).toBe(SONG_PG_ID);
|
||||
expect(c!.versions).toContain("27");
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import { SELECT_SONG_DOCUMENT } from "./song";
|
||||
|
||||
export const SELECT_CHART = [
|
||||
"chart.id as chart_id",
|
||||
"chart.legacy_id as chart_legacy_id",
|
||||
"chart.game as chart_game",
|
||||
"chart.level as chart_level",
|
||||
"chart.level_num as chart_level_num",
|
||||
@@ -33,6 +34,7 @@ export function ToChartDocument(row: ChartRow): ChartDocument {
|
||||
return {
|
||||
game: row.chart_game,
|
||||
chartID: row.chart_id,
|
||||
legacyChartID: row.chart_legacy_id,
|
||||
song: {
|
||||
altTitles: row.song_alt_titles,
|
||||
artist: row.song_artist,
|
||||
|
||||
@@ -68,7 +68,7 @@ export async function DeorphanBmsIfInOrphanChartPg(
|
||||
.insertInto("chart")
|
||||
.values({
|
||||
id: chartDoc.chartID,
|
||||
legacy_id: chartDoc.chartID,
|
||||
legacy_id: chartDoc.legacyChartID,
|
||||
game,
|
||||
song_id: songNewID,
|
||||
level: chartDoc.level,
|
||||
|
||||
@@ -94,7 +94,7 @@ async function writeBmsPmsSongAndChart(
|
||||
.insertInto("chart")
|
||||
.values({
|
||||
id: chartDoc.chartID,
|
||||
legacy_id: chartDoc.chartID,
|
||||
legacy_id: chartDoc.legacyChartID,
|
||||
game: v3Game,
|
||||
song_id: songNewID,
|
||||
level: chartDoc.level,
|
||||
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`CreateScoreID static output > arcaea produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"chartID": "Tc855e4d5df609a3ac6aceccd0aaf75454aecd8091513bde9978a270ae26b40f8",
|
||||
"optional.fast.ignored": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"provided.lamp": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.score": "T77c242b448574d58745a9b5c36bf67e769ee420c2f23c3f0837877cdefe37fda",
|
||||
"userID": "T542bc5112a0a0342be56856bb38c80ebfa14286a7e951578680bc78ff957a134",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > bms-7k produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"chartID": "Tc855e4d5df609a3ac6aceccd0aaf75454aecd8091513bde9978a270ae26b40f8",
|
||||
"optional.fast.ignored": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"provided.lamp": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.score": "T77c242b448574d58745a9b5c36bf67e769ee420c2f23c3f0837877cdefe37fda",
|
||||
"userID": "T542bc5112a0a0342be56856bb38c80ebfa14286a7e951578680bc78ff957a134",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > bms-14k produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"chartID": "Tc855e4d5df609a3ac6aceccd0aaf75454aecd8091513bde9978a270ae26b40f8",
|
||||
"optional.fast.ignored": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"provided.lamp": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.score": "T77c242b448574d58745a9b5c36bf67e769ee420c2f23c3f0837877cdefe37fda",
|
||||
"userID": "T542bc5112a0a0342be56856bb38c80ebfa14286a7e951578680bc78ff957a134",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > chunithm produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T34db52d521109b3a8889ab3b5354fef08f9b39ab6da62eb6240c868035affe93",
|
||||
"chartID": "T343ef4bf7d6bd59590247a5c7c51b2fc99a45be5666a99c282d17232a7a50ed7",
|
||||
"optional.fast.ignored": "T34db52d521109b3a8889ab3b5354fef08f9b39ab6da62eb6240c868035affe93",
|
||||
"provided.clearLamp": "T725933011ed88177e9a6d6785ec1f05789e2a50490216b3bd362f9a148266a87",
|
||||
"provided.noteLamp": "Tdb1dfc3587098c967a9831fde32da01fd2a7e54af9819d1cb51f01d0da55c0cd",
|
||||
"provided.score": "T082194b0a2b91ad226d6aa1705e6dd1def4485e6b63bb57030bc8f9b05657ae6",
|
||||
"userID": "T05f3250757ccea76b266f478c51dadf3a704234a3006dbe24362468648ac6a14",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > ddr-dp produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "Tefde7e5b2f221fb4522c8d02033e0d7e7c4f14e47c18978e9054d4412d743ebd",
|
||||
"chartID": "T475d196f662d46fca9bc65e29e495cead9d19fedfc541e67a7841027169ee505",
|
||||
"optional.exScore.null": "T17a4b7a38a565b747be4a838bbe1cb996a817b61f3397d7befd9f4422026a110",
|
||||
"optional.exScore.set": "Tefde7e5b2f221fb4522c8d02033e0d7e7c4f14e47c18978e9054d4412d743ebd",
|
||||
"optional.exScore.unset": "T17a4b7a38a565b747be4a838bbe1cb996a817b61f3397d7befd9f4422026a110",
|
||||
"optional.flare.ignored": "Tefde7e5b2f221fb4522c8d02033e0d7e7c4f14e47c18978e9054d4412d743ebd",
|
||||
"provided.lamp": "T80cbc715b7acc9a7b3a029b2912e4c0f6e020109be46780a42c877654ef1ece8",
|
||||
"provided.score": "T4283ad151d4659d35ef56ef18ae4b5be2f8b2d094c7105666ef92b02d78e0d4f",
|
||||
"userID": "Tde9d8c058751a41e5e278be598968e52d9ce1c74c28b5f426b6390b76e15acf7",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > ddr-sp produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "Tefde7e5b2f221fb4522c8d02033e0d7e7c4f14e47c18978e9054d4412d743ebd",
|
||||
"chartID": "T475d196f662d46fca9bc65e29e495cead9d19fedfc541e67a7841027169ee505",
|
||||
"optional.exScore.null": "T17a4b7a38a565b747be4a838bbe1cb996a817b61f3397d7befd9f4422026a110",
|
||||
"optional.exScore.set": "Tefde7e5b2f221fb4522c8d02033e0d7e7c4f14e47c18978e9054d4412d743ebd",
|
||||
"optional.exScore.unset": "T17a4b7a38a565b747be4a838bbe1cb996a817b61f3397d7befd9f4422026a110",
|
||||
"optional.flare.ignored": "Tefde7e5b2f221fb4522c8d02033e0d7e7c4f14e47c18978e9054d4412d743ebd",
|
||||
"provided.lamp": "T80cbc715b7acc9a7b3a029b2912e4c0f6e020109be46780a42c877654ef1ece8",
|
||||
"provided.score": "T4283ad151d4659d35ef56ef18ae4b5be2f8b2d094c7105666ef92b02d78e0d4f",
|
||||
"userID": "Tde9d8c058751a41e5e278be598968e52d9ce1c74c28b5f426b6390b76e15acf7",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > gitadora-dora produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T5db96789ad7a73096158cb1eb57381d423bf8dd5ad4fd0c0bf4e63883e3d8ac0",
|
||||
"chartID": "T9561c3da01a13f78fdb402026c8424d5a754159e894927bb3118a12064e37b17",
|
||||
"optional.fast.ignored": "T5db96789ad7a73096158cb1eb57381d423bf8dd5ad4fd0c0bf4e63883e3d8ac0",
|
||||
"provided.lamp": "Te6a1f9342cefc623314a5ae8f7c4b37f77ce236023540dd94743cdc959189179",
|
||||
"provided.percent": "T809dbd0b965b67e6b99fdda40d96290100b8f41876f79eeb0d68ed240cfb0f42",
|
||||
"userID": "Te2fecd654e16a3ca7754f7493d0f186f5cfaa7d4df1504558e41fcd2a65233e5",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > gitadora-gita produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T5db96789ad7a73096158cb1eb57381d423bf8dd5ad4fd0c0bf4e63883e3d8ac0",
|
||||
"chartID": "T9561c3da01a13f78fdb402026c8424d5a754159e894927bb3118a12064e37b17",
|
||||
"optional.fast.ignored": "T5db96789ad7a73096158cb1eb57381d423bf8dd5ad4fd0c0bf4e63883e3d8ac0",
|
||||
"provided.lamp": "Te6a1f9342cefc623314a5ae8f7c4b37f77ce236023540dd94743cdc959189179",
|
||||
"provided.percent": "T809dbd0b965b67e6b99fdda40d96290100b8f41876f79eeb0d68ed240cfb0f42",
|
||||
"userID": "Te2fecd654e16a3ca7754f7493d0f186f5cfaa7d4df1504558e41fcd2a65233e5",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > iidx-dp produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"chartID": "Tc855e4d5df609a3ac6aceccd0aaf75454aecd8091513bde9978a270ae26b40f8",
|
||||
"optional.fast.ignored": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"provided.lamp": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.score": "T77c242b448574d58745a9b5c36bf67e769ee420c2f23c3f0837877cdefe37fda",
|
||||
"userID": "T542bc5112a0a0342be56856bb38c80ebfa14286a7e951578680bc78ff957a134",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > iidx-sp produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"chartID": "Tc855e4d5df609a3ac6aceccd0aaf75454aecd8091513bde9978a270ae26b40f8",
|
||||
"optional.fast.ignored": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"provided.lamp": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.score": "T77c242b448574d58745a9b5c36bf67e769ee420c2f23c3f0837877cdefe37fda",
|
||||
"userID": "T542bc5112a0a0342be56856bb38c80ebfa14286a7e951578680bc78ff957a134",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > itg-stamina produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "Tdadb283e70ef31c8702bb89f837bc94436ae6ee7f244ab70ea2a39ae1fe9f7ec",
|
||||
"chartID": "T6ca6f39f038396ecb849664b2c1835a5be822e6192e90b60214d5cd2002723ba",
|
||||
"optional.lifebarHistory.ignored": "Tdadb283e70ef31c8702bb89f837bc94436ae6ee7f244ab70ea2a39ae1fe9f7ec",
|
||||
"provided.lamp": "T2ab2790e85a14fc39a38e8061c80a3ef8a0d67bfe40a9433a4a0ba8f2680ce3d",
|
||||
"provided.scorePercent": "T79c2380cac5b3d602354e86f961bf954e77366fa2d7143ac125a8781adf4b734",
|
||||
"provided.survivedPercent": "Tbc57ec37a8ab4469b5c1fefab2a86434e8bc72518302fc6a2cfaff572f1bd820",
|
||||
"userID": "Tca59e8a903c600ec370819d96cfde63aaa549704c8606109bee8d76a6ef0d20d",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > jubeat produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T83e29d2893291c0cae7aefba2d9405c1d2c60ee98ee6ab1adf2c5cbcebb7550e",
|
||||
"chartID": "T692dd9a1a68dcddb58c19415797dfabf1095d483a0f33367df299ae668c8cdf5",
|
||||
"optional.fast.ignored": "T83e29d2893291c0cae7aefba2d9405c1d2c60ee98ee6ab1adf2c5cbcebb7550e",
|
||||
"provided.lamp": "T97861ca816081d7071e0cfb8acaaa229deb0d11facd73c68380743594812c4b2",
|
||||
"provided.musicRate": "T470a07c210911b79f8fa3b11fb4613d10fd9b5e8020e37c10e47c29669cc0541",
|
||||
"provided.score": "Tb21103a4c8034291988f0fe83e3d4b653e40b027994e43d23855c7b8a32739b8",
|
||||
"userID": "Te92e20c8d9e7463bb8a79367281420bdc146d4c09e666f7056a89bd681481088",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > maimai produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T5db96789ad7a73096158cb1eb57381d423bf8dd5ad4fd0c0bf4e63883e3d8ac0",
|
||||
"chartID": "T9561c3da01a13f78fdb402026c8424d5a754159e894927bb3118a12064e37b17",
|
||||
"optional.fast.ignored": "T5db96789ad7a73096158cb1eb57381d423bf8dd5ad4fd0c0bf4e63883e3d8ac0",
|
||||
"provided.lamp": "Te6a1f9342cefc623314a5ae8f7c4b37f77ce236023540dd94743cdc959189179",
|
||||
"provided.percent": "T809dbd0b965b67e6b99fdda40d96290100b8f41876f79eeb0d68ed240cfb0f42",
|
||||
"userID": "Te2fecd654e16a3ca7754f7493d0f186f5cfaa7d4df1504558e41fcd2a65233e5",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > maimaidx produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T5db96789ad7a73096158cb1eb57381d423bf8dd5ad4fd0c0bf4e63883e3d8ac0",
|
||||
"chartID": "T9561c3da01a13f78fdb402026c8424d5a754159e894927bb3118a12064e37b17",
|
||||
"optional.fast.ignored": "T5db96789ad7a73096158cb1eb57381d423bf8dd5ad4fd0c0bf4e63883e3d8ac0",
|
||||
"provided.lamp": "Te6a1f9342cefc623314a5ae8f7c4b37f77ce236023540dd94743cdc959189179",
|
||||
"provided.percent": "T809dbd0b965b67e6b99fdda40d96290100b8f41876f79eeb0d68ed240cfb0f42",
|
||||
"userID": "Te2fecd654e16a3ca7754f7493d0f186f5cfaa7d4df1504558e41fcd2a65233e5",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > museca produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"chartID": "T17a81c58b451236c6f80c80848bef7d1e4dbaff88e4fac35638e966f686f5602",
|
||||
"optional.fast.ignored": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.lamp": "Te570c06f4431c29ff6493869c56749e259dc83fa19e9cfd7e02899a772152d9c",
|
||||
"provided.score": "Tfd40722a31de045b97b7ae23b0d1613ebc9442ab27cdbdfe5252ef823a0ad94f",
|
||||
"userID": "Tc18bba02eca24e8fa646f70f8d2d447caf9315b0b16efb9ac90ae8a798118848",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > ongeki produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T14a20b47ff8adaa78a3c8fcc587996cccf9e69ff9e94d28ffc2a99a43b2e1173",
|
||||
"chartID": "T1a31b6d75fa427ee4154981c5212abf0494db270b2f1d5d8472ff7cc3671ca1e",
|
||||
"optional.bellCount.null": "T093f6dc7537e576dba449a2699246efa947ed8fa9f697cd3e4dcfeefc3ffe784",
|
||||
"optional.bellCount.set": "T14a20b47ff8adaa78a3c8fcc587996cccf9e69ff9e94d28ffc2a99a43b2e1173",
|
||||
"optional.bellCount.unset": "T093f6dc7537e576dba449a2699246efa947ed8fa9f697cd3e4dcfeefc3ffe784",
|
||||
"optional.damage.null": "T66c94a56bb6d00a1ce8700b64adf5d6bcd7da022c6fc27ba98b80236e9d7fd1a",
|
||||
"optional.damage.set": "T14a20b47ff8adaa78a3c8fcc587996cccf9e69ff9e94d28ffc2a99a43b2e1173",
|
||||
"optional.damage.unset": "T66c94a56bb6d00a1ce8700b64adf5d6bcd7da022c6fc27ba98b80236e9d7fd1a",
|
||||
"optional.fast.ignored": "T14a20b47ff8adaa78a3c8fcc587996cccf9e69ff9e94d28ffc2a99a43b2e1173",
|
||||
"provided.bellLamp": "Tca16675649b342a148d57cfc2a966489f753bce05c721d22a331fe5b1848ac0a",
|
||||
"provided.noteLamp": "Taa7a7095805bc712a33997530f0594c32aa67a4aad31d8040d52246d9aecd0b2",
|
||||
"provided.platinumScore": "T2ada4cd09159bbc5affcb2ff1f13df6353e784382a1181a9d38b3178621496e9",
|
||||
"provided.score": "T207b7cf7f0423818cc4a69f8b7ce111559b82ee71ab6cf64d7ad8afb41ce7d65",
|
||||
"userID": "T8910462fc86dc4dd87878884784fb2aa5a63934cf121dcf0fe2832c9b0ce576a",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > pms-controller produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"chartID": "Tc855e4d5df609a3ac6aceccd0aaf75454aecd8091513bde9978a270ae26b40f8",
|
||||
"optional.fast.ignored": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"provided.lamp": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.score": "T77c242b448574d58745a9b5c36bf67e769ee420c2f23c3f0837877cdefe37fda",
|
||||
"userID": "T542bc5112a0a0342be56856bb38c80ebfa14286a7e951578680bc78ff957a134",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > pms-keyboard produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"chartID": "Tc855e4d5df609a3ac6aceccd0aaf75454aecd8091513bde9978a270ae26b40f8",
|
||||
"optional.fast.ignored": "T611ff4451dd7ff905e9468d128a73f05fa106bc80c75a86e132b51d819f9dd00",
|
||||
"provided.lamp": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.score": "T77c242b448574d58745a9b5c36bf67e769ee420c2f23c3f0837877cdefe37fda",
|
||||
"userID": "T542bc5112a0a0342be56856bb38c80ebfa14286a7e951578680bc78ff957a134",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > popn produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "Tb4ad84adf0b4e18f32d696e3567887cd4607db61550d6fbd9fac0231b9f8e7bb",
|
||||
"chartID": "T77807c70a4da0d1b5210b5ce8d18c09444555ffacd28d590509acb961c9de6c9",
|
||||
"optional.fast.ignored": "Tb4ad84adf0b4e18f32d696e3567887cd4607db61550d6fbd9fac0231b9f8e7bb",
|
||||
"provided.clearMedal": "T1e9d1165a2ccc2c1082d9bc266030a48fa66e140e86853670e292d3397446d27",
|
||||
"provided.score": "T265b8102af7d36a80b44e33a10a8989b608e4014120da5148aa23aff427230a4",
|
||||
"userID": "T9137d13aa0eb07438184dd49dd296ab8485b3a62737416547f98d52adc578ad4",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > sdvx produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "Tefde7e5b2f221fb4522c8d02033e0d7e7c4f14e47c18978e9054d4412d743ebd",
|
||||
"chartID": "T475d196f662d46fca9bc65e29e495cead9d19fedfc541e67a7841027169ee505",
|
||||
"optional.exScore.null": "T17a4b7a38a565b747be4a838bbe1cb996a817b61f3397d7befd9f4422026a110",
|
||||
"optional.exScore.set": "Tefde7e5b2f221fb4522c8d02033e0d7e7c4f14e47c18978e9054d4412d743ebd",
|
||||
"optional.exScore.unset": "T17a4b7a38a565b747be4a838bbe1cb996a817b61f3397d7befd9f4422026a110",
|
||||
"optional.fast.ignored": "Tefde7e5b2f221fb4522c8d02033e0d7e7c4f14e47c18978e9054d4412d743ebd",
|
||||
"provided.lamp": "Td12f6eb659d72412c12316a69570e6e488c525f211b311467810d5c324ed355d",
|
||||
"provided.score": "T4283ad151d4659d35ef56ef18ae4b5be2f8b2d094c7105666ef92b02d78e0d4f",
|
||||
"userID": "Tde9d8c058751a41e5e278be598968e52d9ce1c74c28b5f426b6390b76e15acf7",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > usc-controller produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"chartID": "T17a81c58b451236c6f80c80848bef7d1e4dbaff88e4fac35638e966f686f5602",
|
||||
"optional.fast.ignored": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.lamp": "T810246f082ed7f9722ec63cba06f3f773eb4bcca671ad9ee9ce64be0ff2c492e",
|
||||
"provided.score": "Tfd40722a31de045b97b7ae23b0d1613ebc9442ab27cdbdfe5252ef823a0ad94f",
|
||||
"userID": "Tc18bba02eca24e8fa646f70f8d2d447caf9315b0b16efb9ac90ae8a798118848",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > usc-keyboard produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"chartID": "T17a81c58b451236c6f80c80848bef7d1e4dbaff88e4fac35638e966f686f5602",
|
||||
"optional.fast.ignored": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.lamp": "T810246f082ed7f9722ec63cba06f3f773eb4bcca671ad9ee9ce64be0ff2c492e",
|
||||
"provided.score": "Tfd40722a31de045b97b7ae23b0d1613ebc9442ab27cdbdfe5252ef823a0ad94f",
|
||||
"userID": "Tc18bba02eca24e8fa646f70f8d2d447caf9315b0b16efb9ac90ae8a798118848",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`CreateScoreID static output > wacca produces stable score ids 1`] = `
|
||||
{
|
||||
"baseline": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"chartID": "T17a81c58b451236c6f80c80848bef7d1e4dbaff88e4fac35638e966f686f5602",
|
||||
"optional.fast.ignored": "T87515e8d5a9921435c1c449d605d185a2d534f949f32cc03367c00c5787f5d78",
|
||||
"provided.lamp": "Tc6ea52eaea944f67bedd0b513ffb87215210ea3b22a19f54e96a19f892ab17f8",
|
||||
"provided.score": "Tfd40722a31de045b97b7ae23b0d1613ebc9442ab27cdbdfe5252ef823a0ad94f",
|
||||
"userID": "Tc18bba02eca24e8fa646f70f8d2d447caf9315b0b16efb9ac90ae8a798118848",
|
||||
}
|
||||
`;
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
import { dmf } from "#test-utils/misc";
|
||||
|
||||
import { ALL_GAMES, GetGameConfig, type ConfScoreMetric, type V3Game } from "tachi-common";
|
||||
|
||||
import type { DryScore } from "../common/types";
|
||||
|
||||
import { CreateScoreID } from "./score-id";
|
||||
|
||||
/** Stable inputs shared by every snapshot case. */
|
||||
export const SCORE_ID_CANONICAL_USER_ID = 1;
|
||||
export const SCORE_ID_ALT_USER_ID = 424_242;
|
||||
export const SCORE_ID_CANONICAL_CHART_ID = "c2311194e3897ddb5745b1760d2c0141f933e683";
|
||||
export const SCORE_ID_ALT_CHART_ID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
|
||||
function isEnumMetric(
|
||||
conf: ConfScoreMetric,
|
||||
): conf is ConfScoreMetric & { type: "ENUM"; values: Array<string> } {
|
||||
return conf.type === "ENUM";
|
||||
}
|
||||
|
||||
function baselineProvidedValue(conf: ConfScoreMetric): number | string {
|
||||
switch (conf.type) {
|
||||
case "INTEGER":
|
||||
return 500_000;
|
||||
case "DECIMAL":
|
||||
return 97.53;
|
||||
case "ENUM": {
|
||||
if (isEnumMetric(conf) && conf.minimumRelevantValue) {
|
||||
return conf.minimumRelevantValue;
|
||||
}
|
||||
|
||||
return isEnumMetric(conf) ? conf.values[Math.min(2, conf.values.length - 1)]! : "CLEAR";
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported provided metric type: ${(conf as ConfScoreMetric).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function alternateProvidedValue(conf: ConfScoreMetric, baseline: number | string): number | string {
|
||||
switch (conf.type) {
|
||||
case "INTEGER":
|
||||
return baseline === 500_000 ? 123_456 : 500_000;
|
||||
case "DECIMAL":
|
||||
return baseline === 97.53 ? 88.88 : 97.53;
|
||||
case "ENUM": {
|
||||
if (!isEnumMetric(conf)) {
|
||||
throw new Error("Expected enum metric");
|
||||
}
|
||||
|
||||
const idx = conf.values.indexOf(baseline as string);
|
||||
|
||||
return conf.values[(idx + 1) % conf.values.length]!;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported provided metric type: ${(conf as ConfScoreMetric).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function baselineOptionalPartOfScoreIdValue(conf: ConfScoreMetric): number | string {
|
||||
switch (conf.type) {
|
||||
case "INTEGER":
|
||||
return 777;
|
||||
case "DECIMAL":
|
||||
return 12.34;
|
||||
case "ENUM": {
|
||||
if (isEnumMetric(conf) && conf.minimumRelevantValue) {
|
||||
return conf.minimumRelevantValue;
|
||||
}
|
||||
|
||||
return isEnumMetric(conf) ? conf.values[0]! : "0";
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported partOfScoreID optional metric type: ${(conf as ConfScoreMetric).type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a minimal DryScore with every provided metric and every
|
||||
* partOfScoreID optional metric populated with stable values.
|
||||
*/
|
||||
export function buildCanonicalDryScore(game: V3Game): DryScore {
|
||||
const gameConfig = GetGameConfig(game);
|
||||
|
||||
const scoreData: Record<string, unknown> = {
|
||||
judgements: {},
|
||||
optional: {},
|
||||
};
|
||||
|
||||
for (const [metric, conf] of Object.entries(gameConfig.providedMetrics)) {
|
||||
scoreData[metric] = baselineProvidedValue(conf);
|
||||
}
|
||||
|
||||
for (const [metric, conf] of Object.entries(gameConfig.optionalMetrics)) {
|
||||
if (conf.partOfScoreID) {
|
||||
(scoreData.optional as Record<string, unknown>)[metric] =
|
||||
baselineOptionalPartOfScoreIdValue(conf);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
service: "score-id-fixture",
|
||||
game,
|
||||
scoreData: scoreData as DryScore["scoreData"],
|
||||
scoreMeta: {},
|
||||
timeAchieved: null,
|
||||
comment: null,
|
||||
importType: "file/batch-manual",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns every score-id variant we care about locking for a game:
|
||||
* baseline identity inputs, user/chart permutations, each provided metric,
|
||||
* and each partOfScoreID optional (set vs null).
|
||||
*/
|
||||
export function collectScoreIdVariants(game: V3Game): Record<string, string> {
|
||||
const dryScore = buildCanonicalDryScore(game);
|
||||
const gameConfig = GetGameConfig(game);
|
||||
|
||||
const variants: Record<string, string> = {
|
||||
baseline: CreateScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dryScore,
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
),
|
||||
userID: CreateScoreID(game, SCORE_ID_ALT_USER_ID, dryScore, SCORE_ID_CANONICAL_CHART_ID),
|
||||
chartID: CreateScoreID(game, SCORE_ID_CANONICAL_USER_ID, dryScore, SCORE_ID_ALT_CHART_ID),
|
||||
};
|
||||
|
||||
for (const [metric, conf] of Object.entries(gameConfig.providedMetrics)) {
|
||||
const baseline = baselineProvidedValue(conf);
|
||||
const alternate = alternateProvidedValue(conf, baseline);
|
||||
|
||||
variants[`provided.${metric}`] = CreateScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, {
|
||||
scoreData: {
|
||||
[metric]: alternate,
|
||||
},
|
||||
}),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
}
|
||||
|
||||
for (const [metric, conf] of Object.entries(gameConfig.optionalMetrics)) {
|
||||
if (!conf.partOfScoreID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const setValue = baselineOptionalPartOfScoreIdValue(conf);
|
||||
|
||||
variants[`optional.${metric}.set`] = CreateScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, {
|
||||
scoreData: {
|
||||
optional: {
|
||||
[metric]: setValue,
|
||||
},
|
||||
},
|
||||
}),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
|
||||
variants[`optional.${metric}.null`] = CreateScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, {
|
||||
scoreData: {
|
||||
optional: {
|
||||
[metric]: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
|
||||
variants[`optional.${metric}.unset`] = CreateScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, {
|
||||
scoreData: {
|
||||
optional: {
|
||||
[metric]: undefined,
|
||||
},
|
||||
},
|
||||
}),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
}
|
||||
|
||||
// Non-partOfScoreID optionals must not affect the hash.
|
||||
const firstNonScoreIdOptional = Object.entries(gameConfig.optionalMetrics).find(
|
||||
([, conf]) => !conf.partOfScoreID,
|
||||
);
|
||||
|
||||
if (firstNonScoreIdOptional) {
|
||||
const [metric] = firstNonScoreIdOptional;
|
||||
|
||||
variants[`optional.${metric}.ignored`] = CreateScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, {
|
||||
scoreData: {
|
||||
optional: {
|
||||
[metric]: 99_999,
|
||||
},
|
||||
},
|
||||
}),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
}
|
||||
|
||||
return variants;
|
||||
}
|
||||
|
||||
export const SCORE_ID_SNAPSHOT_GAMES = ALL_GAMES;
|
||||
@@ -12,28 +12,43 @@ import { CreateScoreID } from "./score-id";
|
||||
|
||||
describe("CreateScoreID", () => {
|
||||
it("returns a T-prefixed 40-hex score id", () => {
|
||||
const scoreID = CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, Testing511SPA.chartID);
|
||||
const scoreID = CreateScoreID(
|
||||
"iidx-sp",
|
||||
1,
|
||||
TestingIIDXSPDryScore,
|
||||
Testing511SPA.legacyChartID,
|
||||
);
|
||||
|
||||
expect(scoreID).toMatch(/^T[0-9a-f]{40}/u);
|
||||
});
|
||||
|
||||
it("varies with user id", () => {
|
||||
const a = CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, Testing511SPA.chartID);
|
||||
const b = CreateScoreID("iidx-sp", 2, TestingIIDXSPDryScore, Testing511SPA.chartID);
|
||||
const a = CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, Testing511SPA.legacyChartID);
|
||||
const b = CreateScoreID("iidx-sp", 2, TestingIIDXSPDryScore, Testing511SPA.legacyChartID);
|
||||
|
||||
expect(a).not.toBe(b);
|
||||
});
|
||||
|
||||
it("is stable for the same inputs", () => {
|
||||
const scoreID = CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, Testing511SPA.chartID);
|
||||
const scoreID = CreateScoreID(
|
||||
"iidx-sp",
|
||||
1,
|
||||
TestingIIDXSPDryScore,
|
||||
Testing511SPA.legacyChartID,
|
||||
);
|
||||
|
||||
expect(scoreID).toBe(
|
||||
CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, Testing511SPA.chartID),
|
||||
CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, Testing511SPA.legacyChartID),
|
||||
);
|
||||
});
|
||||
|
||||
it("only incorporates score metrics that affect the checksum", () => {
|
||||
const scoreID = CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, Testing511SPA.chartID);
|
||||
const scoreID = CreateScoreID(
|
||||
"iidx-sp",
|
||||
1,
|
||||
TestingIIDXSPDryScore,
|
||||
Testing511SPA.legacyChartID,
|
||||
);
|
||||
|
||||
expect(scoreID).toBe(
|
||||
CreateScoreID(
|
||||
@@ -49,13 +64,18 @@ describe("CreateScoreID", () => {
|
||||
},
|
||||
} as DryScoreData<"iidx-sp">,
|
||||
}),
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("changes when a provided metric changes", () => {
|
||||
const scoreID = CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, Testing511SPA.chartID);
|
||||
const scoreID = CreateScoreID(
|
||||
"iidx-sp",
|
||||
1,
|
||||
TestingIIDXSPDryScore,
|
||||
Testing511SPA.legacyChartID,
|
||||
);
|
||||
|
||||
expect(scoreID).not.toBe(
|
||||
CreateScoreID(
|
||||
@@ -66,7 +86,7 @@ describe("CreateScoreID", () => {
|
||||
score: 0,
|
||||
} as DryScoreData<"iidx-sp">,
|
||||
}),
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -76,7 +96,7 @@ describe("CreateScoreID", () => {
|
||||
"sdvx",
|
||||
1,
|
||||
TestingSDVXSingleDryScore,
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
);
|
||||
|
||||
expect(sdvxScoreID).not.toBe(
|
||||
@@ -86,7 +106,7 @@ describe("CreateScoreID", () => {
|
||||
dmf(TestingSDVXSingleDryScore, {
|
||||
scoreData: { optional: { exScore: 1 } },
|
||||
}),
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -97,7 +117,7 @@ describe("CreateScoreID", () => {
|
||||
dmf(TestingSDVXSingleDryScore, {
|
||||
scoreData: { optional: { exScore: 1 } },
|
||||
}),
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
),
|
||||
).not.toBe(
|
||||
CreateScoreID(
|
||||
@@ -106,7 +126,7 @@ describe("CreateScoreID", () => {
|
||||
dmf(TestingSDVXSingleDryScore, {
|
||||
scoreData: { optional: { exScore: 100 } },
|
||||
}),
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -119,7 +139,7 @@ describe("CreateScoreID", () => {
|
||||
dmf(TestingSDVXSingleDryScore, {
|
||||
scoreData: { optional: { exScore: 1, fast: 18 } },
|
||||
}),
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
),
|
||||
).toBe(
|
||||
CreateScoreID(
|
||||
@@ -128,7 +148,7 @@ describe("CreateScoreID", () => {
|
||||
dmf(TestingSDVXSingleDryScore, {
|
||||
scoreData: { optional: { exScore: 1 } },
|
||||
}),
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -141,7 +161,7 @@ describe("CreateScoreID", () => {
|
||||
dmf(TestingSDVXSingleDryScore, {
|
||||
scoreData: { optional: { exScore: undefined } },
|
||||
}),
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
),
|
||||
).toBe(
|
||||
CreateScoreID(
|
||||
@@ -150,14 +170,25 @@ describe("CreateScoreID", () => {
|
||||
dmf(TestingSDVXSingleDryScore, {
|
||||
scoreData: { optional: { exScore: null } },
|
||||
}),
|
||||
Testing511SPA.chartID,
|
||||
Testing511SPA.legacyChartID,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("is deterministic (canary - changing the algorithm is a breaking change)", () => {
|
||||
const scoreID = CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, Testing511SPA.chartID);
|
||||
const scoreID = CreateScoreID(
|
||||
"iidx-sp",
|
||||
1,
|
||||
TestingIIDXSPDryScore,
|
||||
Testing511SPA.legacyChartID,
|
||||
);
|
||||
|
||||
expect(scoreID).toBe("T5d669c4d5d6ca80761e87698acd77c51d2bed95b64ab76e65952dbca7c26bc81");
|
||||
});
|
||||
|
||||
it("throws when legacyChartID is not exactly 40 characters", () => {
|
||||
expect(() =>
|
||||
CreateScoreID("iidx-sp", 1, TestingIIDXSPDryScore, "not-a-legacy-chart-id"),
|
||||
).toThrow(/legacyChartID must be exactly 40 characters/u);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,16 @@ import {
|
||||
|
||||
import type { DryScore } from "../common/types";
|
||||
|
||||
export const LEGACY_CHART_ID_LENGTH = 40;
|
||||
|
||||
export function assertLegacyChartIDForScoreID(legacyChartID: string): void {
|
||||
if (legacyChartID.length !== LEGACY_CHART_ID_LENGTH) {
|
||||
throw new Error(
|
||||
`legacyChartID must be exactly ${LEGACY_CHART_ID_LENGTH} characters for score deduplication, got ${legacyChartID.length}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an identifier for this score.
|
||||
* This is used to deduplicate repeated scores.
|
||||
@@ -19,10 +29,18 @@ export function CreateScoreID(
|
||||
game: V3Game,
|
||||
userID: integer,
|
||||
dryScore: DryScore,
|
||||
chartID: string,
|
||||
legacyChartID: string,
|
||||
logger?: KtLogger,
|
||||
) {
|
||||
const elements: Record<string, number | string> = { userID, chartID };
|
||||
// scoreIDs CANNOT CHANGE. and we changed the chartID format for v3. So, we _have_ to use
|
||||
// the legacyID here instead of the pretty ID.
|
||||
//
|
||||
// TODO(zk): migrate all scoreIDs over to the new format at some point, this is not that bad
|
||||
// if we have on update cascade :3c
|
||||
assertLegacyChartIDForScoreID(legacyChartID);
|
||||
|
||||
// Score IDs were historically keyed on the mongo-era chartID (40-char SHA1 hex).
|
||||
const elements: Record<string, number | string> = { userID, chartID: legacyChartID };
|
||||
|
||||
const gameConfig = GetGameConfig(game);
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ async function HydrateCheckAndInsertScore(
|
||||
): Promise<ScoreDocument | null> {
|
||||
const game = dryScore.game;
|
||||
|
||||
const scoreID = CreateScoreID(game, userID, dryScore, chart.chartID, importLog);
|
||||
const scoreID = CreateScoreID(game, userID, dryScore, chart.legacyChartID, importLog);
|
||||
|
||||
// sub-context thelog so the below logs are more accurate
|
||||
const log = AppendLogCtx(scoreID, importLog);
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ async function seedSongs() {
|
||||
await DB.insertInto("chart")
|
||||
.values({
|
||||
id: chart.chartID,
|
||||
legacy_id: index,
|
||||
legacy_id: chart.legacyChartID,
|
||||
game: "chunithm",
|
||||
song_id: chart.song.id,
|
||||
difficulty: chart.difficulty,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "#lib/score-import/framework/common/converter-failures";
|
||||
import { DeorphanScores } from "#lib/score-import/framework/orphans/orphans";
|
||||
import { ServerConfig, TachiConfig } from "#lib/setup/config";
|
||||
import { Random20Hex } from "#utils/misc";
|
||||
import { FindChartOnSHA256, FindChartOnSHA256Playtype } from "#utils/queries/charts";
|
||||
import { FindSongOnID } from "#utils/queries/songs";
|
||||
import {
|
||||
@@ -297,6 +298,7 @@ function ConvertBeatorajaChartToTachi(chart: BeatorajaChart, game: BMSGames) {
|
||||
const chartDoc: ChartDocument<BMSGames> = {
|
||||
game,
|
||||
chartID,
|
||||
legacyChartID: Random20Hex(),
|
||||
difficulty: "CHART",
|
||||
isPrimary: true,
|
||||
level: "?",
|
||||
|
||||
@@ -303,7 +303,7 @@ describe("ConverterIRFervidex", () => {
|
||||
log,
|
||||
);
|
||||
|
||||
const scoreID = CreateScoreID("iidx-sp", userId, dryScore, chart.chartID);
|
||||
const scoreID = CreateScoreID("iidx-sp", userId, dryScore, chart.legacyChartID);
|
||||
const hydrated = HydrateScore(userId, dryScore, chart, song, scoreID, log);
|
||||
const { data, derived, judgements } = mongoScoreDataToPg("iidx-sp", hydrated.scoreData);
|
||||
const now = new Date().toISOString();
|
||||
|
||||
@@ -262,7 +262,7 @@ export const ConverterIRFervidex: ConverterFunction<FervidexScore, FervidexConte
|
||||
// marked as a duplicate, but with highlight set. As such, we should highlight
|
||||
// the score this is for.
|
||||
if (data.highlight === true) {
|
||||
const scoreID = CreateScoreID(game, context.userID, dryScore, chart.chartID);
|
||||
const scoreID = CreateScoreID(game, context.userID, dryScore, chart.legacyChartID);
|
||||
|
||||
await DB.transaction().execute(async (trx) => {
|
||||
const scoreRow = await trx
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
/**
|
||||
* Compare CreateScoreID output at HEAD vs eed374649 (2026-02-01).
|
||||
*
|
||||
* Usage: bun run src/scripts/compare-score-id-feb1.ts
|
||||
*/
|
||||
import type { DryScore } from "#lib/score-import/framework/common/types";
|
||||
import {
|
||||
buildCanonicalDryScore,
|
||||
SCORE_ID_ALT_CHART_ID,
|
||||
SCORE_ID_ALT_USER_ID,
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
SCORE_ID_SNAPSHOT_GAMES,
|
||||
} from "#lib/score-import/framework/score-importing/score-id-fixtures";
|
||||
import { collectScoreIdVariants } from "#lib/score-import/framework/score-importing/score-id-fixtures";
|
||||
import { dmf } from "#test-utils/misc";
|
||||
import fjsh from "fast-json-stable-hash";
|
||||
import { GetGPTConfig } from "/tmp/tachi-feb1/common/src/config/config.ts";
|
||||
import {
|
||||
GetGameConfig,
|
||||
LEGACY_GameToGPTString,
|
||||
type ConfScoreMetric,
|
||||
type V3Game,
|
||||
} from "tachi-common";
|
||||
|
||||
const LEGACY_COMMIT = "eed37464970d6863454508ea5064e627b8fd0a33";
|
||||
|
||||
type ScoreIdElements = Record<string, number | string | null>;
|
||||
|
||||
function isEnumMetric(
|
||||
conf: ConfScoreMetric,
|
||||
): conf is ConfScoreMetric & { type: "ENUM"; values: Array<string> } {
|
||||
return conf.type === "ENUM";
|
||||
}
|
||||
|
||||
function baselineProvidedValue(conf: ConfScoreMetric): number | string {
|
||||
switch (conf.type) {
|
||||
case "INTEGER":
|
||||
return 500_000;
|
||||
case "DECIMAL":
|
||||
return 97.53;
|
||||
case "ENUM": {
|
||||
if (isEnumMetric(conf) && conf.minimumRelevantValue) {
|
||||
return conf.minimumRelevantValue;
|
||||
}
|
||||
|
||||
return isEnumMetric(conf) ? conf.values[Math.min(2, conf.values.length - 1)]! : "CLEAR";
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported provided metric type: ${(conf as ConfScoreMetric).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function alternateProvidedValue(conf: ConfScoreMetric, baseline: number | string): number | string {
|
||||
switch (conf.type) {
|
||||
case "INTEGER":
|
||||
return baseline === 500_000 ? 123_456 : 500_000;
|
||||
case "DECIMAL":
|
||||
return baseline === 97.53 ? 88.88 : 97.53;
|
||||
case "ENUM": {
|
||||
if (!isEnumMetric(conf)) {
|
||||
throw new Error("Expected enum metric");
|
||||
}
|
||||
|
||||
const idx = conf.values.indexOf(baseline as string);
|
||||
|
||||
return conf.values[(idx + 1) % conf.values.length]!;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported provided metric type: ${(conf as ConfScoreMetric).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function baselineOptionalPartOfScoreIdValue(conf: ConfScoreMetric): number | string {
|
||||
switch (conf.type) {
|
||||
case "INTEGER":
|
||||
return 777;
|
||||
case "DECIMAL":
|
||||
return 12.34;
|
||||
case "ENUM": {
|
||||
if (isEnumMetric(conf) && conf.minimumRelevantValue) {
|
||||
return conf.minimumRelevantValue;
|
||||
}
|
||||
|
||||
return isEnumMetric(conf) ? conf.values[0]! : "0";
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported partOfScoreID optional metric type: ${(conf as ConfScoreMetric).type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function buildLegacyElements(
|
||||
game: V3Game,
|
||||
userID: number,
|
||||
dryScore: DryScore,
|
||||
chartID: string,
|
||||
): ScoreIdElements {
|
||||
const gptString = LEGACY_GameToGPTString(game);
|
||||
const gptConfig = GetGPTConfig(gptString);
|
||||
const elements: ScoreIdElements = { userID, chartID };
|
||||
|
||||
for (const metric of Object.keys(gptConfig.providedMetrics)) {
|
||||
elements[metric] = dryScore.scoreData[metric as keyof typeof dryScore.scoreData] as
|
||||
| number
|
||||
| string;
|
||||
}
|
||||
|
||||
for (const [metric, conf] of Object.entries(gptConfig.optionalMetrics)) {
|
||||
if (conf.partOfScoreID) {
|
||||
elements[`optional.${metric}`] = dryScore.scoreData.optional[metric] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
function createLegacyScoreID(
|
||||
game: V3Game,
|
||||
userID: number,
|
||||
dryScore: DryScore,
|
||||
chartID: string,
|
||||
): string {
|
||||
const elements = buildLegacyElements(game, userID, dryScore, chartID);
|
||||
|
||||
return `T${fjsh.hash(elements, "sha256")}`;
|
||||
}
|
||||
|
||||
function buildCurrentElements(
|
||||
game: V3Game,
|
||||
userID: number,
|
||||
dryScore: DryScore,
|
||||
legacyChartID: string,
|
||||
): ScoreIdElements {
|
||||
const gameConfig = GetGameConfig(game);
|
||||
const elements: ScoreIdElements = { userID, chartID: legacyChartID };
|
||||
|
||||
for (const metric of Object.keys(gameConfig.providedMetrics)) {
|
||||
elements[metric] = dryScore.scoreData[metric as keyof typeof dryScore.scoreData] as
|
||||
| number
|
||||
| string;
|
||||
}
|
||||
|
||||
for (const [metric, conf] of Object.entries(gameConfig.optionalMetrics)) {
|
||||
if (conf.partOfScoreID) {
|
||||
elements[`optional.${metric}`] = dryScore.scoreData.optional[metric] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
function resolveVariantInputs(
|
||||
game: V3Game,
|
||||
variant: string,
|
||||
): { dryScore: DryScore; userID: number; chartID: string } {
|
||||
const dryScore = buildCanonicalDryScore(game);
|
||||
const gameConfig = GetGameConfig(game);
|
||||
|
||||
if (variant === "baseline") {
|
||||
return {
|
||||
dryScore,
|
||||
userID: SCORE_ID_CANONICAL_USER_ID,
|
||||
chartID: SCORE_ID_CANONICAL_CHART_ID,
|
||||
};
|
||||
}
|
||||
|
||||
if (variant === "userID") {
|
||||
return { dryScore, userID: SCORE_ID_ALT_USER_ID, chartID: SCORE_ID_CANONICAL_CHART_ID };
|
||||
}
|
||||
|
||||
if (variant === "chartID") {
|
||||
return { dryScore, userID: SCORE_ID_CANONICAL_USER_ID, chartID: SCORE_ID_ALT_CHART_ID };
|
||||
}
|
||||
|
||||
if (variant.startsWith("provided.")) {
|
||||
const metric = variant.slice("provided.".length);
|
||||
const conf = gameConfig.providedMetrics[metric]!;
|
||||
const alternate = alternateProvidedValue(conf, baselineProvidedValue(conf));
|
||||
|
||||
return {
|
||||
dryScore: dmf(dryScore, { scoreData: { [metric]: alternate } }),
|
||||
userID: SCORE_ID_CANONICAL_USER_ID,
|
||||
chartID: SCORE_ID_CANONICAL_CHART_ID,
|
||||
};
|
||||
}
|
||||
|
||||
if (variant.startsWith("optional.")) {
|
||||
const rest = variant.slice("optional.".length);
|
||||
const dot = rest.lastIndexOf(".");
|
||||
const metric = rest.slice(0, dot);
|
||||
const mode = rest.slice(dot + 1);
|
||||
const conf = gameConfig.optionalMetrics[metric]!;
|
||||
|
||||
let value: number | string | null | undefined;
|
||||
|
||||
if (mode === "set") {
|
||||
value = baselineOptionalPartOfScoreIdValue(conf);
|
||||
} else if (mode === "null") {
|
||||
value = null;
|
||||
} else if (mode === "unset") {
|
||||
value = undefined;
|
||||
} else if (mode === "ignored") {
|
||||
value = 99_999;
|
||||
} else {
|
||||
throw new Error(`Unknown optional variant ${variant}`);
|
||||
}
|
||||
|
||||
return {
|
||||
dryScore: dmf(dryScore, { scoreData: { optional: { [metric]: value } } }),
|
||||
userID: SCORE_ID_CANONICAL_USER_ID,
|
||||
chartID: SCORE_ID_CANONICAL_CHART_ID,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unknown variant ${variant}`);
|
||||
}
|
||||
|
||||
function collectLegacyScoreIdVariants(game: V3Game): Record<string, string> {
|
||||
const dryScore = buildCanonicalDryScore(game);
|
||||
const gameConfig = GetGameConfig(game);
|
||||
|
||||
const variants: Record<string, string> = {
|
||||
baseline: createLegacyScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dryScore,
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
),
|
||||
userID: createLegacyScoreID(
|
||||
game,
|
||||
SCORE_ID_ALT_USER_ID,
|
||||
dryScore,
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
),
|
||||
chartID: createLegacyScoreID(game, SCORE_ID_CANONICAL_USER_ID, dryScore, SCORE_ID_ALT_CHART_ID),
|
||||
};
|
||||
|
||||
for (const [metric, conf] of Object.entries(gameConfig.providedMetrics)) {
|
||||
const alternate = alternateProvidedValue(conf, baselineProvidedValue(conf));
|
||||
|
||||
variants[`provided.${metric}`] = createLegacyScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, { scoreData: { [metric]: alternate } }),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
}
|
||||
|
||||
for (const [metric, conf] of Object.entries(gameConfig.optionalMetrics)) {
|
||||
if (!conf.partOfScoreID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const setValue = baselineOptionalPartOfScoreIdValue(conf);
|
||||
|
||||
variants[`optional.${metric}.set`] = createLegacyScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, { scoreData: { optional: { [metric]: setValue } } }),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
|
||||
variants[`optional.${metric}.null`] = createLegacyScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, { scoreData: { optional: { [metric]: null } } }),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
|
||||
variants[`optional.${metric}.unset`] = createLegacyScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, { scoreData: { optional: { [metric]: undefined } } }),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
}
|
||||
|
||||
const firstNonScoreIdOptional = Object.entries(gameConfig.optionalMetrics).find(
|
||||
([, conf]) => !conf.partOfScoreID,
|
||||
);
|
||||
|
||||
if (firstNonScoreIdOptional) {
|
||||
const [metric] = firstNonScoreIdOptional;
|
||||
|
||||
variants[`optional.${metric}.ignored`] = createLegacyScoreID(
|
||||
game,
|
||||
SCORE_ID_CANONICAL_USER_ID,
|
||||
dmf(dryScore, { scoreData: { optional: { [metric]: 99_999 } } }),
|
||||
SCORE_ID_CANONICAL_CHART_ID,
|
||||
);
|
||||
}
|
||||
|
||||
return variants;
|
||||
}
|
||||
|
||||
function main() {
|
||||
let total = 0;
|
||||
let matched = 0;
|
||||
const mismatches: Array<string> = [];
|
||||
const configDrift: Array<string> = [];
|
||||
|
||||
for (const game of SCORE_ID_SNAPSHOT_GAMES) {
|
||||
const currentVariants = collectScoreIdVariants(game);
|
||||
const legacyVariants = collectLegacyScoreIdVariants(game);
|
||||
|
||||
for (const [variant, currentId] of Object.entries(currentVariants)) {
|
||||
total++;
|
||||
const legacyId = legacyVariants[variant];
|
||||
|
||||
if (currentId === legacyId) {
|
||||
matched++;
|
||||
continue;
|
||||
}
|
||||
|
||||
mismatches.push(`${game} ${variant}: current=${currentId} legacy=${legacyId}`);
|
||||
|
||||
const { dryScore, userID, chartID } = resolveVariantInputs(game, variant);
|
||||
const currentElements = buildCurrentElements(game, userID, dryScore, chartID);
|
||||
const legacyElements = buildLegacyElements(game, userID, dryScore, chartID);
|
||||
|
||||
if (JSON.stringify(currentElements) === JSON.stringify(legacyElements)) {
|
||||
configDrift.push(`${game} ${variant}: hash inputs match but ids differ (algorithm drift!)`);
|
||||
} else {
|
||||
configDrift.push(`${game} ${variant}: hash inputs differ (likely config drift)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`CreateScoreID comparison vs ${LEGACY_COMMIT} (2026-02-01)`);
|
||||
console.log(`Games: ${SCORE_ID_SNAPSHOT_GAMES.length}, variants checked: ${total}`);
|
||||
console.log(`Matched: ${matched}/${total}`);
|
||||
|
||||
if (mismatches.length === 0) {
|
||||
console.log("All inputs produce identical score ids.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log("\nMismatches:");
|
||||
for (const line of mismatches) {
|
||||
console.log(` ${line}`);
|
||||
}
|
||||
|
||||
console.log("\nDiagnosis:");
|
||||
for (const line of configDrift) {
|
||||
console.log(` ${line}`);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -328,6 +328,7 @@ export const TestingSDVXAlbidaSong: SongDocument<"sdvx"> = {
|
||||
export const TestingSDVXAlbidaChart: ChartDocument<"sdvx"> = {
|
||||
game: "sdvx",
|
||||
chartID: "5088a4d0e1ee9d0cc2f625934306e45b1a60699b",
|
||||
legacyChartID: "5088a4d0e1ee9d0cc2f625934306e45b1a60699b",
|
||||
difficulty: "ADV",
|
||||
song: TestingSDVXAlbidaSong,
|
||||
levelNum: 10,
|
||||
@@ -444,6 +445,7 @@ export const TestingJubeatSong: SongDocument<"jubeat"> = {
|
||||
export const TestingJubeatChart: ChartDocument<"jubeat"> = {
|
||||
game: "jubeat",
|
||||
chartID: "b90a319f18d1a746b330b8f4cd6f74874f664421",
|
||||
legacyChartID: "b90a319f18d1a746b330b8f4cd6f74874f664421",
|
||||
song: TestingJubeatSong,
|
||||
level: "6",
|
||||
levelNum: 6,
|
||||
@@ -469,6 +471,7 @@ export const TestingGitadoraSong: SongDocument<"gitadora"> = {
|
||||
export const TestingGitadoraChart: ChartDocument<"gitadora-dora"> = {
|
||||
game: "gitadora-dora",
|
||||
chartID: "bf62dd6c78afaf6b4f95cce70b5b08af193c0c57",
|
||||
legacyChartID: "bf62dd6c78afaf6b4f95cce70b5b08af193c0c57",
|
||||
data: {
|
||||
inGameID: 0,
|
||||
},
|
||||
@@ -496,6 +499,7 @@ export const TestingMaimaiSong: SongDocument<"maimai"> = {
|
||||
export const TestingMaimaiChart: ChartDocument<"maimai"> = {
|
||||
game: "maimai",
|
||||
chartID: "6e087e5f58519a2697374b9bff776a004046e952",
|
||||
legacyChartID: "6e087e5f58519a2697374b9bff776a004046e952",
|
||||
data: {
|
||||
inGameID: 834,
|
||||
inGameStrID: "pandora_paradoxxx",
|
||||
@@ -524,6 +528,7 @@ export const TestingMaimaiDXSong: SongDocument<"maimaidx"> = {
|
||||
export const TestingMaimaiDXChart: ChartDocument<"maimaidx"> = {
|
||||
game: "maimaidx",
|
||||
chartID: "fbde73d543f93cbad9ec67052a7165700c5c8df6",
|
||||
legacyChartID: "fbde73d543f93cbad9ec67052a7165700c5c8df6",
|
||||
data: {
|
||||
displayVersion: "maimai",
|
||||
inGameID: 1,
|
||||
@@ -552,6 +557,7 @@ export const TestingMusecaSong: SongDocument<"museca"> = {
|
||||
export const TestingMusecaChart: ChartDocument<"museca"> = {
|
||||
game: "museca",
|
||||
chartID: "4f3a7bf907f7be54a0c531dd9077e58fd7b6bb9c",
|
||||
legacyChartID: "4f3a7bf907f7be54a0c531dd9077e58fd7b6bb9c",
|
||||
data: {
|
||||
inGameID: 1,
|
||||
},
|
||||
@@ -579,6 +585,7 @@ export const TestingPopnSong: SongDocument<"popn"> = {
|
||||
export const TestingPopnChart: ChartDocument<"popn"> = {
|
||||
game: "popn",
|
||||
chartID: "7489166ddfb6ec780249823d5bda255611fbc528",
|
||||
legacyChartID: "7489166ddfb6ec780249823d5bda255611fbc528",
|
||||
data: {
|
||||
hashSHA256: "83287260cb32c5c54b06b76967ba8620dd3735063959d9fbaf2cffc0dba8a049",
|
||||
inGameID: 0,
|
||||
@@ -606,6 +613,7 @@ export const Testing511Song: SongDocument<"iidx"> = {
|
||||
export const Testing511SPA: ChartDocument<"iidx-sp"> = {
|
||||
game: "iidx-sp",
|
||||
chartID: "c2311194e3897ddb5745b1760d2c0141f933e683",
|
||||
legacyChartID: "c2311194e3897ddb5745b1760d2c0141f933e683",
|
||||
difficulty: "ANOTHER",
|
||||
song: Testing511Song,
|
||||
levelNum: 10,
|
||||
@@ -653,6 +661,7 @@ export const TestingDDRSong: SongDocument<"ddr"> = {
|
||||
export const TestingDDRSP: ChartDocument<"ddr-sp"> = {
|
||||
game: "ddr-sp",
|
||||
chartID: "2fc2e0cfdda42addb7840b58be1df1f545310d66",
|
||||
legacyChartID: "2fc2e0cfdda42addb7840b58be1df1f545310d66",
|
||||
data: {
|
||||
inGameID: 10,
|
||||
},
|
||||
@@ -667,6 +676,7 @@ export const TestingDDRSP: ChartDocument<"ddr-sp"> = {
|
||||
export const TestingAlbidaADV: ChartDocument<"sdvx"> = {
|
||||
game: "sdvx",
|
||||
chartID: "5088a4d0e1ee9d0cc2f625934306e45b1a60699b",
|
||||
legacyChartID: "5088a4d0e1ee9d0cc2f625934306e45b1a60699b",
|
||||
difficulty: "ADV",
|
||||
song: TestingSDVXAlbidaSong,
|
||||
levelNum: 10,
|
||||
@@ -697,6 +707,7 @@ export const BMSGazerChart: ChartDocument<"bms-7k"> = {
|
||||
game: "bms-7k",
|
||||
song: BMSGazerSong,
|
||||
chartID: "88eb6cc5683e2740cbd07f588a5f3db1db8d467b",
|
||||
legacyChartID: "88eb6cc5683e2740cbd07f588a5f3db1db8d467b",
|
||||
data: {
|
||||
aiLevel: "0",
|
||||
notecount: 2256,
|
||||
@@ -731,6 +742,7 @@ export const CHUNITHMBBKKSong: SongDocument<"chunithm"> = {
|
||||
export const CHUNITHMBBKKChart: ChartDocument<"chunithm"> = {
|
||||
game: "chunithm",
|
||||
chartID: "192b96bdb6150f80ba6412ce02df1249e16c0cb0",
|
||||
legacyChartID: "192b96bdb6150f80ba6412ce02df1249e16c0cb0",
|
||||
difficulty: "BASIC",
|
||||
song: CHUNITHMBBKKSong,
|
||||
levelNum: 3,
|
||||
@@ -780,6 +792,7 @@ export const TestingDoraChart: ChartDocument<"gitadora-dora"> = {
|
||||
game: "gitadora-dora",
|
||||
song: TestingGitadoraSong,
|
||||
chartID: "29f0bfab357ba54e3fd0176fb3cbc578c9ec8df5",
|
||||
legacyChartID: "29f0bfab357ba54e3fd0176fb3cbc578c9ec8df5",
|
||||
difficulty: "BASIC",
|
||||
levelNum: 1.6,
|
||||
level: "1.60",
|
||||
@@ -806,6 +819,7 @@ export const TestingWaccaPupaExp: ChartDocument<"wacca"> = {
|
||||
game: "wacca",
|
||||
song: TestingWaccaPupaSong,
|
||||
chartID: "2abcf2da86f3d968dbe07695ab7ebf6b79cb0841",
|
||||
legacyChartID: "2abcf2da86f3d968dbe07695ab7ebf6b79cb0841",
|
||||
difficulty: "EXPERT",
|
||||
levelNum: 13.7,
|
||||
level: "13+",
|
||||
@@ -1046,6 +1060,7 @@ export const TestingUSCSong: SongDocument<"usc"> = {
|
||||
export const TestingUSCChart: ChartDocument<"usc-keyboard"> = {
|
||||
game: "usc-keyboard",
|
||||
chartID: "USC_CHART_ID",
|
||||
legacyChartID: "0000000000000000000000000000000000000001",
|
||||
difficulty: "NOV",
|
||||
song: TestingUSCSong,
|
||||
levelNum: 1,
|
||||
@@ -1454,6 +1469,7 @@ export const TestingArcaeaSheriruthSong: SongDocument<"arcaea"> = {
|
||||
export const TestingArcaeaSheriruthFTR: ChartDocument<"arcaea"> = {
|
||||
game: "arcaea",
|
||||
chartID: "48de13e0be17a282eed6ef97e6e1478eb59bc55e",
|
||||
legacyChartID: "48de13e0be17a282eed6ef97e6e1478eb59bc55e",
|
||||
data: {
|
||||
inGameStrID: "sheriruth",
|
||||
notecount: 1151,
|
||||
@@ -1481,6 +1497,7 @@ export const TestingOngekiSong: SongDocument<"ongeki"> = {
|
||||
export const TestingOngekiChart: ChartDocument<"ongeki"> = {
|
||||
game: "ongeki",
|
||||
chartID: "213796bdb6150f80ba6412ce69df1249e16c0cb0",
|
||||
legacyChartID: "213796bdb6150f80ba6412ce69df1249e16c0cb0",
|
||||
data: {
|
||||
displayVersion: "オンゲキ bright MEMORY Act.3",
|
||||
inGameID: 2137,
|
||||
@@ -1534,6 +1551,7 @@ export const TestingOngekiScorePB: PBScoreDocument<"ongeki"> = {
|
||||
export const TestingOngekiChartConverter: ChartDocument<"ongeki"> = {
|
||||
game: "ongeki",
|
||||
chartID: "e5e4ee3d4feb233c399751b3ba3daf8ba149c9e6",
|
||||
legacyChartID: "e5e4ee3d4feb233c399751b3ba3daf8ba149c9e6",
|
||||
data: {
|
||||
displayVersion: "オンゲキ bright",
|
||||
inGameID: 678,
|
||||
@@ -1564,6 +1582,7 @@ export const TestingChunithmSongConverter: SongDocument<"chunithm"> = {
|
||||
export const TestingChunithmChartConverter: ChartDocument<"chunithm"> = {
|
||||
game: "chunithm",
|
||||
chartID: "fbba26a6a223b1dd0bf8ef139cfee83114b91086",
|
||||
legacyChartID: "fbba26a6a223b1dd0bf8ef139cfee83114b91086",
|
||||
data: {
|
||||
inGameID: 956,
|
||||
},
|
||||
@@ -1595,6 +1614,7 @@ export const TestingChunithmWEChartConverter: ChartDocument<"chunithm"> = {
|
||||
},
|
||||
difficulty: "割☆4",
|
||||
chartID: "C19e10e7a402af3865b8",
|
||||
legacyChartID: "e5e4ee3d4feb233c399751b3ba3daf8ba149c9e6",
|
||||
isPrimary: true,
|
||||
level: "",
|
||||
levelNum: 0,
|
||||
@@ -1616,6 +1636,7 @@ export const TestingMaimaiDXSongConverter: SongDocument<"maimaidx"> = {
|
||||
export const TestingMaimaiDXChartConverter: ChartDocument<"maimaidx"> = {
|
||||
game: "maimaidx",
|
||||
chartID: "fab3d632610b9b98ee1e4f68e9ecf0161f9cb8cd",
|
||||
legacyChartID: "fab3d632610b9b98ee1e4f68e9ecf0161f9cb8cd",
|
||||
data: {
|
||||
displayVersion: "maimaiでらっくす UNiVERSE",
|
||||
inGameID: 11294,
|
||||
|
||||
Reference in New Issue
Block a user