feat: release version 1.0.5

This commit is contained in:
2026-08-20 14:30:22 +09:00
parent f910db8901
commit 6537ab1f20
35 changed files with 1151 additions and 181 deletions
+131 -1
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"source": "https://dreams.wf-calc.net",
"source": "https://dreams.wf-calc.net; hololive Dreams CDN via https://github.com/HolodoriDB/holodori-asset-tools",
"charts": [
{
"songId": "m0001",
@@ -4712,6 +4712,58 @@
],
"path": "/data/charts/m0331.hard.json"
},
{
"songId": "m0332",
"title": "BANZAI☆MANKAI",
"difficulty": "expert",
"durationSeconds": 136,
"scoreRatio": 280.40008469873237,
"totalNotes": 938,
"singerType": "solo",
"singerCharacterIds": [
"chr-00010"
],
"path": "/data/charts/m0332.expert.json"
},
{
"songId": "m0332",
"title": "BANZAI☆MANKAI",
"difficulty": "hard",
"durationSeconds": 136,
"scoreRatio": 220.44697428340993,
"totalNotes": 760,
"singerType": "solo",
"singerCharacterIds": [
"chr-00010"
],
"path": "/data/charts/m0332.hard.json"
},
{
"songId": "m0333",
"title": "Takoyaki Gazing",
"difficulty": "expert",
"durationSeconds": 131,
"scoreRatio": 299.0414358467602,
"totalNotes": 888,
"singerType": "solo",
"singerCharacterIds": [
"chr-00028"
],
"path": "/data/charts/m0333.expert.json"
},
{
"songId": "m0333",
"title": "Takoyaki Gazing",
"difficulty": "hard",
"durationSeconds": 131,
"scoreRatio": 209.7523345809546,
"totalNotes": 624,
"singerType": "solo",
"singerCharacterIds": [
"chr-00028"
],
"path": "/data/charts/m0333.hard.json"
},
{
"songId": "m0334",
"title": "NIGHTBREAK",
@@ -4738,6 +4790,58 @@
],
"path": "/data/charts/m0334.hard.json"
},
{
"songId": "m0335",
"title": "Oshi Mode ON",
"difficulty": "expert",
"durationSeconds": 129,
"scoreRatio": 418.6877175179705,
"totalNotes": 1173,
"singerType": "solo",
"singerCharacterIds": [
"chr-03004"
],
"path": "/data/charts/m0335.expert.json"
},
{
"songId": "m0335",
"title": "Oshi Mode ON",
"difficulty": "hard",
"durationSeconds": 129,
"scoreRatio": 250.28427636994522,
"totalNotes": 753,
"singerType": "solo",
"singerCharacterIds": [
"chr-03004"
],
"path": "/data/charts/m0335.hard.json"
},
{
"songId": "m0336",
"title": "Stardust Capsule",
"difficulty": "expert",
"durationSeconds": 117,
"scoreRatio": 250.46994719811275,
"totalNotes": 879,
"singerType": "solo",
"singerCharacterIds": [
"chr-04003"
],
"path": "/data/charts/m0336.expert.json"
},
{
"songId": "m0336",
"title": "Stardust Capsule",
"difficulty": "hard",
"durationSeconds": 117,
"scoreRatio": 152.06440826927707,
"totalNotes": 611,
"singerType": "solo",
"singerCharacterIds": [
"chr-04003"
],
"path": "/data/charts/m0336.hard.json"
},
{
"songId": "m0337",
"title": "美少女戦士まつりみっとZero",
@@ -4816,6 +4920,32 @@
],
"path": "/data/charts/m0347.hard.json"
},
{
"songId": "m0353",
"title": "Night Loop",
"difficulty": "expert",
"durationSeconds": 134,
"scoreRatio": 327.3933713079548,
"totalNotes": 953,
"singerType": "solo",
"singerCharacterIds": [
"chr-00014"
],
"path": "/data/charts/m0353.expert.json"
},
{
"songId": "m0353",
"title": "Night Loop",
"difficulty": "hard",
"durationSeconds": 134,
"scoreRatio": 204.12650848747597,
"totalNotes": 641,
"singerType": "solo",
"singerCharacterIds": [
"chr-00014"
],
"path": "/data/charts/m0353.hard.json"
},
{
"songId": "m0524",
"title": "You & 合図",
+73
View File
@@ -0,0 +1,73 @@
export type ReleaseClockSource = 'server' | 'system';
export type ReleaseClock = {
source: ReleaseClockSource;
now: () => number;
};
type TimeResponse = {
now?: unknown;
};
type ReleaseClockOptions = {
fetcher?: typeof fetch;
wallNow?: () => number;
monotonicNow?: () => number;
online?: boolean;
timeoutMs?: number;
};
function defaultMonotonicNow(): number {
return typeof performance === 'undefined' ? Date.now() : performance.now();
}
function serverClock(serverNow: number, monotonicNow: () => number, baseMonotonic = monotonicNow()): ReleaseClock {
return {
source: 'server',
now: () => serverNow + Math.max(0, monotonicNow() - baseMonotonic)
};
}
function systemClock(wallNow: () => number): ReleaseClock {
return { source: 'system', now: wallNow };
}
export function createInitialReleaseClock(serverNow: unknown, options: Pick<ReleaseClockOptions, 'wallNow' | 'monotonicNow'> = {}): ReleaseClock {
const wallNow = options.wallNow ?? Date.now;
const monotonicNow = options.monotonicNow ?? defaultMonotonicNow;
return typeof serverNow === 'number' && Number.isFinite(serverNow)
? serverClock(serverNow, monotonicNow)
: systemClock(wallNow);
}
export async function synchronizeReleaseClock(options: ReleaseClockOptions = {}): Promise<ReleaseClock> {
const wallNow = options.wallNow ?? Date.now;
const monotonicNow = options.monotonicNow ?? defaultMonotonicNow;
const online = options.online ?? (typeof location === 'undefined' || location.protocol !== 'file:');
if (!online) return systemClock(wallNow);
const fetcher = options.fetcher ?? globalThis.fetch?.bind(globalThis);
if (!fetcher) return systemClock(wallNow);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3_000);
const requestStarted = monotonicNow();
try {
const response = await fetcher('/api/time', {
cache: 'no-store',
headers: { accept: 'application/json' },
signal: controller.signal
});
if (!response.ok) return systemClock(wallNow);
const payload = await response.json() as TimeResponse;
if (typeof payload.now !== 'number' || !Number.isFinite(payload.now)) return systemClock(wallNow);
const responseReceived = monotonicNow();
const estimatedNow = payload.now + Math.max(0, responseReceived - requestStarted) / 2;
return serverClock(estimatedNow, monotonicNow, responseReceived);
} catch {
return systemClock(wallNow);
} finally {
clearTimeout(timeout);
}
}
+16
View File
@@ -0,0 +1,16 @@
export const SONG_RELEASE_TIMES: Readonly<Record<string, number>> = {
m0332: 1_787_137_200_000,
m0353: 1_787_151_600_000,
m0335: 1_787_310_000_000,
m0333: 1_787_482_800_000,
m0336: 1_787_655_600_000
};
export function isSongReleased(songId: string, now = Date.now()): boolean {
const releaseTime = SONG_RELEASE_TIMES[songId];
return releaseTime === undefined || now >= releaseTime;
}
export function releasedSongCharts<T extends { songId: string }>(charts: readonly T[], now = Date.now()): T[] {
return charts.filter((chart) => isSongReleased(chart.songId, now));
}