feat: iidx seeds and rerunners update (#1711)

* fix: update iidx-mdb-parse for v3

* fix: iidx-mdb-parse: check if legg conflict warning is necessary

* seed: update iidx to 20260616

* feat: enable ferv imports for iidx 33

* seed: fix iidx infinitas->ac ports

* seed: update IIDX SP12 tiers

* feat: IIDX SP12 (and 11 sorta) tierlist update rerunner

* seed: iidx eamuse csv titles update

* refactor: don't expose randomHex, it's an internal fn, just inline again for now until I fix things up

---------

Co-authored-by: zk <zkldi@proton.me>
This commit is contained in:
GRIM657
2026-07-12 13:28:03 +01:00
committed by GitHub
co-authored by zk
parent dba4942514
commit fd694e2da9
8 changed files with 9862 additions and 128 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+709 -1
View File
@@ -3309,6 +3309,7 @@
"artist": "AKIRA YAMAOKA",
"data": {
"displayVersion": "7",
"eamusementCsvTitle": "i feel...",
"genre": "TRANCE"
},
"id": "S19d35e0afd14495fc26",
@@ -9848,6 +9849,7 @@
"artist": "TAKA respect for J.S.B.",
"data": {
"displayVersion": "15",
"eamusementCsvTitle": "Ubertreffen",
"genre": "PROGRESSIVE BAROQUE"
},
"id": "S19d35e0b1de03f23552",
@@ -10388,6 +10390,7 @@
"artist": "上野圭市(aka. Babyweapon,DJ SWAMI)feat.星野奏子",
"data": {
"displayVersion": "16",
"eamusementCsvTitle": "Punch Love 仮面",
"genre": "E-DANCE POP"
},
"id": "S19d35e0b20ea1acdfc0",
@@ -13832,6 +13835,7 @@
"artist": "劇団レコード",
"data": {
"displayVersion": "20",
"eamusementCsvTitle": "YELLOW FROG from Steel Chronicle",
"genre": "DESERT ALTERNATIVE EDGE"
},
"id": "S19d35e0b32c0098974d",
@@ -13846,6 +13850,7 @@
"artist": "dj TAKA feat.wac & secret K",
"data": {
"displayVersion": "20",
"eamusementCsvTitle": "Time to Empress",
"genre": "TRANCE"
},
"id": "S19d35e0b32dc934f33e",
@@ -14625,6 +14630,7 @@
"artist": "楓璃夢=ジークフリード=ファイエルローゼン",
"data": {
"displayVersion": "21",
"eamusementCsvTitle": "旋律のドグマ~Miserables",
"genre": "JUVENILE DRUM & BASS"
},
"id": "S19d35e0b36cf10b8206",
@@ -14756,6 +14762,7 @@
"artist": "天叢雲剣",
"data": {
"displayVersion": "21",
"eamusementCsvTitle": "Close the World feat.a☆ru",
"genre": "J-CORE"
},
"id": "S19d35e0b376b3400853",
@@ -15362,6 +15369,7 @@
"artist": "TAG underground",
"data": {
"displayVersion": "21",
"eamusementCsvTitle": "ATHER",
"genre": "ASTRAL HARDCORE"
},
"id": "S19d35e0b3a535522359",
@@ -15798,6 +15806,7 @@
"artist": "機龍",
"data": {
"displayVersion": "22",
"eamusementCsvTitle": "Rave*it!! Rave*it!! ",
"genre": "EDM SPEED"
},
"id": "S19d35e0b3dcd6c8243d",
@@ -16439,6 +16448,7 @@
"artist": "Y&Co.",
"data": {
"displayVersion": "22",
"eamusementCsvTitle": "TA・DA☆YO・SHI",
"genre": "EUROBEAT"
},
"id": "S19d35e0b40e9bff9379",
@@ -16913,6 +16923,7 @@
"artist": "Hommarju",
"data": {
"displayVersion": "22",
"eamusementCsvTitle": "Sounds Of Summer ",
"genre": "UK HARDCORE"
},
"id": "S19d35e0b4341f5e547d",
@@ -18270,6 +18281,7 @@
"artist": "Ryu*",
"data": {
"displayVersion": "24",
"eamusementCsvTitle": "!Viva!",
"genre": "TRIBAL HOUSE"
},
"id": "S19d35e0b4c01dddda3d",
@@ -18479,6 +18491,7 @@
"artist": "RoughSkreamZ feat.楽天斎",
"data": {
"displayVersion": "24",
"eamusementCsvTitle": "火影",
"genre": "HIP HOP"
},
"id": "S19d35e0b4d9a7e63d39",
@@ -19331,6 +19344,7 @@
"artist": "BEMANI Sound Team \"TAG\"",
"data": {
"displayVersion": "26",
"eamusementCsvTitle": "BLOSSOM",
"genre": "BLOOM TRANCE"
},
"id": "S19d35e0b51d9e2dc98c",
@@ -19488,6 +19502,7 @@
"artist": "kors k",
"data": {
"displayVersion": "26",
"eamusementCsvTitle": "Macho Monky",
"genre": "JUNGLE TERROR"
},
"id": "S19d35e0b5295ee51691",
@@ -19743,6 +19758,7 @@
"artist": "Y&Co.",
"data": {
"displayVersion": "26",
"eamusementCsvTitle": "ROCK女 feat. 大山愛未, Ken",
"genre": "TRANCE"
},
"id": "S19d35e0b53d3ccd451d",
@@ -19997,6 +20013,7 @@
"artist": "sasakure.UK",
"data": {
"displayVersion": "26",
"eamusementCsvTitle": "Xlo",
"genre": "NEUTRONIKA"
},
"id": "S19d35e0b5516f90f625",
@@ -20356,6 +20373,7 @@
"artist": "Maozon",
"data": {
"displayVersion": "26",
"eamusementCsvTitle": "Geirskogul",
"genre": "CYBER DRUMSTEP"
},
"id": "S19d35e0b56d7e9d3f4e",
@@ -20894,6 +20912,7 @@
"artist": "RoughSketch",
"data": {
"displayVersion": "25",
"eamusementCsvTitle": "Life Is A Game ft.DD\"ナカタ\"",
"genre": "UPTEMPO HARDCORE"
},
"id": "S19d35e0b5991c24de0b",
@@ -21285,6 +21304,7 @@
"artist": "D-Evoke(与那嶺雅人/小日向翔)",
"data": {
"displayVersion": "25",
"eamusementCsvTitle": "共犯ヘヴンズコード",
"genre": "ELEGANT SWING"
},
"id": "S19d35e0b5b8bd078201",
@@ -21785,6 +21805,7 @@
"artist": "MOSAIC.WAV",
"data": {
"displayVersion": "25",
"eamusementCsvTitle": "パラドキシカル・タイムリープトライアル(Short.Ver)",
"genre": "AKIBA-POP"
},
"id": "S19d35e0b5e004a8b9df",
@@ -21837,6 +21858,7 @@
"artist": "Dirty Androids",
"data": {
"displayVersion": "25",
"eamusementCsvTitle": "City Never Sleeps (IIDX EDITION)",
"genre": "DRUM&BASS"
},
"id": "S19d35e0b5e4fcfbab69",
@@ -21911,6 +21933,7 @@
"artist": "BEMANI Sound Team \"Sota F.\"",
"data": {
"displayVersion": "25",
"eamusementCsvTitle": "Let's bounce !!",
"genre": "FUTURE HOUSE"
},
"id": "S19d35e0b5ea8fddea95",
@@ -22017,6 +22040,7 @@
"artist": "T.Σ.Generation",
"data": {
"displayVersion": "26",
"eamusementCsvTitle": "Χ-DEN",
"genre": "ULTIΣATE COLLABORATION"
},
"id": "S19d35e0b5f2303164f0",
@@ -22107,6 +22131,7 @@
"artist": "猫叉Master",
"data": {
"displayVersion": "20",
"eamusementCsvTitle": "Reflection Into the EDEN",
"genre": "BOSSA ELECTRONICA"
},
"id": "S19d35e0b5f9e932a3da",
@@ -22210,6 +22235,7 @@
"artist": "BEMANI Sound Team \"レ・度胸兄弟\"",
"data": {
"displayVersion": "27",
"eamusementCsvTitle": "Dans la nuit de l'eternite",
"genre": "POLONAISE"
},
"id": "S19d35e0b602eaa58303",
@@ -23578,6 +23604,7 @@
"artist": "BEMANI Sound Team Sota Fujimori",
"data": {
"displayVersion": "28",
"eamusementCsvTitle": "Breakin' Rules",
"genre": "CYBER PUNK"
},
"id": "S19d35e0b670854ca114",
@@ -23592,6 +23619,7 @@
"artist": "BEMANI Sound Team Sota Fujimori",
"data": {
"displayVersion": "28",
"eamusementCsvTitle": "Hands Up feat. kradness BEMANI Sound Team \"Sota Fujimori\" Remix",
"genre": "THE 4TH"
},
"id": "S19d35e0b6714d682759",
@@ -23882,6 +23910,7 @@
"artist": "Nhato",
"data": {
"displayVersion": "28",
"eamusementCsvTitle": "Dont Stop The Music feat. Kanae Asaba",
"genre": "ELECTRO FUNK"
},
"id": "S19d35e0b688cdf86f5a",
@@ -23923,6 +23952,7 @@
"artist": "かめりあ feat. ななひら",
"data": {
"displayVersion": "28",
"eamusementCsvTitle": "POLKAMANIA",
"genre": "ポルカ2020"
},
"id": "S19d35e0b68b00229000",
@@ -24469,6 +24499,7 @@
"artist": "BEMANI Sound Team \"U1 overground\"",
"data": {
"displayVersion": "28",
"eamusementCsvTitle": "Parvati",
"genre": "ORBITALIC WORLD MUSIC"
},
"id": "S19d35e0b6b7cea13e16",
@@ -24715,6 +24746,7 @@
"artist": "BEMANI Sound Team \"神\"",
"data": {
"displayVersion": "28",
"eamusementCsvTitle": "Ignis†Irae",
"genre": "X"
},
"id": "S19d35e0b6cbe5666c1e",
@@ -25788,6 +25820,7 @@
"artist": "MK",
"data": {
"displayVersion": "29",
"eamusementCsvTitle": "Get set Go! feat.Kanae Asaba",
"genre": "FUTURE BOUNCE"
},
"id": "S19d35e0b7237cb6d652",
@@ -26840,6 +26873,7 @@
"artist": "かめりあ",
"data": {
"displayVersion": "30",
"eamusementCsvTitle": "Ou Legends",
"genre": "TOHOKU NEURO DENSETSU"
},
"id": "S19d35e0b778cda770ea",
@@ -27472,6 +27506,7 @@
"artist": "GeMiNioИ",
"data": {
"displayVersion": "30",
"eamusementCsvTitle": "RINNE",
"genre": "SYMPHONIC SAVIOR"
},
"id": "S19d35e0b7aa4f36d79f",
@@ -27618,6 +27653,7 @@
"artist": "天土",
"data": {
"displayVersion": "30",
"eamusementCsvTitle": "異心前進♪ぴょんぴょんぴょん ",
"genre": "EUROBEAT"
},
"id": "S19d35e0b7b5c5fe360d",
@@ -28962,6 +28998,7 @@
"artist": "モリモリあつし",
"data": {
"displayVersion": "31",
"eamusementCsvTitle": "ACT0",
"genre": "NEOTRANCE CORE"
},
"id": "S19d35e0b81dbf132cab",
@@ -29487,6 +29524,7 @@
"artist": "BEMANI Sound Team \"TATSUYA\" feat. ななひら",
"data": {
"displayVersion": "32",
"eamusementCsvTitle": "最強頂戴girl",
"genre": "TWINKLE POP"
},
"id": "S19d35e0b84637d5b3be",
@@ -29637,6 +29675,7 @@
"artist": "BlackY feat. Risa Yuzuki",
"data": {
"displayVersion": "32",
"eamusementCsvTitle": "FERMI PARADOX",
"genre": "COSMIC HAPPY TUNE"
},
"id": "S19d35e0b8522759e6f6",
@@ -29727,6 +29766,7 @@
"artist": "lapix feat. PANXI",
"data": {
"displayVersion": "32",
"eamusementCsvTitle": "オッド ラヴィ",
"genre": "LOVELY TECHCORE"
},
"id": "S19d35e0b85914e9c8a3",
@@ -29883,6 +29923,7 @@
"artist": "Tatsunoshin & 藍良あれれ",
"data": {
"displayVersion": "32",
"eamusementCsvTitle": "Raspberry Potion (feat.あれたん & ぎゃるのしん☆)",
"genre": "LOVE♡CORE"
},
"id": "S19d35e0b8658b46a23f",
@@ -31065,10 +31106,13 @@
"title": "From Human Blood"
},
{
"altTitles": [],
"altTitles": [
"Space Battleship S4TO"
],
"artist": "BEMANI Sound Team \"Captain Sonic\"",
"data": {
"displayVersion": "33",
"eamusementCsvTitle": "Space Battleship S4TO",
"genre": "BIGCORE"
},
"id": "S19d35e0b8c03f018a8e",
@@ -31789,5 +31833,669 @@
"Rush"
],
"title": "Rush!!"
},
{
"altTitles": [],
"artist": "ビートまりお Prod. by Yuta Imai & L.E.D.-G",
"data": {
"displayVersion": "33",
"genre": "HARD MIXTURE"
},
"id": "S19f39a108de1fe0d14d",
"legacySongID": 2607,
"searchTerms": [],
"title": "HEROES"
},
{
"altTitles": [],
"artist": "cosMo@暴走P & L.E.D.-G",
"data": {
"displayVersion": "33",
"genre": "Hi-ROM COM"
},
"id": "S19f39a108de64e81754",
"legacySongID": 2606,
"searchTerms": [
"rom com sometime serious"
],
"title": "ラブコメトキドキシリアス"
},
{
"altTitles": [],
"artist": "red glasses vs Ujico*",
"data": {
"displayVersion": "33",
"genre": "FM JAZZY TECH"
},
"id": "S19f39a108de8cd7ed96",
"legacySongID": 2609,
"searchTerms": [],
"title": "BitRacer"
},
{
"altTitles": [],
"artist": "PHQUASE vs. dj TAKA",
"data": {
"displayVersion": "33",
"genre": "DOUBLE DRIVE"
},
"id": "S19f39a108de914e528b",
"legacySongID": 2612,
"searchTerms": [],
"title": "fixer"
},
{
"altTitles": [],
"artist": "BEMANI Sound Team \"DJ TOTTO\"",
"data": {
"displayVersion": "33",
"genre": "FUTURISTIC 12 BEAT"
},
"id": "S19f39a108dea0e3e8c6",
"legacySongID": 2610,
"searchTerms": [],
"title": "chaplet -IIDX re:build-"
},
{
"altTitles": [],
"artist": "ネオン(CV:藤川茜)、芽兎めう(CV:五十嵐裕美)",
"data": {
"displayVersion": "33",
"genre": "PROJECT ZIRCON"
},
"id": "S19f39a108deb48d1cff",
"legacySongID": 2611,
"searchTerms": [],
"title": "Signs and Wonders"
},
{
"altTitles": [],
"artist": "TAN1CHU",
"data": {
"displayVersion": "33",
"genre": "EMOTIONAL FUTURE RIDDIM"
},
"id": "S19f39a108def65b88a2",
"legacySongID": 2608,
"searchTerms": [],
"title": "Nemophila"
},
{
"altTitles": [],
"artist": "Nhato",
"data": {
"displayVersion": "33",
"genre": "CELTIC CORE"
},
"id": "S19f39a108df108312d9",
"legacySongID": 2622,
"searchTerms": [],
"title": "Dusk Endir"
},
{
"altTitles": [],
"artist": "TANUKI",
"data": {
"displayVersion": "33",
"genre": "YALDICORE"
},
"id": "S19f39a108df15f44c71",
"legacySongID": 2640,
"searchTerms": [],
"title": "HyperTwist"
},
{
"altTitles": [],
"artist": "葛城ユイ feat. Command (Eurobeat Union)",
"data": {
"displayVersion": "33",
"genre": "EUROBEAT"
},
"id": "S19f39a108df188d1e32",
"legacySongID": 2621,
"searchTerms": [],
"title": "UNDO THE NIGHT"
},
{
"altTitles": [],
"artist": "DJ YOSHITAKA",
"data": {
"displayVersion": "33",
"genre": "HARD RENAISSANCE"
},
"id": "S19f39a108df275b58cd",
"legacySongID": 2628,
"searchTerms": [],
"title": "Lisa-RICCIA"
},
{
"altTitles": [],
"artist": "Plantica",
"data": {
"displayVersion": "33",
"genre": "POP"
},
"id": "S19f39a108df2deff094",
"legacySongID": 2614,
"searchTerms": [],
"title": "Bloom"
},
{
"altTitles": [],
"artist": "Ice vs BEMANI Sound Team \"dj TAKA\"",
"data": {
"displayVersion": "33",
"genre": "ROMANTIC SCHOOL VARIATIONS"
},
"id": "S19f39a108df2f57065f",
"legacySongID": 2616,
"searchTerms": [
"Flying beyond reason"
],
"title": "⁽⁽ଘ( ˙꒳˙ )ଓ⁾⁾ beyond reason"
},
{
"altTitles": [],
"artist": "OSTER project",
"data": {
"displayVersion": "33",
"genre": "BOLLYWOOD CORE"
},
"id": "S19f39a108df39333a05",
"legacySongID": 2625,
"searchTerms": [
"Bengal vs. Amur Battle"
],
"title": "ベンガル vs. アムール Battle"
},
{
"altTitles": [],
"artist": "Zekk",
"data": {
"displayVersion": "33",
"genre": "SUPER ULTRA MIRACLE HAPPY HARDCORE"
},
"id": "S19f39a108df434e13be",
"legacySongID": 2624,
"searchTerms": [],
"title": "PERFECT GREAT!!"
},
{
"altTitles": [],
"artist": "crayvxn",
"data": {
"displayVersion": "33",
"genre": "HYPERTRANCE"
},
"id": "S19f39a108df448fde00",
"legacySongID": 2618,
"searchTerms": [],
"title": "voice of echo."
},
{
"altTitles": [],
"artist": "kors k",
"data": {
"displayVersion": "33",
"genre": "CHANNEL K"
},
"id": "S19f39a108df4820c03e",
"legacySongID": 2617,
"searchTerms": [],
"title": "kors k's Let's make an Image Song!"
},
{
"altTitles": [],
"artist": "かめりあ feat. かめりあ",
"data": {
"displayVersion": "33",
"genre": "GREY DISTRICT"
},
"id": "S19f39a108df53500318",
"legacySongID": 2639,
"searchTerms": [
"irowoushinattamachi"
],
"title": "色を喪った街"
},
{
"altTitles": [],
"artist": "めめめ",
"data": {
"displayVersion": "33",
"genre": "超炭酸すぱしゃわコア"
},
"id": "S19f39a108df5c783cfa",
"legacySongID": 2615,
"searchTerms": [
"fizzy potion"
],
"title": "FiZZλ_PØT!0И"
},
{
"altTitles": [],
"artist": "Whac-A-Me",
"data": {
"displayVersion": "33",
"genre": "PSY-TRANCE"
},
"id": "S19f39a108df6043f179",
"legacySongID": 2633,
"searchTerms": [],
"title": "Begin"
},
{
"altTitles": [],
"artist": "Dirty Androids",
"data": {
"displayVersion": "33",
"genre": "UPTEMPO ELECTRO FUNK"
},
"id": "S19f39a108df60cf3e1b",
"legacySongID": 2623,
"searchTerms": [],
"title": "Funky Quirky"
},
{
"altTitles": [],
"artist": "Remixed by Pizuya's Cell",
"data": {
"displayVersion": "33",
"genre": "IIDX EDITION"
},
"id": "S19f39a108df6128f60d",
"legacySongID": 2635,
"searchTerms": [
"Override"
],
"title": "オーバーライド"
},
{
"altTitles": [],
"artist": "中山真斗 feat. CHAN",
"data": {
"displayVersion": "33",
"genre": "ALTERNATIVE"
},
"id": "S19f39a108df67291279",
"legacySongID": 2627,
"searchTerms": [
"Haochi Raise de Electric"
],
"title": "好吃来世deエレクトリック"
},
{
"altTitles": [],
"artist": "MYUKKE.",
"data": {
"displayVersion": "33",
"genre": "ORIENTAL MIRAGE"
},
"id": "S19f39a108df69191b6d",
"legacySongID": 2638,
"searchTerms": [],
"title": "TOKAKU=ALMiRAJ"
},
{
"altTitles": [],
"artist": "YUC'e",
"data": {
"displayVersion": "33",
"genre": "EXPLO POP"
},
"id": "S19f39a108df87e08b46",
"legacySongID": 2636,
"searchTerms": [],
"title": "Watermelon Explosion"
},
{
"altTitles": [],
"artist": "kors k",
"data": {
"displayVersion": "33",
"genre": "BEBOP CORE"
},
"id": "S19f39a108df8feae8ba",
"legacySongID": 2641,
"searchTerms": [],
"title": "Last Card"
},
{
"altTitles": [],
"artist": "Qlarabelle",
"data": {
"displayVersion": "33",
"genre": "SYMPHONIC HARD TECHNO"
},
"id": "S19f39a108dfa28cd3f6",
"legacySongID": 2631,
"searchTerms": [],
"title": "OMEGA"
},
{
"altTitles": [],
"artist": "Tom-H@ck feat.Nadia",
"data": {
"displayVersion": "33",
"genre": "DIGITAL ROCK"
},
"id": "S19f39a108dfa8a0d32d",
"legacySongID": 2629,
"searchTerms": [],
"title": "X-Plan"
},
{
"altTitles": [],
"artist": "Blacklolita",
"data": {
"displayVersion": "33",
"genre": "DUBSTEP"
},
"id": "S19f39a108dfaed47950",
"legacySongID": 2626,
"searchTerms": [],
"title": "BattleRoyal"
},
{
"altTitles": [],
"artist": "Remixed by BEMANI Sound Team \"めめめ\" feat. beatnation Records",
"data": {
"displayVersion": "33",
"genre": "RECKLESS RAVE"
},
"id": "S19f39a108dfb4a5a838",
"legacySongID": 2632,
"searchTerms": [],
"title": "GOLD RUSH 2025"
},
{
"altTitles": [],
"artist": "Virtuoso",
"data": {
"displayVersion": "33",
"genre": "CLASSICAL"
},
"id": "S19f39a108dfc549bd05",
"legacySongID": 2613,
"searchTerms": [
"Crescendo"
],
"title": "クレッシェンド"
},
{
"altTitles": [],
"artist": "ピノキオピー",
"data": {
"displayVersion": "33",
"genre": "ボカロ"
},
"id": "S19f39a108dfc7d3d4f1",
"legacySongID": 2634,
"searchTerms": [
"Playing Immortal"
],
"title": "不死身ごっこ"
},
{
"altTitles": [],
"artist": "KO3 & Relect",
"data": {
"displayVersion": "33",
"genre": "PSYCHEDELIC TRANCE"
},
"id": "S19f39a108dfd1d5370e",
"legacySongID": 2620,
"searchTerms": [],
"title": "Hallucination"
},
{
"altTitles": [],
"artist": "猫叉Master feat.常盤ゆう",
"data": {
"displayVersion": "33",
"genre": "フルフィーポップ"
},
"id": "S19f39a108dfd2a0b711",
"legacySongID": 2637,
"searchTerms": [],
"title": "REcorrection"
},
{
"altTitles": [],
"artist": "MK&Kanae Asaba",
"data": {
"displayVersion": "33",
"genre": "HYBRID JERSY CLUB"
},
"id": "S19f39a108dfd73ece7b",
"legacySongID": 2619,
"searchTerms": [],
"title": "Show Time"
},
{
"altTitles": [],
"artist": "BlackY",
"data": {
"displayVersion": "33",
"genre": "PIANO DANCE APPASSIONATO"
},
"id": "S19f39a108dff00b9fac",
"legacySongID": 2630,
"searchTerms": [
"Amor Fati"
],
"title": "Amor∞Fati"
},
{
"altTitles": [],
"artist": "音戯探偵ひなビタ♫",
"data": {
"displayVersion": "33",
"genre": "ガールズティーパーティー"
},
"id": "S19f39a108e0053a92f8",
"legacySongID": 2644,
"searchTerms": [
"Recipe Riddle"
],
"title": "レシピのリドル"
},
{
"altTitles": [],
"artist": "TAN1CHU",
"data": {
"displayVersion": "33",
"genre": "HAM PARADOX"
},
"id": "S19f39a108e008a706f0",
"legacySongID": 2652,
"searchTerms": [],
"title": "Smintheus"
},
{
"altTitles": [],
"artist": "Ryu☆ Remixed by Mamonis",
"data": {
"displayVersion": "33",
"genre": "HYPER BUCHIAGE TRANCE"
},
"id": "S19f39a108e00ba6e381",
"legacySongID": 2648,
"searchTerms": [],
"title": "Second Heaven(Mamonis Remix)"
},
{
"altTitles": [],
"artist": "音戯探偵ひなビタ♫",
"data": {
"displayVersion": "33",
"genre": "オトギタンテイ"
},
"id": "S19f39a108e0364c8ab5",
"legacySongID": 2643,
"searchTerms": [
"The Great Otogitantei-Hinabita"
],
"title": "華麗なる!音戯探偵ひなビタ♫"
},
{
"altTitles": [],
"artist": "Dustup VS MAX MAXIMIZER",
"data": {
"displayVersion": "33",
"genre": "HARD PSY/SPEEDCORE"
},
"id": "S19f39a108e0399f8734",
"legacySongID": 2642,
"searchTerms": [],
"title": "RUINA"
},
{
"altTitles": [],
"artist": "kors k Remixed by 青龍",
"data": {
"displayVersion": "33",
"genre": "HYPER DANCE SPEED"
},
"id": "S19f39a108e039b37ee4",
"legacySongID": 2647,
"searchTerms": [
"Gimme a Big Beat Seiryu Remix"
],
"title": "Gimme a Big Beat(青龍 Remix)"
},
{
"altTitles": [],
"artist": "秀よし君",
"data": {
"displayVersion": "33",
"genre": "太閤ヒップホップ"
},
"id": "S19f39a108e04270852e",
"legacySongID": 2657,
"searchTerms": [
"mikkadenkamonkey"
],
"title": "三日天下モンキー"
},
{
"altTitles": [],
"artist": "星野音楽工房",
"data": {
"displayVersion": "33",
"genre": "MELODIOUS TECH"
},
"id": "S19f39a108e047009a54",
"legacySongID": 2646,
"searchTerms": [],
"title": "pique"
},
{
"altTitles": [],
"artist": "The 4th feat.TORIENA",
"data": {
"displayVersion": "33",
"genre": "HYPER CANDY RAVE"
},
"id": "S19f39a108e05733bbe9",
"legacySongID": 2649,
"searchTerms": [],
"title": "Kick The Rainbow"
},
{
"altTitles": [],
"artist": "NU-KO",
"data": {
"displayVersion": "33",
"genre": "HAPPY LOVEGAZER"
},
"id": "S19f39a108e07e2c5c2c",
"legacySongID": 2650,
"searchTerms": [
"RENAI KANSOKU"
],
"title": "恋愛観測"
},
{
"altTitles": [],
"artist": "dj MAX STEROID",
"data": {
"displayVersion": "33",
"genre": "NU SKOOL BREAKS"
},
"id": "S19f39a108e099aa9c0b",
"legacySongID": 2656,
"searchTerms": [
"kakkoii"
],
"title": "[E]"
},
{
"altTitles": [],
"artist": "Nhato",
"data": {
"displayVersion": "33",
"genre": "TRANCECORE"
},
"id": "S19f39a108e0a308aecc",
"legacySongID": 2645,
"searchTerms": [],
"title": "Skywalking"
},
{
"altTitles": [],
"artist": "BEMANI Sound Team \"akYm\" feat.maimie",
"data": {
"displayVersion": "33",
"genre": "POP ROCK"
},
"id": "S19f39a108e0ae3e2682",
"legacySongID": 2655,
"searchTerms": [
"kimitoegakukiseki"
],
"title": "キミとえがくキセキ"
},
{
"altTitles": [],
"artist": "BEMANI Sound Team \"HuΣeR\" feat.ゆきまめ",
"data": {
"displayVersion": "33",
"genre": "幻想音樂"
},
"id": "S19f39a108e0b9009b03",
"legacySongID": 2651,
"searchTerms": [
"Telos"
],
"title": "Τέλος"
},
{
"altTitles": [],
"artist": "KE!JU",
"data": {
"displayVersion": "33",
"genre": "JUDGEMENT SYMPHONIC SPEEDCORE"
},
"id": "S19f39a108e0c78848df",
"legacySongID": 2653,
"searchTerms": [
"judgement mimesis"
],
"title": "断罪のミメシス"
},
{
"altTitles": [],
"artist": "めめめ",
"data": {
"displayVersion": "33",
"genre": "DTM SPEEDRUN"
},
"id": "S19f39a108e0e5096529",
"legacySongID": 2654,
"searchTerms": [
"Any Percent"
],
"title": "Any%"
}
]
@@ -214,7 +214,11 @@ export async function ParseIIDXData(
const notecount = chart.notecount;
if (chart.difficulty === "LEGGENDARIA") {
if (notecount !== 0 && notecounts[`${chart.playtype}-LEGGENDARIA`]) {
if (
notecount !== 0 &&
notecounts[`${chart.playtype}-LEGGENDARIA`] &&
notecount !== notecounts[`${chart.playtype}-LEGGENDARIA`]
) {
log.warn(
`${chart.artist} - ${chart.title} has conflicting ${
chart.playtype
@@ -3,23 +3,30 @@ import { Command } from "commander";
import fs from "fs";
import path from "path";
import {
type ChartDocument,
type SEEDS_ChartDocument,
type Difficulties,
type GamesForGroup,
type integer,
LEGACY_GetGamePTConfig,
type SongDocument,
GetGameConfig,
type SEEDS_SongDocument,
type Versions,
} from "tachi-common";
import {
CreateChartID,
CreateSongID,
GetFreshSongIDGenerator,
ReadCollection,
WriteCollection,
} from "../../../util";
import { ParseIIDXData } from "./convert";
function randomHex(bytes: any) {
const buf = new Uint8Array(bytes);
globalThis.crypto.getRandomValues(buf);
return Array.from(buf, (b) => b.toString(16).padStart(2, "0")).join("");
}
if (require.main !== module) {
throw new Error(`This is a script. It should be ran directly from the command line with bun.`);
}
@@ -46,7 +53,7 @@ const options = program.opts() as {
version: Versions["iidx-sp"];
};
const iidxConfig = LEGACY_GetGamePTConfig("iidx", "SP");
const iidxConfig = GetGameConfig("iidx-sp");
if (!Object.keys(iidxConfig.versions).includes(options.version)) {
throw new Error(
@@ -62,9 +69,10 @@ if (options.index !== "0" && options.index !== "1") {
throw new Error(`Expected an --index of 0 or 1. Got ${options.index}.`);
}
const existingCharts: ChartDocument<GamesForGroup["iidx"]>[] = ReadCollection("charts-iidx.json");
const existingChartsSP: SEEDS_ChartDocument<"iidx-sp">[] = ReadCollection("charts-iidx-sp.json");
const existingChartsDP: SEEDS_ChartDocument<"iidx-dp">[] = ReadCollection("charts-iidx-dp.json");
const existingSongs: SongDocument<"iidx">[] = ReadCollection("songs-iidx.json");
const existingSongs: SEEDS_SongDocument<"iidx">[] = ReadCollection("songs-iidx.json");
const blacklist = fs
.readFileSync(path.join(__dirname, "blacklist.txt"), "utf-8")
@@ -99,17 +107,17 @@ async function ParseIIDXMDB() {
options.alwaysExtract,
);
const chartMap = new Map<integer, ChartDocument<GamesForGroup["iidx"]>>();
const songMap = new Map<integer, SongDocument<"iidx">>();
const songTitleMap = new Map<string, SongDocument<"iidx">>();
const chartDiffMap = new Map<string, ChartDocument<GamesForGroup["iidx"]>>();
const chartMap = new Map<integer, SEEDS_ChartDocument<GamesForGroup["iidx"]>>();
const songMap = new Map<string, SEEDS_SongDocument<"iidx">>();
const songTitleMap = new Map<string, SEEDS_SongDocument<"iidx">>();
const chartDiffMap = new Map<string, SEEDS_ChartDocument<GamesForGroup["iidx"]>>();
for (const song of existingSongs) {
songMap.set(song.id, song);
songTitleMap.set(song.title, song);
}
for (const chart of existingCharts) {
for (const chart of [...existingChartsSP, ...existingChartsDP]) {
// what, you thought this was easy?
if (Array.isArray(chart.data.inGameID)) {
for (const igid of chart.data.inGameID) {
@@ -120,17 +128,27 @@ async function ParseIIDXMDB() {
}
}
for (const chart of existingCharts) {
for (const chart of existingChartsSP) {
if (Array.isArray(chart.data.inGameID)) {
for (const igid of chart.data.inGameID) {
chartDiffMap.set(`${igid}-${chart.playtype}-${chart.difficulty}`, chart);
chartDiffMap.set(`${igid}-SP-${chart.difficulty}`, chart);
}
} else {
chartDiffMap.set(`${chart.data.inGameID}-${chart.playtype}-${chart.difficulty}`, chart);
chartDiffMap.set(`${chart.data.inGameID}-SP-${chart.difficulty}`, chart);
}
}
const getFreeSongID = GetFreshSongIDGenerator("iidx");
for (const chart of existingChartsDP) {
if (Array.isArray(chart.data.inGameID)) {
for (const igid of chart.data.inGameID) {
chartDiffMap.set(`${igid}-DP-${chart.difficulty}`, chart);
}
} else {
chartDiffMap.set(`${chart.data.inGameID}-DP-${chart.difficulty}`, chart);
}
}
const getFreeLegacySongID = GetFreshSongIDGenerator("iidx");
for (const inp of mdbCharts) {
if (isInBlacklist(`S${inp.songID}`)) {
@@ -141,7 +159,7 @@ async function ParseIIDXMDB() {
}
const anySongIDMatch = chartMap.get(inp.songID);
let song: SongDocument<"iidx">;
let song: SEEDS_SongDocument<"iidx">;
if (!anySongIDMatch) {
// new song?
@@ -168,8 +186,9 @@ async function ParseIIDXMDB() {
searchTerms.push(inp.marquee);
}
const tachiSong: SongDocument<"iidx"> = {
id: getFreeSongID(),
const tachiSong: SEEDS_SongDocument<"iidx"> = {
id: CreateSongID(),
legacySongID: getFreeLegacySongID(),
artist: inp.artist,
title: inp.title,
data: {
@@ -241,15 +260,13 @@ async function ParseIIDXMDB() {
// otherwise, make new chart?
const playtypeStr = diffName.split("-")[0] as "DP" | "SP";
const game: GamesForGroup["iidx"] = playtypeStr === "SP" ? "iidx-sp" : "iidx-dp";
const tachiChart: ChartDocument<GamesForGroup["iidx"]> = {
game,
chartID: CreateChartID(),
const tachiChart: SEEDS_ChartDocument<GamesForGroup["iidx"]> = {
id: CreateChartID(),
legacyChartID: randomHex(20),
difficulty: diffName.split("-")[1] as Difficulties[GamesForGroup["iidx"]],
level: level.toString(),
levelNum: level,
isPrimary: true,
playtype: playtypeStr,
songID: song.id,
versions: [options.version],
@@ -265,7 +282,11 @@ async function ParseIIDXMDB() {
};
log.info(`Inserting new chart ${inp.title} ${diffName}.`);
existingCharts.push(tachiChart);
if (playtypeStr === "SP") {
existingChartsSP.push(tachiChart);
} else {
existingChartsDP.push(tachiChart);
}
} else {
if (!notecount) {
log.warn(
@@ -304,7 +325,8 @@ async function ParseIIDXMDB() {
}
WriteCollection("songs-iidx.json", existingSongs);
WriteCollection("charts-iidx.json", existingCharts);
WriteCollection("charts-iidx-sp.json", existingChartsSP);
WriteCollection("charts-iidx-dp.json", existingChartsDP);
}
ParseIIDXMDB();
@@ -0,0 +1,366 @@
import { log } from "#log";
import * as cheerio from "cheerio";
import { type AnyNode } from "domhandler";
import { type SEEDS_ChartDocument, type SEEDS_SongDocument } from "tachi-common";
import { ReadCollection, WriteCollection } from "../../util";
if (require.main !== module) {
throw new Error(`This is a script. It should be ran directly from the command line with bun.`);
}
const URL_L12_BASE =
"https://docs.google.com/spreadsheets/u/0/d/e/2PACX-1vSUdp6iuEzE8Z5AL1hkoxzLexp89nJnLQMmICm6_MC0_UjCp1ImZFzabcZkvCpK7mcWvm_2t6iYoJRg/pubhtml/sheet?headers=false&gid=";
const URL_L11_BASE =
"https://docs.google.com/spreadsheets/u/0/d/e/2PACX-1vSLnjRLdYQ53QgdmKoCvW9zVSgHjBCcGf8iTeEgjiSia4i_4YwR9isSfhxC2kSAq_wWygd_9xGORhk0/pubhtml/sheet?headers=false&gid=";
const TIER_CONFIGS = [
{
url: URL_L12_BASE + "1184656976",
level: 12,
gauge: "normal",
format: "columnwise",
},
{
url: URL_L12_BASE + "1277599511",
level: 12,
gauge: "hard",
format: "columnwise",
},
// The 11 sheet is out of date - trying to find a better one.
// {
// url: URL_L11_BASE + "411493762",
// level: 11,
// gauge: "normal",
// format: "rowwise",
// },
// {
// url: URL_L11_BASE + "1585306050",
// level: 11,
// gauge: "hard",
// format: "rowwise",
// },
];
type songInTier = {
title: string;
difficulty: string;
kojinsa: boolean;
};
const KOJINSA_CLASS = "s5";
function editDistance(a: string, b: string) {
const matrix = [];
for (let i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
for (let j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
for (let i = 1; i <= b.length; i++) {
for (let j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1,
matrix[i][j - 1] + 1,
matrix[i - 1][j] + 1,
);
}
}
}
return matrix[b.length][a.length];
}
function parseTitleAndDiff(text: string) {
// L12 sheet uses [L]/[H]; L11 sheet uses (L)/(H).
if (text.endsWith("[L]") || text.endsWith("(L)")) {
return { title: text.slice(0, -3).trim(), difficulty: "LEGGENDARIA" };
}
if (text.endsWith("[H]") || text.endsWith("(H)")) {
return { title: text.slice(0, -3).trim(), difficulty: "HYPER" };
}
return { title: text.trim(), difficulty: "ANOTHER" };
}
// NFKC folds fullwidth ASCII/digits to halfwidth ( -> 1, -> +); the regex
// passes then collapse curly quotes which NFKC leaves as stylistic variants.
function normalize(s: string) {
return s.normalize("NFKC").replace(/[“”]/g, '"').replace(/[‘’]/g, "'");
}
function findMatchingSong(title: string, songsData: SEEDS_SongDocument<"iidx">[]) {
const waveDashed = title.replaceAll("", "〜");
const normalizedClean = normalize(waveDashed);
for (const song of songsData) {
if (normalize(song.title) === normalizedClean) {
return song;
}
}
const lowerTitle = normalizedClean.toLowerCase();
let bestMatch = null;
let bestDistance = Infinity;
const maxDistance = 4;
for (const song of songsData) {
const distance = editDistance(lowerTitle, normalize(song.title).toLowerCase().trim());
if (distance <= maxDistance && distance < bestDistance) {
bestDistance = distance;
bestMatch = song;
}
}
if (bestMatch === null) {
return null;
}
if (bestDistance > 0) {
log.info(`Fuzzy match: "${title}" -> "${bestMatch.title}" (distance: ${bestDistance})`);
}
return bestMatch;
}
function isRedSpan(node: AnyNode) {
if (node.type !== "tag" || node.name !== "span") {
return false;
}
const style = node.attribs?.style || "";
return /color\s*:\s*#ff0000/i.test(style);
}
// Walks a <td>, returning [{title, difficulty, kojinsa}] split on <br>.
// Any text encountered inside a red <span> marks its segment kojinsa=true.
function parseCell(cellEl: AnyNode) {
const titles: songInTier[] = [];
let buffer = "";
let bufferIsRed = false;
function flush() {
const t = buffer.trim();
if (t.length > 0) {
const { title, difficulty } = parseTitleAndDiff(t);
titles.push({ title: title, difficulty: difficulty, kojinsa: bufferIsRed });
}
buffer = "";
bufferIsRed = false;
}
function walk(nodes: AnyNode[], redContext: boolean) {
for (const node of nodes) {
if (node.type === "text") {
buffer += node.data;
if (redContext) {
bufferIsRed = true;
}
continue;
}
if (node.type !== "tag") {
continue;
}
if (node.name === "br") {
flush();
continue;
}
if (isRedSpan(node)) {
walk(node.children, true);
continue;
}
walk(node.children, redContext);
}
}
if (cellEl.type === "tag") {
walk(cellEl.children, false);
}
flush();
return titles;
}
async function fetchSheet(url: string) {
log.info(`Fetching ${url}`);
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status} fetching ${url}`);
}
return res.text();
}
function cleanTierName(text: string) {
return text.trim().replaceAll("", "+");
}
// L12 layout: thead shim, then one tier-header row + one data row in tbody.
// Each data <td> holds <br>-separated titles; red <span>s mark 個人差.
function extractTiersColumnwise(html: string) {
const $ = cheerio.load(html);
const rows = $("tr").toArray();
if (rows.length < 3) {
throw new Error(`Expected at least 3 rows, got ${rows.length}`);
}
const headerCells = $(rows[1]).find("td").toArray();
const tierCells = $(rows[2]).find("td").toArray();
const colCount = Math.min(headerCells.length, tierCells.length);
if (headerCells.length !== tierCells.length) {
log.info(
`Warning: header has ${headerCells.length} cols, data has ${tierCells.length} cols`,
);
}
const tiers = [];
for (let k = 0; k < colCount; k++) {
const tierName = cleanTierName($(headerCells[k]).text());
const songsInTier = parseCell(tierCells[k]);
tiers.push({ tier: tierName, songsInTier });
}
return tiers;
}
// L11 layout: thead shim, then in tbody: title row, tier-header row, then one
// song per cell per row. Empty cell = tier ran out of songs. 個人差 cells
// carry class "s5" on the <td>.
function extractTiersRowwise(html: string) {
const $ = cheerio.load(html);
const rows = $("tr").toArray();
if (rows.length < 4) {
throw new Error(`Expected at least 4 rows, got ${rows.length}`);
}
const headerCells = $(rows[2]).find("td").toArray();
const tiers = headerCells.map((c) => ({
tier: cleanTierName($(c).text()),
songsInTier: [] as songInTier[],
}));
for (let k = 3; k < rows.length; k++) {
const cells = $(rows[k]).find("td").toArray();
const colCount = Math.min(tiers.length, cells.length);
for (let col = 0; col < colCount; col++) {
const cell = $(cells[col]);
const text = cell.text().trim();
if (text.length === 0) {
continue;
}
const cls = cell.attr("class") || "";
const kojinsa = cls.split(/\s+/).includes(KOJINSA_CLASS);
const { title, difficulty } = parseTitleAndDiff(text);
tiers[col].songsInTier.push({ title: title, difficulty: difficulty, kojinsa: kojinsa });
}
}
return tiers;
}
function applyTiers(
chartsData: SEEDS_ChartDocument<"iidx-sp">[],
songsData: SEEDS_SongDocument<"iidx">[],
level: number,
tiers: {
tier: string;
songsInTier: songInTier[];
}[],
field: "hcTier" | "ncTier",
) {
let updated = 0;
let notFound = 0;
function tierToValue(tier: string) {
switch (tier) {
case "S+":
return level + 1.2;
case "S":
return level + 1.0;
case "A+":
return level + 0.9;
case "A":
return level + 0.8;
case "B+":
return level + 0.7;
case "B":
return level + 0.6;
case "C":
return level + 0.4;
case "D":
return level + 0.2;
case "E":
return level + 0.0;
case "F":
return level - 0.2;
default:
return level;
}
}
function isInAc(versions: string[]) {
for (const version of ["33", "32", "31", "30"]) {
if (versions.includes(version)) return true;
}
return false;
}
for (const { tier, songsInTier } of tiers) {
if (!tier || tier === "未定" || tier === "超個人差") {
continue;
}
for (const { title, difficulty, kojinsa } of songsInTier) {
const songMatch = findMatchingSong(title, songsData);
if (!songMatch) {
log.warn(`No match: ${title}`);
notFound++;
continue;
}
let chartFound = false;
for (const chart of chartsData) {
if (
chart.songID === songMatch.id &&
chart.difficulty === difficulty &&
chart.data["2dxtraSet"] === null &&
isInAc(chart.versions)
) {
chart.data[field] = {
individualDifference: kojinsa,
text: level + tier,
value: tierToValue(tier),
};
updated++;
chartFound = true;
break;
}
}
if (!chartFound) {
log.warn(`Chart not found: ${songMatch.title} (SP ${difficulty})`);
notFound++;
continue;
}
}
}
return { updated, notFound };
}
async function main() {
const chartsData = ReadCollection("charts-iidx-sp.json");
const songsData = ReadCollection("songs-iidx.json");
for (const config of TIER_CONFIGS) {
log.info(`Processing L${config.level} ${config.gauge}`);
const html = await fetchSheet(config.url);
const tiers =
config.format === "rowwise" ? extractTiersRowwise(html) : extractTiersColumnwise(html);
log.info(`Parsed ${tiers.length} tiers: ${tiers.map((t) => t.tier).join(", ")}`);
const { updated, notFound } = applyTiers(
chartsData,
songsData,
config.level,
tiers,
config.gauge === "normal" ? "ncTier" : "hcTier",
);
log.info(`L${config.level} ${config.gauge}: ${updated} updated, ${notFound} not found`);
}
WriteCollection("charts-iidx-sp.json", chartsData);
}
main().catch((err) => {
console.error(`ERROR: ${err.message}`);
process.exit(1);
});
@@ -26,4 +26,13 @@ export const EXT_PINKYCRUSH = [
"2025082500",
];
export const EXT_SPARKLE_SHOWER = [
"2026022400",
"2026031000",
"2026033100",
"2026042200",
"2026052600",
"2026061600",
];
export const MODEL_SDVX3_KONASTE = "QCV";
@@ -11,6 +11,7 @@ import {
EXT_PINKYCRUSH,
EXT_RESIDENT,
EXT_ROOTAGE,
EXT_SPARKLE_SHOWER,
MODEL_IIDX,
MODEL_IIDX_LIGHTNING,
MODEL_INFINITAS_2,
@@ -157,6 +158,10 @@ export function SoftwareIDToVersion(model: string, log: KtLogger): Versions[Game
} else if (data.rev === REV_OMNIMIX) {
return "32-omni";
}
} else if (EXT_SPARKLE_SHOWER.includes(data.ext)) {
if (data.rev === REV_NORMAL) {
return "33";
}
}
}