data: add Watcha Gatcha charts from official master
Publish GitHub Pages output / publish (push) Successful in 36s
Publish GitHub Pages output / publish (push) Successful in 36s
This commit is contained in:
@@ -11,6 +11,7 @@ data/*
|
||||
!data/.gitkeep
|
||||
!data/card-overrides.csv
|
||||
!data/score-calibrations.json
|
||||
!data/song-metadata-overrides.json
|
||||
!data/holodoridb/
|
||||
!data/holodoridb/**
|
||||
data/holodoridb/*.tmp
|
||||
|
||||
+7
-1
@@ -1,7 +1,13 @@
|
||||
---
|
||||
updatedAt: 2026-09-25T14:08:52Z
|
||||
updatedAt: 2026-09-26T12:00:00Z
|
||||
---
|
||||
|
||||
## 2026년 9월 26일
|
||||
|
||||
### Watcha Gatcha!!!!!!!! 채보 추가
|
||||
|
||||
`NEW` 「Watcha Gatcha!!!!!!!!」의 Easy·Normal·Hard·Expert 채보를 추가했습니다. 게임 내 확인 난이도는 6·13·22·27이며 곡 길이는 122초입니다. 노트와 스페셜 발동 시각은 원본 채보를 사용합니다. 곡별 점수 계수와 가창 멤버 정보를 반영하여 점수 공식과 가창 멤버 보너스를 적용합니다.
|
||||
|
||||
## 2026년 9월 25일
|
||||
|
||||
### 배치 분석 빈도 조합 비교
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"generatedAt": "2026-09-19T01:15:22.929Z",
|
||||
"generatedAt": "2026-09-26T21:34:40.634Z",
|
||||
"sources": {
|
||||
"eng": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-eng-diff",
|
||||
"commit": "f7428d4092d57b34311f030e00ff46e7dbbc29e8",
|
||||
"commitDate": "2026-09-18T10:50:49Z",
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9"
|
||||
"commit": "7f69b2404abe3001ce411240ed2323a6313a2708",
|
||||
"commitDate": "2026-09-26T16:03:09Z",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd"
|
||||
},
|
||||
"kor": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-kor-diff",
|
||||
"commit": "5517e40b931fec10253f5b850989346518e233c2",
|
||||
"commitDate": "2026-09-18T13:17:53Z",
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9"
|
||||
"commit": "98f8b557a095c837b51126d993c34d582fb5c5f2",
|
||||
"commitDate": "2026-09-26T13:30:40Z",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd"
|
||||
},
|
||||
"jpn": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-jpn-diff",
|
||||
"commit": "a22c8e29a981a8987bf2074c245e2fc40ad4dbea",
|
||||
"commitDate": "2026-09-18T11:03:55Z",
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9"
|
||||
"commit": "e43f062c32ff4e04567235efcd58448cd6b10f35",
|
||||
"commitDate": "2026-09-26T16:18:53Z",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd"
|
||||
}
|
||||
},
|
||||
"tables": {
|
||||
|
||||
+10
-10
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"generatedAt": "2026-09-19T01:15:22.929Z",
|
||||
"generatedAt": "2026-09-26T21:34:40.634Z",
|
||||
"sources": {
|
||||
"eng": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-eng-diff",
|
||||
"commit": "f7428d4092d57b34311f030e00ff46e7dbbc29e8",
|
||||
"commitDate": "2026-09-18T10:50:49Z",
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9"
|
||||
"commit": "7f69b2404abe3001ce411240ed2323a6313a2708",
|
||||
"commitDate": "2026-09-26T16:03:09Z",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd"
|
||||
},
|
||||
"kor": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-kor-diff",
|
||||
"commit": "5517e40b931fec10253f5b850989346518e233c2",
|
||||
"commitDate": "2026-09-18T13:17:53Z",
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9"
|
||||
"commit": "98f8b557a095c837b51126d993c34d582fb5c5f2",
|
||||
"commitDate": "2026-09-26T13:30:40Z",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd"
|
||||
},
|
||||
"jpn": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-jpn-diff",
|
||||
"commit": "a22c8e29a981a8987bf2074c245e2fc40ad4dbea",
|
||||
"commitDate": "2026-09-18T11:03:55Z",
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9"
|
||||
"commit": "e43f062c32ff4e04567235efcd58448cd6b10f35",
|
||||
"commitDate": "2026-09-26T16:18:53Z",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd"
|
||||
}
|
||||
},
|
||||
"cards": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"generatedAt": "2026-09-19T01:15:22.929Z",
|
||||
"generatedAt": "2026-09-26T21:34:40.634Z",
|
||||
"source": "HolodoriDB community master-data diff repositories",
|
||||
"sourceLicense": null,
|
||||
"sourceLicenseNote": "No LICENSE file was present in the three source repositories at collection time.",
|
||||
@@ -8,21 +8,21 @@
|
||||
"sources": {
|
||||
"eng": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-eng-diff",
|
||||
"commit": "f7428d4092d57b34311f030e00ff46e7dbbc29e8",
|
||||
"commitDate": "2026-09-18T10:50:49Z",
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9"
|
||||
"commit": "7f69b2404abe3001ce411240ed2323a6313a2708",
|
||||
"commitDate": "2026-09-26T16:03:09Z",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd"
|
||||
},
|
||||
"kor": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-kor-diff",
|
||||
"commit": "5517e40b931fec10253f5b850989346518e233c2",
|
||||
"commitDate": "2026-09-18T13:17:53Z",
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9"
|
||||
"commit": "98f8b557a095c837b51126d993c34d582fb5c5f2",
|
||||
"commitDate": "2026-09-26T13:30:40Z",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd"
|
||||
},
|
||||
"jpn": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-jpn-diff",
|
||||
"commit": "a22c8e29a981a8987bf2074c245e2fc40ad4dbea",
|
||||
"commitDate": "2026-09-18T11:03:55Z",
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9"
|
||||
"commit": "e43f062c32ff4e04567235efcd58448cd6b10f35",
|
||||
"commitDate": "2026-09-26T16:18:53Z",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd"
|
||||
}
|
||||
},
|
||||
"counts": {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"songs": {
|
||||
"m0525": {
|
||||
"page": "https://holodori.best/songs/m0525",
|
||||
"acquisition": "User-provided song detail JSON",
|
||||
"file": "docs/research/m0525-song-detail.json",
|
||||
"sha256": "3d532b5e5fec73a145a04efd9d17d63d0ddf537b5dac015233a4778bf3cd5198"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"source": {
|
||||
"repository": "https://github.com/HolodoriDB/holodori-db-kor-diff.git",
|
||||
"commit": "98f8b557a095c837b51126d993c34d582fb5c5f2",
|
||||
"locale": "kor"
|
||||
},
|
||||
"music": {
|
||||
"id": "m0525",
|
||||
"titleLangId": "la-music_title-m0525",
|
||||
"titleRubyLangId": "la-music_title_ruby-m0525",
|
||||
"lyricistLangId": "la-music_lyricist-m0525",
|
||||
"lyricistRubyLangId": "la-music_lyricist_ruby-m0525",
|
||||
"composerLangId": "la-music_composer-m0525",
|
||||
"composerRubyLangId": "la-music_composer_ruby-m0525",
|
||||
"arrangerLangId": "la-music_arranger-m0525",
|
||||
"arrangerRubyLangId": "la-music_arranger_ruby-m0525",
|
||||
"jacketAssetId": "m0525",
|
||||
"startTime": "1790424000000",
|
||||
"playingSeconds": 122,
|
||||
"categoryType": "MusicCategoryType_MUSIC_CATEGORY_TYPE_ORIGINAL",
|
||||
"liveScoreCoefficientPermil": 5,
|
||||
"singleLiveScoreEvaluationRankGroupId": "live_score_rank-s001",
|
||||
"multiLiveScoreEvaluationRankGroupId": "live_score_rank-m001",
|
||||
"liveComboGroupId": "live_combo-1",
|
||||
"releaseType": "MusicReleaseType_MUSIC_RELEASE_TYPE_PRESET",
|
||||
"isHighestScoreRatingTarget": true,
|
||||
"mvUrl": "https://youtu.be/iSQYbf9XFg8",
|
||||
"mvThumbnailAssetId": "m0525-mv-01",
|
||||
"musicSingerType": "MusicSingerType_MUSIC_SINGER_TYPE_GROUP",
|
||||
"characterIds": [
|
||||
"chr-00030",
|
||||
"chr-00031",
|
||||
"chr-00032",
|
||||
"chr-00034",
|
||||
"chr-00035",
|
||||
"chr-00036",
|
||||
"chr-00037",
|
||||
"chr-00039"
|
||||
],
|
||||
"characterGroupDisplayNameLangId": "la-singer_name-m0525",
|
||||
"characterGroupDisplayNameRubyLangId": "la-singer_name_ruby-m0525",
|
||||
"chorusStartMillisecond": 68571,
|
||||
"chorusEndMillisecond": 98285,
|
||||
"assetId": "m0525",
|
||||
"order": 40000525
|
||||
},
|
||||
"difficulties": [
|
||||
{
|
||||
"musicId": "m0525",
|
||||
"difficultyType": "MusicDifficultyType_MUSIC_DIFFICULTY_TYPE_EASY",
|
||||
"difficultyLevel": 6,
|
||||
"chartAssetId": "chart_m0525_easy"
|
||||
},
|
||||
{
|
||||
"musicId": "m0525",
|
||||
"difficultyType": "MusicDifficultyType_MUSIC_DIFFICULTY_TYPE_EXPERT",
|
||||
"difficultyLevel": 27,
|
||||
"chartAssetId": "chart_m0525_expert"
|
||||
},
|
||||
{
|
||||
"musicId": "m0525",
|
||||
"difficultyType": "MusicDifficultyType_MUSIC_DIFFICULTY_TYPE_HARD",
|
||||
"difficultyLevel": 22,
|
||||
"chartAssetId": "chart_m0525_hard"
|
||||
},
|
||||
{
|
||||
"musicId": "m0525",
|
||||
"difficultyType": "MusicDifficultyType_MUSIC_DIFFICULTY_TYPE_NORMAL",
|
||||
"difficultyLevel": 13,
|
||||
"chartAssetId": "chart_m0525_normal"
|
||||
}
|
||||
],
|
||||
"charts": [
|
||||
{
|
||||
"musicId": "m0525",
|
||||
"difficultyType": "MusicDifficultyType_MUSIC_DIFFICULTY_TYPE_EASY",
|
||||
"fullComboNoteCount": 280,
|
||||
"maxComboCountRewardThreshold": 200,
|
||||
"chartHash": "d57f91ec1c09e69aaf2018f794924b9f",
|
||||
"normalNoteCount": 82
|
||||
},
|
||||
{
|
||||
"musicId": "m0525",
|
||||
"difficultyType": "MusicDifficultyType_MUSIC_DIFFICULTY_TYPE_EXPERT",
|
||||
"fullComboNoteCount": 1144,
|
||||
"maxComboCountRewardThreshold": 900,
|
||||
"chartHash": "8ad3680f19803518e6a9d50a6bc27d6a",
|
||||
"normalNoteCount": 352
|
||||
},
|
||||
{
|
||||
"musicId": "m0525",
|
||||
"difficultyType": "MusicDifficultyType_MUSIC_DIFFICULTY_TYPE_HARD",
|
||||
"fullComboNoteCount": 812,
|
||||
"maxComboCountRewardThreshold": 650,
|
||||
"chartHash": "773b12269be49f29b209fe541ac06744",
|
||||
"normalNoteCount": 337
|
||||
},
|
||||
{
|
||||
"musicId": "m0525",
|
||||
"difficultyType": "MusicDifficultyType_MUSIC_DIFFICULTY_TYPE_NORMAL",
|
||||
"fullComboNoteCount": 432,
|
||||
"maxComboCountRewardThreshold": 350,
|
||||
"chartHash": "17aa683df422800347a952dd579ba557",
|
||||
"normalNoteCount": 200
|
||||
}
|
||||
],
|
||||
"strings": [
|
||||
{
|
||||
"id": "la-music_arranger-m0525",
|
||||
"text": "PandaBoY"
|
||||
},
|
||||
{
|
||||
"id": "la-music_arranger_ruby-m0525",
|
||||
"text": "PandaBoY"
|
||||
},
|
||||
{
|
||||
"id": "la-music_composer-m0525",
|
||||
"text": "PandaBoY"
|
||||
},
|
||||
{
|
||||
"id": "la-music_composer_ruby-m0525",
|
||||
"text": "PandaBoY"
|
||||
},
|
||||
{
|
||||
"id": "la-music_lyricist-m0525",
|
||||
"text": "PandaBoY"
|
||||
},
|
||||
{
|
||||
"id": "la-music_lyricist_ruby-m0525",
|
||||
"text": "PandaBoY"
|
||||
},
|
||||
{
|
||||
"id": "la-music_title-m0525",
|
||||
"text": "Watcha Gatcha!!!!!!!!"
|
||||
},
|
||||
{
|
||||
"id": "la-music_title_ruby-m0525",
|
||||
"text": "WatchaGatcha!!!!!!!!"
|
||||
},
|
||||
{
|
||||
"id": "la-singer_name-m0525",
|
||||
"text": "hololive 5th Generation, Secret Society holoX"
|
||||
},
|
||||
{
|
||||
"id": "la-singer_name_ruby-m0525",
|
||||
"text": "hololive5thGenerationSecretSocietyholoX"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -286,6 +286,10 @@
|
||||
{"songId":"m0352","difficulty":"easy","susSha256":"d31a500a280011ad6da7d48ef23b950ef59fe649e1135408166277f76d35223c","fullComboNoteCount":220,"counts":{"NORMAL":64,"FLICK":0,"LONG_START":21,"LONG_END":21,"LONG_FLICK_END":0,"LONG_RELAY":3,"LONG_CONTINUE":111},"invisibleRelayCount":0,"bezierPointCount":0,"baseWeight":117400,"perfectNoteWeight":117400,"liveScoreCoefficientPermil":7,"difficultyLevel":3,"catalogRevision":84,"masterCommit":"5517e40b931fec10253f5b850989346518e233c2"},
|
||||
{"songId":"m0352","difficulty":"normal","susSha256":"097fddec319e17d1df3669068e794f826ea24a95b1b1f3a3959211570a309dd9","fullComboNoteCount":364,"counts":{"NORMAL":186,"FLICK":0,"LONG_START":32,"LONG_END":32,"LONG_FLICK_END":0,"LONG_RELAY":13,"LONG_CONTINUE":101},"invisibleRelayCount":0,"bezierPointCount":0,"baseWeight":261400,"perfectNoteWeight":261400,"liveScoreCoefficientPermil":7,"difficultyLevel":11,"catalogRevision":84,"masterCommit":"5517e40b931fec10253f5b850989346518e233c2"},
|
||||
{"songId":"m0352","difficulty":"hard","susSha256":"f39acc366a08d344740cdad5d5dc80551d1df9f9dcaedb862558677c0e58ce5d","fullComboNoteCount":640,"counts":{"NORMAL":321,"FLICK":30,"LONG_START":74,"LONG_END":63,"LONG_FLICK_END":11,"LONG_RELAY":25,"LONG_CONTINUE":116},"invisibleRelayCount":15,"bezierPointCount":0,"baseWeight":514600,"perfectNoteWeight":516100,"liveScoreCoefficientPermil":7,"difficultyLevel":19,"catalogRevision":84,"masterCommit":"5517e40b931fec10253f5b850989346518e233c2"},
|
||||
{"songId":"m0352","difficulty":"expert","susSha256":"71b330468b4d87a0db9f5734884934cf17252ed90ed69c8440b88122d550b0cb","fullComboNoteCount":1064,"counts":{"NORMAL":558,"FLICK":33,"LONG_START":135,"LONG_END":104,"LONG_FLICK_END":31,"LONG_RELAY":84,"LONG_CONTINUE":119},"invisibleRelayCount":227,"bezierPointCount":0,"baseWeight":882950,"perfectNoteWeight":905650,"liveScoreCoefficientPermil":7,"difficultyLevel":26,"catalogRevision":84,"masterCommit":"5517e40b931fec10253f5b850989346518e233c2"}
|
||||
{"songId":"m0352","difficulty":"expert","susSha256":"71b330468b4d87a0db9f5734884934cf17252ed90ed69c8440b88122d550b0cb","fullComboNoteCount":1064,"counts":{"NORMAL":558,"FLICK":33,"LONG_START":135,"LONG_END":104,"LONG_FLICK_END":31,"LONG_RELAY":84,"LONG_CONTINUE":119},"invisibleRelayCount":227,"bezierPointCount":0,"baseWeight":882950,"perfectNoteWeight":905650,"liveScoreCoefficientPermil":7,"difficultyLevel":26,"catalogRevision":84,"masterCommit":"5517e40b931fec10253f5b850989346518e233c2"},
|
||||
{"songId": "m0525", "fullComboNoteCount": 280, "counts": {"NORMAL": 82, "FLICK": 0, "LONG_START": 33, "LONG_END": 33, "LONG_FLICK_END": 0, "LONG_RELAY": 0, "LONG_CONTINUE": 132}, "invisibleRelayCount": 0, "bezierPointCount": 0, "baseWeight": 161200, "perfectNoteWeight": 161200, "difficulty": "easy", "susSha256": "370f884efeb1a407792fb5ea56be1a7ef43a0aeba590002e85fc50dcc9341fbd", "liveScoreCoefficientPermil": 5, "difficultyLevel": 6, "catalogRevision": 86, "chartHash": "d57f91ec1c09e69aaf2018f794924b9f", "metadataSource": {"page": "https://holodori.best/songs/m0525", "acquisition": "User-provided song detail JSON", "file": "docs/research/m0525-song-detail.json", "sha256": "3d532b5e5fec73a145a04efd9d17d63d0ddf537b5dac015233a4778bf3cd5198"}, "masterCommit": "98f8b557a095c837b51126d993c34d582fb5c5f2"},
|
||||
{"songId": "m0525", "fullComboNoteCount": 432, "counts": {"NORMAL": 200, "FLICK": 0, "LONG_START": 40, "LONG_END": 40, "LONG_FLICK_END": 0, "LONG_RELAY": 4, "LONG_CONTINUE": 148}, "invisibleRelayCount": 7, "bezierPointCount": 0, "baseWeight": 295200, "perfectNoteWeight": 295900, "difficulty": "normal", "susSha256": "1bf7c2eb76b7e517352156a0d07a0e2e076e7c44f36efd8a1457488a06661b4a", "liveScoreCoefficientPermil": 5, "difficultyLevel": 13, "catalogRevision": 86, "chartHash": "17aa683df422800347a952dd579ba557", "metadataSource": {"page": "https://holodori.best/songs/m0525", "acquisition": "User-provided song detail JSON", "file": "docs/research/m0525-song-detail.json", "sha256": "3d532b5e5fec73a145a04efd9d17d63d0ddf537b5dac015233a4778bf3cd5198"}, "masterCommit": "98f8b557a095c837b51126d993c34d582fb5c5f2"},
|
||||
{"songId": "m0525", "fullComboNoteCount": 812, "counts": {"NORMAL": 337, "FLICK": 26, "LONG_START": 95, "LONG_END": 81, "LONG_FLICK_END": 14, "LONG_RELAY": 50, "LONG_CONTINUE": 209}, "invisibleRelayCount": 174, "bezierPointCount": 0, "baseWeight": 580200, "perfectNoteWeight": 597600, "difficulty": "hard", "susSha256": "36a6a60665282ff3971a628d71e98f7b312cd3cef953dd4fd93a08d607386e42", "liveScoreCoefficientPermil": 5, "difficultyLevel": 22, "catalogRevision": 86, "chartHash": "773b12269be49f29b209fe541ac06744", "metadataSource": {"page": "https://holodori.best/songs/m0525", "acquisition": "User-provided song detail JSON", "file": "docs/research/m0525-song-detail.json", "sha256": "3d532b5e5fec73a145a04efd9d17d63d0ddf537b5dac015233a4778bf3cd5198"}, "masterCommit": "98f8b557a095c837b51126d993c34d582fb5c5f2"},
|
||||
{"songId": "m0525", "fullComboNoteCount": 1144, "counts": {"NORMAL": 352, "FLICK": 49, "LONG_START": 208, "LONG_END": 187, "LONG_FLICK_END": 21, "LONG_RELAY": 62, "LONG_CONTINUE": 265}, "invisibleRelayCount": 203, "bezierPointCount": 0, "baseWeight": 852150, "perfectNoteWeight": 872450, "difficulty": "expert", "susSha256": "ffca5baeeab5e5dd32449fb95a8f40bcb045cc8c8ce35920c48677e69ef2b523", "liveScoreCoefficientPermil": 5, "difficultyLevel": 27, "catalogRevision": 86, "chartHash": "8ad3680f19803518e6a9d50a6bc27d6a", "metadataSource": {"page": "https://holodori.best/songs/m0525", "acquisition": "User-provided song detail JSON", "file": "docs/research/m0525-song-detail.json", "sha256": "3d532b5e5fec73a145a04efd9d17d63d0ddf537b5dac015233a4778bf3cd5198"}, "masterCommit": "98f8b557a095c837b51126d993c34d582fb5c5f2"}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"repository": "https://github.com/DD4SN/DD4SN.github.io.git",
|
||||
"approvedMasterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9",
|
||||
"approvedNotBefore": "2026-09-19T01:00:00.000Z"
|
||||
"approvedMasterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd",
|
||||
"approvedNotBefore": "2026-09-26T12:00:00.000Z"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
// Convert Hololive Dreams' decrypted SUS dialect to this application's compact score timeline.
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readHolodoriDbTable as readMaster } from './holodoridb-source.mjs';
|
||||
import { readHolodoriDbTable as readMaster, prepareHolodoriDbSource } from './holodoridb-source.mjs';
|
||||
import path from 'node:path';
|
||||
import { susScoreWeight } from './sus-score-weight.mjs';
|
||||
import { formulaScoreMetadata } from './song-score-formulas.mjs';
|
||||
|
||||
import { parseSusTimeline } from './sus-timeline.mjs';
|
||||
const difficulties = ['easy', 'normal', 'hard', 'expert'];
|
||||
const overrides = JSON.parse(await readFile(new URL('../data/song-metadata-overrides.json', import.meta.url), 'utf8')).songs;
|
||||
|
||||
const music = new Map(readMaster('kor', 'Music.json').map((row) => { const data = row.data ?? row; return [data.id, data]; }));
|
||||
const translation = new Map(readMaster('kor', 'LangMusic_Kor.json').map((row) => { const data = row.data ?? row; return [data.id, data.text]; }));
|
||||
const levels = new Map(readMaster('kor', 'MusicDifficulty.json').map((row) => { const data = row.data ?? row; return [`${data.musicId}.${data.difficultyType.split('_').at(-1).toLowerCase()}`, data.difficultyLevel]; }));
|
||||
const reviewedSource = prepareHolodoriDbSource('kor');
|
||||
const masterDataSource = { repository: reviewedSource.repository, commit: reviewedSource.commit, locale: 'kor' };
|
||||
const songIds = process.argv.slice(2);
|
||||
if (songIds.length === 0) throw new Error('곡 ID 또는 곡 ID.난이도를 지정하세요.');
|
||||
const selected = songIds.flatMap((value) => {
|
||||
@@ -30,13 +33,37 @@ for (const { songId, difficulty } of selected) {
|
||||
const mids = timeline.notes.filter((note) => note.type === 'mid').length;
|
||||
throw new Error(`${songId}.${difficulty}: 파서 노트 수 ${timeline.notes.length}/${weight.fullComboNoteCount} 불일치 (중간 ${mids}, 헤더 ${weight.counts.LONG_RELAY + weight.counts.LONG_CONTINUE})`);
|
||||
}
|
||||
const master = music.get(songId);
|
||||
const difficultyLevel = levels.get(`${songId}.${difficulty}`);
|
||||
const officialMaster = music.get(songId);
|
||||
const metadataSource = !officialMaster ? overrides[songId] : undefined;
|
||||
let supplemental;
|
||||
if (metadataSource) {
|
||||
const metadataBytes = await readFile(path.resolve(metadataSource.file));
|
||||
if (createHash('sha256').update(metadataBytes).digest('hex') !== metadataSource.sha256) {
|
||||
throw new Error(songId + ': 보충 메타데이터 해시가 다릅니다.');
|
||||
}
|
||||
supplemental = JSON.parse(metadataBytes.toString('utf8'));
|
||||
if (supplemental.id !== songId) throw new Error(songId + ': 보충 메타데이터 곡 ID가 다릅니다.');
|
||||
}
|
||||
// Fresh official master takes precedence over the pinned supplementary response.
|
||||
const master = officialMaster ?? supplemental;
|
||||
const supplementalDifficulty = supplemental?.difficulties.find((row) => row.difficultyType.split('_').at(-1).toLowerCase() === difficulty);
|
||||
const difficultyLevel = levels.get(songId + '.' + difficulty) ?? supplementalDifficulty?.difficultyLevel;
|
||||
const releaseTime = Number(master?.startTime);
|
||||
const durationSeconds = master?.playingSeconds;
|
||||
if (!master || !Number.isInteger(difficultyLevel) || difficultyLevel < 1 || !Number.isFinite(releaseTime) ||
|
||||
!Number.isFinite(durationSeconds) || durationSeconds < timeline.notes.at(-1).timeSeconds ||
|
||||
!Number.isFinite(master.liveScoreCoefficientPermil) || !Array.isArray(master.characterIds) || !master.musicSingerType) {
|
||||
throw new Error(songId + '.' + difficulty + ': 검증된 곡 메타데이터가 필요합니다.');
|
||||
}
|
||||
if (supplementalDifficulty && (supplementalDifficulty.fullComboNoteCount !== weight.fullComboNoteCount || supplementalDifficulty.normalNoteCount !== weight.counts.NORMAL)) {
|
||||
throw new Error(songId + '.' + difficulty + ': 메타데이터와 SUS 노트 수가 다릅니다.');
|
||||
}
|
||||
const susSha256 = createHash('sha256').update(bytes).digest('hex');
|
||||
const formula = formulaScoreMetadata(songId, difficulty, difficultyLevel, timeline.notes.length, master.liveScoreCoefficientPermil, susSha256);
|
||||
const output = { schemaVersion: 1, songId, difficulty, durationSeconds: master.playingSeconds, scoreRatio: formula?.scoreRatio ?? null, scoreCalibrationSource: formula?.scoreCalibrationSource ?? null, ...(formula ?? {}), notes: timeline.notes, specialStartSeconds: timeline.specialStartSeconds, singerType: master.musicSingerType?.split('_').at(-1).toLowerCase() ?? null, singerCharacterIds: master.characterIds ?? [], source: { page: 'Hololive Dreams CDN via holodori-asset-tools', transcriptionStatus: 'exact_from_decrypted_sus', susSha256 } };
|
||||
outputs.push({ filename: path.resolve(`static/data/charts/${songId}.${difficulty}.json`), output });
|
||||
generatedCatalogEntries.push({ songId, title: translation.get(master.titleLangId) ?? songId, difficulty, difficultyLevel, durationSeconds: master.playingSeconds, scoreRatio: output.scoreRatio, scoreCalibrationSource: output.scoreCalibrationSource, scoreFormula: output.scoreFormula, totalNotes: output.notes.length, singerType: output.singerType, singerCharacterIds: output.singerCharacterIds, releaseTime: Number(master.startTime), path: `/data/charts/${songId}.${difficulty}.json` });
|
||||
if (!formula) throw new Error(songId + '.' + difficulty + ': 검증된 점수 공식이 필요합니다.');
|
||||
const output = { schemaVersion: 1, songId, difficulty, durationSeconds, ...formula, notes: timeline.notes, specialStartSeconds: timeline.specialStartSeconds, singerType: master.musicSingerType.split('_').at(-1).toLowerCase(), singerCharacterIds: master.characterIds, source: { page: 'Hololive Dreams CDN via holodori-asset-tools', transcriptionStatus: 'exact_from_decrypted_sus', susSha256, ...(metadataSource ? { metadataSource } : { masterDataSource }) } };
|
||||
outputs.push({ filename: path.resolve('static/data/charts/' + songId + '.' + difficulty + '.json'), output });
|
||||
generatedCatalogEntries.push({ songId, title: translation.get(master.titleLangId) ?? supplemental?.title?.kor ?? songId, difficulty, difficultyLevel, durationSeconds, scoreRatio: output.scoreRatio, scoreCalibrationSource: output.scoreCalibrationSource, scoreFormula: output.scoreFormula, totalNotes: output.notes.length, singerType: output.singerType, singerCharacterIds: output.singerCharacterIds, releaseTime, path: '/data/charts/' + songId + '.' + difficulty + '.json' });
|
||||
console.log(`${songId}.${difficulty}: ${timeline.notes.length} notes ${timeline.specialStartSeconds.join(', ')}`);
|
||||
}
|
||||
const catalogPath = path.resolve('src/lib/data/exact-chart-catalog.json');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd",
|
||||
"members": [
|
||||
{
|
||||
"id": "chr-00001",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd",
|
||||
"cards": [
|
||||
{
|
||||
"id": "card-00001-5-uniq-0000-00",
|
||||
|
||||
@@ -17122,6 +17122,118 @@
|
||||
"difficultyLevel": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"songId": "m0525",
|
||||
"title": "Watcha Gatcha!!!!!!!!",
|
||||
"difficulty": "easy",
|
||||
"difficultyLevel": 6,
|
||||
"durationSeconds": 122,
|
||||
"scoreRatio": 69.73826519576033,
|
||||
"scoreCalibrationSource": "formula-v1",
|
||||
"scoreFormula": {
|
||||
"perfectNoteWeight": 161200,
|
||||
"liveScoreCoefficientPermil": 5,
|
||||
"difficultyLevel": 6
|
||||
},
|
||||
"totalNotes": 280,
|
||||
"singerType": "group",
|
||||
"singerCharacterIds": [
|
||||
"chr-00030",
|
||||
"chr-00031",
|
||||
"chr-00032",
|
||||
"chr-00034",
|
||||
"chr-00035",
|
||||
"chr-00036",
|
||||
"chr-00037",
|
||||
"chr-00039"
|
||||
],
|
||||
"releaseTime": 1790424000000,
|
||||
"path": "/data/charts/m0525.easy.json"
|
||||
},
|
||||
{
|
||||
"songId": "m0525",
|
||||
"title": "Watcha Gatcha!!!!!!!!",
|
||||
"difficulty": "expert",
|
||||
"difficultyLevel": 27,
|
||||
"durationSeconds": 122,
|
||||
"scoreRatio": 341.73521347434394,
|
||||
"scoreCalibrationSource": "formula-v1",
|
||||
"scoreFormula": {
|
||||
"perfectNoteWeight": 872450,
|
||||
"liveScoreCoefficientPermil": 5,
|
||||
"difficultyLevel": 27
|
||||
},
|
||||
"totalNotes": 1144,
|
||||
"singerType": "group",
|
||||
"singerCharacterIds": [
|
||||
"chr-00030",
|
||||
"chr-00031",
|
||||
"chr-00032",
|
||||
"chr-00034",
|
||||
"chr-00035",
|
||||
"chr-00036",
|
||||
"chr-00037",
|
||||
"chr-00039"
|
||||
],
|
||||
"releaseTime": 1790424000000,
|
||||
"path": "/data/charts/m0525.expert.json"
|
||||
},
|
||||
{
|
||||
"songId": "m0525",
|
||||
"title": "Watcha Gatcha!!!!!!!!",
|
||||
"difficulty": "hard",
|
||||
"difficultyLevel": 22,
|
||||
"durationSeconds": 122,
|
||||
"scoreRatio": 239.47104788619518,
|
||||
"scoreCalibrationSource": "formula-v1",
|
||||
"scoreFormula": {
|
||||
"perfectNoteWeight": 597600,
|
||||
"liveScoreCoefficientPermil": 5,
|
||||
"difficultyLevel": 22
|
||||
},
|
||||
"totalNotes": 812,
|
||||
"singerType": "group",
|
||||
"singerCharacterIds": [
|
||||
"chr-00030",
|
||||
"chr-00031",
|
||||
"chr-00032",
|
||||
"chr-00034",
|
||||
"chr-00035",
|
||||
"chr-00036",
|
||||
"chr-00037",
|
||||
"chr-00039"
|
||||
],
|
||||
"releaseTime": 1790424000000,
|
||||
"path": "/data/charts/m0525.hard.json"
|
||||
},
|
||||
{
|
||||
"songId": "m0525",
|
||||
"title": "Watcha Gatcha!!!!!!!!",
|
||||
"difficulty": "normal",
|
||||
"difficultyLevel": 13,
|
||||
"durationSeconds": 122,
|
||||
"scoreRatio": 123.70401337792642,
|
||||
"scoreCalibrationSource": "formula-v1",
|
||||
"scoreFormula": {
|
||||
"perfectNoteWeight": 295900,
|
||||
"liveScoreCoefficientPermil": 5,
|
||||
"difficultyLevel": 13
|
||||
},
|
||||
"totalNotes": 432,
|
||||
"singerType": "group",
|
||||
"singerCharacterIds": [
|
||||
"chr-00030",
|
||||
"chr-00031",
|
||||
"chr-00032",
|
||||
"chr-00034",
|
||||
"chr-00035",
|
||||
"chr-00036",
|
||||
"chr-00037",
|
||||
"chr-00039"
|
||||
],
|
||||
"releaseTime": 1790424000000,
|
||||
"path": "/data/charts/m0525.normal.json"
|
||||
},
|
||||
{
|
||||
"songId": "m0527",
|
||||
"title": "Mekurumeku Rendezvous",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"masterDataVersion": "927a8489062cfb8aaee197689930a4c4e9c622e9ebbf8b80577bdbe5f065dcf9",
|
||||
"masterDataVersion": "845cee36af22b657511ca406b5417cac7076a001208518c9601d6389a5046ddd",
|
||||
"maxCount": 34,
|
||||
"effects": [
|
||||
{
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { leaderRatingCharts } from './song-rating.ts';
|
||||
import overrides from '../../../data/song-metadata-overrides.json' with { type: 'json' };
|
||||
import officialSong from '../../../docs/research/m0525-holodoridb.json' with { type: 'json' };
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
@@ -62,3 +66,43 @@ test('잘못된 분모와 난이도로 공식 환산값을 만들지 않는다',
|
||||
assert.throws(() => scoreRatioFromFormula({ ...valid, ...invalid }));
|
||||
}
|
||||
});
|
||||
|
||||
test('m0525는 보충 응답과 일치하는 공식 마스터를 우선 적용한다', async () => {
|
||||
const source = overrides.songs.m0525;
|
||||
const bytes = await readFile(new URL(`../../../${source.file}`, import.meta.url));
|
||||
assert.equal(createHash('sha256').update(bytes).digest('hex'), source.sha256);
|
||||
const master = JSON.parse(bytes.toString('utf8'));
|
||||
const charts = catalog.charts.filter((row) => row.songId === master.id);
|
||||
assert.equal(charts.length, 4);
|
||||
const releaseTime = Number(master.startTime);
|
||||
const weights = { easy: 161200, normal: 295900, hard: 597600, expert: 872450 };
|
||||
for (const row of charts) {
|
||||
const difficulty = master.difficulties.find((item: { difficultyType: string }) => item.difficultyType.toLowerCase().endsWith('_' + row.difficulty));
|
||||
const chart = JSON.parse(await readFile(new URL(`../../../static${row.path}`, import.meta.url), 'utf8'));
|
||||
const officialDifficulty = officialSong.difficulties.find((item) => item.difficultyType === difficulty.difficultyType);
|
||||
const officialChart = officialSong.charts.find((item) => item.difficultyType === difficulty.difficultyType);
|
||||
assert.equal(row.difficultyLevel, officialDifficulty?.difficultyLevel);
|
||||
assert.equal(row.totalNotes, officialChart?.fullComboNoteCount);
|
||||
assert.equal(officialChart?.chartHash, difficulty.chartHash);
|
||||
assert.equal(row.difficultyLevel, difficulty.difficultyLevel);
|
||||
assert.equal(row.totalNotes, difficulty.fullComboNoteCount);
|
||||
assert.equal(row.title, master.title.kor);
|
||||
assert.equal(row.durationSeconds, master.playingSeconds);
|
||||
assert.equal(row.releaseTime, releaseTime);
|
||||
assert.equal(row.scoreFormula.liveScoreCoefficientPermil, master.liveScoreCoefficientPermil);
|
||||
assert.equal(row.scoreFormula.perfectNoteWeight, weights[row.difficulty as keyof typeof weights]);
|
||||
assert.equal(row.singerType, 'group');
|
||||
assert.deepEqual(row.singerCharacterIds, master.characterIds);
|
||||
assert.deepEqual(chart.singerCharacterIds, master.characterIds);
|
||||
assert.deepEqual(chart.source.masterDataSource, officialSong.source);
|
||||
assert.equal(chart.source.metadataSource, undefined);
|
||||
assert.deepEqual(chart.singerCharacterIds, officialSong.music.characterIds);
|
||||
assert.equal(row.scoreFormula.liveScoreCoefficientPermil, officialSong.music.liveScoreCoefficientPermil);
|
||||
assert.equal(chart.specialStartSeconds.length, 5);
|
||||
assert.ok(chart.notes.every((note: { timeSeconds: number }) => Number.isFinite(note.timeSeconds) && note.timeSeconds <= row.durationSeconds));
|
||||
}
|
||||
assert.equal(master.characterIds.length, 8);
|
||||
assert.equal(leaderRatingCharts(charts, 'chr-00030', 'all', releaseTime - 1).length, 0);
|
||||
assert.equal(leaderRatingCharts(charts, 'chr-00030', 'all', releaseTime).length, 4);
|
||||
assert.equal(leaderRatingCharts(charts, 'chr-00038', 'all', releaseTime).length, 0);
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user