From 0f0431de5a247d13ed46e5bfcdf48f3e109ae672 Mon Sep 17 00:00:00 2001 From: ProjectQuint Date: Sun, 6 Sep 2026 17:11:40 +0900 Subject: [PATCH] =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EA=B3=B5=EA=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 55 + .vscode/extensions.json | 5 + .vscode/launch.json | 10 + .vscode/settings.json | 71 + AGENTS.md | 23 + Assets/DefaultVolumeProfile.asset | 798 ++ Assets/DefaultVolumeProfile.asset.meta | 8 + Assets/InputSystem_Actions.inputactions | 1057 +++ Assets/InputSystem_Actions.inputactions.meta | 14 + Assets/Plugins.meta | 8 + Assets/Plugins/FMOD.meta | 8 + Assets/Plugins/FMOD/Cache.meta | 8 + Assets/Plugins/FMOD/Cache/Editor.meta | 8 + .../FMOD/Cache/Editor/FMODStudioCache.asset | 22 + .../Cache/Editor/FMODStudioCache.asset.meta | 8 + Assets/Plugins/FMOD/Resources.meta | 8 + .../FMOD/Resources/FMODStudioSettings.asset | 956 +++ .../Resources/FMODStudioSettings.asset.meta | 8 + Assets/ProjectQuint.meta | 8 + Assets/ProjectQuint/Art.meta | 8 + Assets/ProjectQuint/Art/AppIcon.meta | 8 + .../Art/AppIcon/ProjectQuintAppIcon.png | Bin 0 -> 1143648 bytes .../Art/AppIcon/ProjectQuintAppIcon.png.meta | 143 + Assets/ProjectQuint/Editor.meta | 8 + .../Editor/ChartFeatureCalibrationReport.cs | 90 + .../ChartFeatureCalibrationReport.cs.meta | 2 + .../ProjectQuint/Editor/ProjectQuintBuild.cs | 758 ++ .../Editor/ProjectQuintBuild.cs.meta | 2 + .../Editor/ProjectQuintIconConfigurator.cs | 37 + .../ProjectQuintIconConfigurator.cs.meta | 2 + .../Editor/ProjectQuintVisualQa.cs | 1149 +++ .../Editor/ProjectQuintVisualQa.cs.meta | 2 + Assets/ProjectQuint/Plugins.meta | 8 + Assets/ProjectQuint/Plugins/macOS.meta | 8 + .../macOS/ProjectQuintInput.bundle.meta | 2 + .../Contents/Info.plist | 22 + .../Contents/MacOS/ProjectQuintInput | Bin 0 -> 135968 bytes .../Contents/_CodeSignature/CodeResources | 115 + .../macOS/ProjectQuintUpdater.bundle.meta | 2 + .../Contents/Info.plist | 22 + .../Contents/MacOS/ProjectQuintUpdater | Bin 0 -> 136608 bytes .../Contents/_CodeSignature/CodeResources | 115 + Assets/ProjectQuint/Runtime.meta | 8 + Assets/ProjectQuint/Runtime/Core.meta | 8 + .../ProjectQuint/Runtime/Core/Analysis.meta | 8 + .../Core/Analysis/ChartFeatureAnalysis.cs | 741 ++ .../Analysis/ChartFeatureAnalysis.cs.meta | 2 + Assets/ProjectQuint/Runtime/Core/Bms.meta | 8 + .../Runtime/Core/Bms/BmsModels.cs | 175 + .../Runtime/Core/Bms/BmsModels.cs.meta | 2 + .../Runtime/Core/Bms/BmsParser.cs | 588 ++ .../Runtime/Core/Bms/BmsParser.cs.meta | 2 + .../Core/Bms/BmsPlayableChartConverter.cs | 170 + .../Bms/BmsPlayableChartConverter.cs.meta | 2 + .../Runtime/Core/Bms/BmsTimelineCompiler.cs | 493 ++ .../Core/Bms/BmsTimelineCompiler.cs.meta | 2 + Assets/ProjectQuint/Runtime/Core/Charts.meta | 8 + .../Runtime/Core/Charts/PlayableChart.cs | 158 + .../Runtime/Core/Charts/PlayableChart.cs.meta | 2 + Assets/ProjectQuint/Runtime/Core/Course.meta | 8 + .../Core/Course/CourseLaneModifierPolicy.cs | 26 + .../Course/CourseLaneModifierPolicy.cs.meta | 2 + .../Runtime/Core/Course/CourseProgress.cs | 131 + .../Core/Course/CourseProgress.cs.meta | 2 + Assets/ProjectQuint/Runtime/Core/Gauge.meta | 8 + .../Runtime/Core/Gauge/GaugeFallbackChain.cs | 120 + .../Core/Gauge/GaugeFallbackChain.cs.meta | 2 + .../Runtime/Core/Gauge/PlayGauge.cs | 263 + .../Runtime/Core/Gauge/PlayGauge.cs.meta | 2 + .../ProjectQuint/Runtime/Core/Identity.meta | 8 + .../Runtime/Core/Identity/ChartId.cs | 46 + .../Runtime/Core/Identity/ChartId.cs.meta | 2 + .../ProjectQuint/Runtime/Core/Judgement.meta | 8 + .../Core/Judgement/LongNotePolicies.cs | 79 + .../Core/Judgement/LongNotePolicies.cs.meta | 2 + .../Core/Judgement/RhythmJudgementEngine.cs | 575 ++ .../Judgement/RhythmJudgementEngine.cs.meta | 2 + .../Runtime/Core/Progression.meta | 8 + .../Core/Progression/ProfileCollection.cs | 198 + .../Progression/ProfileCollection.cs.meta | 2 + .../Core/Progression/ProfileProgression.cs | 390 + .../Progression/ProfileProgression.cs.meta | 2 + .../Runtime/Core/ProjectQuint.Core.asmdef | 14 + .../Core/ProjectQuint.Core.asmdef.meta | 7 + Assets/ProjectQuint/Runtime/Core/Release.meta | 8 + .../Release/ProjectQuintReleaseVersion.cs | 29 + .../ProjectQuintReleaseVersion.cs.meta | 11 + Assets/ProjectQuint/Runtime/Core/Timing.meta | 8 + .../Timing/AudioSyncCalibrationEstimator.cs | 137 + .../AudioSyncCalibrationEstimator.cs.meta | 2 + .../Core/Timing/AudioSyncTapMatcher.cs | 43 + .../Core/Timing/AudioSyncTapMatcher.cs.meta | 2 + .../Runtime/Core/Timing/MusicalTimingMaps.cs | 321 + .../Core/Timing/MusicalTimingMaps.cs.meta | 2 + .../Runtime/Core/Timing/ResilientSongClock.cs | 95 + .../Core/Timing/ResilientSongClock.cs.meta | 2 + Assets/ProjectQuint/Runtime/Unity.meta | 8 + .../Runtime/Unity/AssemblyInfo.cs | 9 + .../Runtime/Unity/AssemblyInfo.cs.meta | 2 + Assets/ProjectQuint/Runtime/Unity/Audio.meta | 8 + .../Runtime/Unity/Audio/AssemblyInfo.cs | 5 + .../Runtime/Unity/Audio/AssemblyInfo.cs.meta | 2 + .../Runtime/Unity/Audio/AudioAssetStore.cs | 738 ++ .../Unity/Audio/AudioAssetStore.cs.meta | 2 + .../Unity/Audio/AudioBackendContracts.cs | 178 + .../Unity/Audio/AudioBackendContracts.cs.meta | 2 + .../Unity/Audio/AudioDspBufferController.cs | 217 + .../Audio/AudioDspBufferController.cs.meta | 2 + .../Runtime/Unity/Audio/BmsAudioSystem.cs | 186 + .../Unity/Audio/BmsAudioSystem.cs.meta | 2 + .../Unity/Audio/FmodAudioSyncMetronome.cs | 143 + .../Audio/FmodAudioSyncMetronome.cs.meta | 2 + .../Unity/Audio/FmodCoreAudioBackend.cs | 1013 +++ .../Unity/Audio/FmodCoreAudioBackend.cs.meta | 2 + .../Unity/Audio/FmodCoreReactivePlayback.cs | 434 + .../Audio/FmodCoreReactivePlayback.cs.meta | 2 + .../Runtime/Unity/Audio/PremixedSongCache.cs | 721 ++ .../Unity/Audio/PremixedSongCache.cs.meta | 2 + .../Unity/Audio/PremixedSongRenderer.cs | 441 + .../Unity/Audio/PremixedSongRenderer.cs.meta | 2 + .../Audio/ProjectQuint.Media.Audio.asmdef | 18 + .../ProjectQuint.Media.Audio.asmdef.meta | 7 + .../Runtime/Unity/Audio/SmoothAudioMixer.cs | 261 + .../Unity/Audio/SmoothAudioMixer.cs.meta | 2 + .../Unity/Audio/SongAudioPlaybackEngine.cs | 368 + .../Audio/SongAudioPlaybackEngine.cs.meta | 2 + .../Runtime/Unity/Audio/WaveAudioDecoder.cs | 447 + .../Unity/Audio/WaveAudioDecoder.cs.meta | 2 + .../ProjectQuint/Runtime/Unity/Bootstrap.meta | 8 + .../Runtime/Unity/Bootstrap/AssemblyInfo.cs | 3 + .../Unity/Bootstrap/AssemblyInfo.cs.meta | 2 + .../Bootstrap/PinnedPackPremixCacheService.cs | 508 ++ .../PinnedPackPremixCacheService.cs.meta | 2 + .../Bootstrap/ProjectQuint.Bootstrap.asmdef | 23 + .../ProjectQuint.Bootstrap.asmdef.meta | 7 + .../Unity/Bootstrap/ProjectQuintApp.cs | 7251 +++++++++++++++++ .../Unity/Bootstrap/ProjectQuintApp.cs.meta | 2 + .../Bootstrap/ProjectQuintSparkleUpdater.cs | 137 + .../ProjectQuintSparkleUpdater.cs.meta | 2 + .../Bootstrap/ProjectQuintUpdateService.cs | 212 + .../ProjectQuintUpdateService.cs.meta | 2 + .../Bootstrap/StandaloneProfileOptions.cs | 159 + .../StandaloneProfileOptions.cs.meta | 2 + .../ProjectQuint/Runtime/Unity/Features.meta | 8 + .../Runtime/Unity/Features/AssemblyInfo.cs | 5 + .../Unity/Features/AssemblyInfo.cs.meta | 2 + .../Runtime/Unity/Features/Course.meta | 8 + .../Course/CourseSessionController.cs | 123 + .../Course/CourseSessionController.cs.meta | 2 + .../Runtime/Unity/Features/Flow.meta | 8 + .../Unity/Features/Flow/AppFlowController.cs | 121 + .../Features/Flow/AppFlowController.cs.meta | 2 + .../Features/Flow/DirectionalKeyRepeat.cs | 61 + .../Flow/DirectionalKeyRepeat.cs.meta | 2 + .../Flow/StartupAttributionTimeline.cs | 72 + .../Flow/StartupAttributionTimeline.cs.meta | 2 + .../Runtime/Unity/Features/Gameplay.meta | 8 + .../Gameplay/GameplayFramePacingMonitor.cs | 481 ++ .../GameplayFramePacingMonitor.cs.meta | 2 + .../Gameplay/GameplaySessionController.cs | 513 ++ .../GameplaySessionController.cs.meta | 2 + .../Features/Gameplay/HighRateInputMonitor.cs | 327 + .../Gameplay/HighRateInputMonitor.cs.meta | 2 + .../Features/Gameplay/LaneKeySoundResolver.cs | 111 + .../Gameplay/LaneKeySoundResolver.cs.meta | 2 + .../Gameplay/MacNativeKeyboardPoller.cs | 296 + .../Gameplay/MacNativeKeyboardPoller.cs.meta | 2 + .../Runtime/Unity/Features/Library.meta | 8 + .../Features/Library/LibraryController.cs | 572 ++ .../Library/LibraryController.cs.meta | 2 + .../Unity/Features/Library/LibraryState.cs | 395 + .../Features/Library/LibraryState.cs.meta | 2 + .../Runtime/Unity/Features/Loading.meta | 8 + .../Unity/Features/Loading/PreparedSong.cs | 119 + .../Features/Loading/PreparedSong.cs.meta | 2 + .../Features/Loading/SongLoadCoordinator.cs | 407 + .../Loading/SongLoadCoordinator.cs.meta | 2 + .../Features/ProjectQuint.Application.asmdef | 21 + .../ProjectQuint.Application.asmdef.meta | 7 + .../Runtime/Unity/Features/Results.meta | 8 + .../Results/ResultSessionController.cs | 231 + .../Results/ResultSessionController.cs.meta | 2 + .../Runtime/Unity/Features/Settings.meta | 8 + .../Settings/AudioSyncCalibrationSession.cs | 147 + .../AudioSyncCalibrationSession.cs.meta | 2 + .../Runtime/Unity/Infrastructure.meta | 8 + .../Runtime/Unity/Infrastructure/Images.meta | 8 + .../Infrastructure/Images/AssemblyInfo.cs | 6 + .../Images/AssemblyInfo.cs.meta | 2 + .../Infrastructure/Images/BmsImageLoader.cs | 117 + .../Images/BmsImageLoader.cs.meta | 2 + .../Images/ProjectQuint.Media.Images.asmdef | 16 + .../ProjectQuint.Media.Images.asmdef.meta | 7 + .../ProjectQuint/Runtime/Unity/Library.meta | 8 + .../Runtime/Unity/Library/AeryCourseIndex.cs | 424 + .../Unity/Library/AeryCourseIndex.cs.meta | 2 + .../Unity/Library/AeryCourseRecordStore.cs | 111 + .../Library/AeryCourseRecordStore.cs.meta | 2 + .../Runtime/Unity/Library/AeryTableIndex.cs | 116 + .../Unity/Library/AeryTableIndex.cs.meta | 2 + .../Unity/Library/BitmapImageDecoder.cs | 150 + .../Unity/Library/BitmapImageDecoder.cs.meta | 2 + .../Runtime/Unity/Library/BmsCourseRules.cs | 220 + .../Unity/Library/BmsCourseRules.cs.meta | 2 + .../Unity/Library/BmsDifficultyTableIndex.cs | 385 + .../Library/BmsDifficultyTableIndex.cs.meta | 2 + .../Library/BmsDifficultyTableRegistry.cs | 364 + .../BmsDifficultyTableRegistry.cs.meta | 2 + .../Unity/Library/BmsLibraryLocator.cs | 123 + .../Unity/Library/BmsLibraryLocator.cs.meta | 2 + .../Unity/Library/BmsPackImportService.cs | 521 ++ .../Library/BmsPackImportService.cs.meta | 2 + .../Runtime/Unity/Library/BmsPackRegistry.cs | 650 ++ .../Unity/Library/BmsPackRegistry.cs.meta | 2 + .../Unity/Library/BmsResourceResolver.cs | 51 + .../Unity/Library/BmsResourceResolver.cs.meta | 2 + .../Runtime/Unity/Library/ChartCatalog.cs | 115 + .../Unity/Library/ChartCatalog.cs.meta | 2 + .../Unity/Library/ExternalJsonDataSource.cs | 225 + .../Library/ExternalJsonDataSource.cs.meta | 2 + .../Unity/Library/JapaneseSearchNormalizer.cs | 238 + .../Library/JapaneseSearchNormalizer.cs.meta | 2 + .../Unity/Library/SongClearLampStore.cs | 41 + .../Unity/Library/SongClearLampStore.cs.meta | 2 + .../Unity/Library/SongLibraryScanner.cs | 765 ++ .../Unity/Library/SongLibraryScanner.cs.meta | 2 + .../Runtime/Unity/Library/SongLibraryView.cs | 167 + .../Unity/Library/SongLibraryView.cs.meta | 2 + .../Unity/Library/SongPlayRecordStore.cs | 506 ++ .../Unity/Library/SongPlayRecordStore.cs.meta | 2 + .../Runtime/Unity/Library/SongSearchIndex.cs | 224 + .../Unity/Library/SongSearchIndex.cs.meta | 2 + .../Runtime/Unity/Persistence.meta | 8 + .../Unity/Persistence/AtomicJsonFile.cs | 176 + .../Unity/Persistence/AtomicJsonFile.cs.meta | 2 + .../Persistence/ChartIdentityResolver.cs | 46 + .../Persistence/ChartIdentityResolver.cs.meta | 2 + .../Persistence/CourseRecordRepository.cs | 332 + .../CourseRecordRepository.cs.meta | 2 + .../Persistence/FavoriteChartRepository.cs | 118 + .../FavoriteChartRepository.cs.meta | 2 + .../Unity/Persistence/PersistencePaths.cs | 58 + .../Persistence/PersistencePaths.cs.meta | 2 + .../PlayResultCommitCoordinator.cs | 120 + .../PlayResultCommitCoordinator.cs.meta | 2 + .../Unity/Persistence/PlayResultEvent.cs | 86 + .../Unity/Persistence/PlayResultEvent.cs.meta | 2 + .../PlayerProgressionRepository.cs | 259 + .../PlayerProgressionRepository.cs.meta | 2 + .../ProductIdentityStorageMigrator.cs | 128 + .../ProductIdentityStorageMigrator.cs.meta | 2 + .../Persistence/ProfileArchiveService.cs | 580 ++ .../Persistence/ProfileArchiveService.cs.meta | 2 + .../Unity/Persistence/ProfileImageStore.cs | 250 + .../Persistence/ProfileImageStore.cs.meta | 2 + .../Persistence/ProfileStorageMigrator.cs | 140 + .../ProfileStorageMigrator.cs.meta | 2 + .../Unity/Persistence/SongRecordRepository.cs | 606 ++ .../Persistence/SongRecordRepository.cs.meta | 2 + .../Unity/ProjectQuint.Infrastructure.asmdef | 17 + .../ProjectQuint.Infrastructure.asmdef.meta | 7 + .../ProjectQuint/Runtime/Unity/Resources.meta | 8 + .../Runtime/Unity/Resources/ProjectQuint.meta | 8 + .../Resources/ProjectQuint/Branding.meta | 8 + .../ProjectQuint/Branding/FMODLogoWhite.png | Bin 0 -> 8803 bytes .../Branding/FMODLogoWhite.png.meta | 247 + .../ProjectQuint/Branding/TeamDeniaMark.png | Bin 0 -> 187231 bytes .../Branding/TeamDeniaMark.png.meta | 143 + .../Unity/Resources/ProjectQuint/Data.meta | 8 + .../ProjectQuint/Data/AeryHeader.json | 1 + .../ProjectQuint/Data/AeryHeader.json.meta | 7 + .../ProjectQuint/Data/SearchAliases.json | 17 + .../ProjectQuint/Data/SearchAliases.json.meta | 7 + .../ProjectQuint/Runtime/Unity/Settings.meta | 8 + .../Runtime/Unity/Settings/GameSettings.cs | 643 ++ .../Unity/Settings/GameSettings.cs.meta | 2 + .../Unity/Settings/GameSettingsRepository.cs | 332 + .../Settings/GameSettingsRepository.cs.meta | 2 + Assets/ProjectQuint/Runtime/Unity/UI.meta | 8 + .../Runtime/Unity/UI/AssemblyInfo.cs | 4 + .../Runtime/Unity/UI/AssemblyInfo.cs.meta | 2 + .../Runtime/Unity/UI/BpmDisplayFormatter.cs | 41 + .../Unity/UI/BpmDisplayFormatter.cs.meta | 2 + .../Unity/UI/ChartFeatureRadarElement.cs | 340 + .../Unity/UI/ChartFeatureRadarElement.cs.meta | 2 + .../Runtime/Unity/UI/CourseClearLampView.cs | 55 + .../Unity/UI/CourseClearLampView.cs.meta | 2 + .../Runtime/Unity/UI/DaylightFloatTween.cs | 91 + .../Unity/UI/DaylightFloatTween.cs.meta | 2 + .../Runtime/Unity/UI/DaylightHeaderView.cs | 114 + .../Unity/UI/DaylightHeaderView.cs.meta | 2 + .../Unity/UI/DaylightMetricDeltaView.cs | 112 + .../Unity/UI/DaylightMetricDeltaView.cs.meta | 2 + .../Runtime/Unity/UI/DaylightModalMotion.cs | 129 + .../Unity/UI/DaylightModalMotion.cs.meta | 2 + .../Unity/UI/DaylightPrismFontPolicy.cs | 101 + .../Unity/UI/DaylightPrismFontPolicy.cs.meta | 2 + .../Unity/UI/DaylightShortcutButtonView.cs | 62 + .../UI/DaylightShortcutButtonView.cs.meta | 2 + .../Unity/UI/DaylightStaggeredReveal.cs | 117 + .../Unity/UI/DaylightStaggeredReveal.cs.meta | 2 + .../Runtime/Unity/UI/DefaultJacketProvider.cs | 37 + .../Unity/UI/DefaultJacketProvider.cs.meta | 2 + .../Runtime/Unity/UI/FpsCounterPresenter.cs | 97 + .../Unity/UI/FpsCounterPresenter.cs.meta | 2 + .../Unity/UI/GameplayGearPreviewPresenter.cs | 247 + .../UI/GameplayGearPreviewPresenter.cs.meta | 2 + .../Runtime/Unity/UI/GameplayHudPresenter.cs | 1014 +++ .../Unity/UI/GameplayHudPresenter.cs.meta | 2 + .../Runtime/Unity/UI/GameplayNoteWindow.cs | 141 + .../Unity/UI/GameplayNoteWindow.cs.meta | 2 + .../Unity/UI/GameplayPlayfieldPresenter.cs | 927 +++ .../UI/GameplayPlayfieldPresenter.cs.meta | 2 + .../Unity/UI/GameplayPresentationSurface.cs | 90 + .../UI/GameplayPresentationSurface.cs.meta | 2 + .../Runtime/Unity/UI/GaugeModeText.cs | 12 + .../Runtime/Unity/UI/GaugeModeText.cs.meta | 2 + .../Runtime/Unity/UI/HomeScreenPresenter.cs | 98 + .../Unity/UI/HomeScreenPresenter.cs.meta | 2 + .../Unity/UI/LibraryRowThumbnailCache.cs | 230 + .../Unity/UI/LibraryRowThumbnailCache.cs.meta | 2 + .../Unity/UI/OptionsProfileCoursePresenter.cs | 2687 ++++++ .../UI/OptionsProfileCoursePresenter.cs.meta | 2 + .../Unity/UI/PackMusicScreenPresenter.cs | 2858 +++++++ .../Unity/UI/PackMusicScreenPresenter.cs.meta | 2 + .../Runtime/Unity/UI/PlayerCapsuleView.cs | 156 + .../Unity/UI/PlayerCapsuleView.cs.meta | 2 + .../Unity/UI/ProjectQuint.Presentation.asmdef | 20 + .../UI/ProjectQuint.Presentation.asmdef.meta | 7 + .../Unity/UI/ProjectQuintPresentation.cs | 405 + .../Unity/UI/ProjectQuintPresentation.cs.meta | 2 + .../Unity/UI/ProjectQuintPresentationTypes.cs | 261 + .../UI/ProjectQuintPresentationTypes.cs.meta | 2 + .../Runtime/Unity/UI/Resources.meta | 8 + .../Unity/UI/Resources/ProjectQuint.meta | 8 + .../Unity/UI/Resources/ProjectQuint/UI.meta | 8 + .../UI/DaylightPrismComponents.uss | 600 ++ .../UI/DaylightPrismComponents.uss.meta | 12 + .../ProjectQuint/UI/DaylightPrismTokens.uss | 156 + .../UI/DaylightPrismTokens.uss.meta | 12 + .../Resources/ProjectQuint/UI/FpsCounter.uxml | 6 + .../ProjectQuint/UI/FpsCounter.uxml.meta | 10 + .../ProjectQuint/UI/FpsCounterStyles.uss | 28 + .../ProjectQuint/UI/FpsCounterStyles.uss.meta | 12 + .../ProjectQuint/UI/GameplayHud.uxml | 83 + .../ProjectQuint/UI/GameplayHud.uxml.meta | 10 + .../ProjectQuint/UI/GameplayHudStyles.uss | 379 + .../UI/GameplayHudStyles.uss.meta | 12 + .../ProjectQuint/UI/HomeDaylightPrism.uxml | 9 + .../UI/HomeDaylightPrism.uxml.meta | 10 + .../UI/HomeDaylightPrismStyles.uss | 43 + .../UI/HomeDaylightPrismStyles.uss.meta | 12 + .../UI/OptionsProfileCourseDaylightPrism.uxml | 427 + ...ptionsProfileCourseDaylightPrism.uxml.meta | 10 + ...ptionsProfileCourseDaylightPrismStyles.uss | 3716 +++++++++ ...sProfileCourseDaylightPrismStyles.uss.meta | 12 + .../UI/PackMusicDaylightPrism.uxml | 529 ++ .../UI/PackMusicDaylightPrism.uxml.meta | 10 + .../UI/PackMusicDaylightPrismStyles.uss | 7030 ++++++++++++++++ .../UI/PackMusicDaylightPrismStyles.uss.meta | 12 + .../ProjectQuint/UI/SessionDaylightPrism.uxml | 389 + .../UI/SessionDaylightPrism.uxml.meta | 10 + .../UI/SessionDaylightPrismStyles.uss | 4527 ++++++++++ .../UI/SessionDaylightPrismStyles.uss.meta | 12 + .../UI/UnityDefaultRuntimeTheme.tss | 1 + .../UI/UnityDefaultRuntimeTheme.tss.meta | 12 + .../ProjectQuint/UI/UpdateNotice.uxml | 33 + .../ProjectQuint/UI/UpdateNotice.uxml.meta | 10 + .../ProjectQuint/UI/UpdateNoticeStyles.uss | 194 + .../UI/UpdateNoticeStyles.uss.meta | 12 + .../Unity/UI/SessionScreensPresenter.cs | 1989 +++++ .../Unity/UI/SessionScreensPresenter.cs.meta | 2 + .../Unity/UI/StartupBrandingPresenter.cs | 171 + .../Unity/UI/StartupBrandingPresenter.cs.meta | 2 + .../Runtime/Unity/UI/UpdateNoticePresenter.cs | 184 + .../Unity/UI/UpdateNoticePresenter.cs.meta | 2 + Assets/ProjectQuint/Runtime/Unity/Video.meta | 8 + .../Runtime/Unity/Video/AssemblyInfo.cs | 5 + .../Runtime/Unity/Video/AssemblyInfo.cs.meta | 2 + .../Unity/Video/BgaVideoTranscodeCache.cs | 319 + .../Video/BgaVideoTranscodeCache.cs.meta | 2 + .../Runtime/Unity/Video/BmsBgaSystem.cs | 741 ++ .../Runtime/Unity/Video/BmsBgaSystem.cs.meta | 2 + .../Video/ProjectQuint.Media.Video.asmdef | 17 + .../ProjectQuint.Media.Video.asmdef.meta | 7 + Assets/ProjectQuint/Tests.meta | 8 + Assets/ProjectQuint/Tests/EditMode.meta | 8 + .../EditMode/AeryIntegrationBoundaryTests.cs | 235 + .../AeryIntegrationBoundaryTests.cs.meta | 2 + .../ApplicationStateControllerTests.cs | 399 + .../ApplicationStateControllerTests.cs.meta | 2 + .../EditMode/ArchitectureBoundaryTests.cs | 231 + .../ArchitectureBoundaryTests.cs.meta | 2 + .../EditMode/AudioDspBufferControllerTests.cs | 155 + .../AudioDspBufferControllerTests.cs.meta | 2 + .../EditMode/AudioSyncCalibrationTests.cs | 80 + .../AudioSyncCalibrationTests.cs.meta | 2 + .../Tests/EditMode/BitmapImageDecoderTests.cs | 142 + .../EditMode/BitmapImageDecoderTests.cs.meta | 2 + .../Tests/EditMode/BmsAudioSystemTests.cs | 1149 +++ .../EditMode/BmsAudioSystemTests.cs.meta | 2 + .../Tests/EditMode/BmsBgaSystemTests.cs | 226 + .../Tests/EditMode/BmsBgaSystemTests.cs.meta | 2 + .../Tests/EditMode/BmsCoreTests.cs | 1451 ++++ .../Tests/EditMode/BmsCoreTests.cs.meta | 2 + .../EditMode/BmsDifficultyTableIndexTests.cs | 144 + .../BmsDifficultyTableIndexTests.cs.meta | 2 + .../Tests/EditMode/BmsPackRegistryTests.cs | 645 ++ .../EditMode/BmsPackRegistryTests.cs.meta | 2 + .../EditMode/ChartFeatureAnalyzerTests.cs | 406 + .../ChartFeatureAnalyzerTests.cs.meta | 2 + .../EditMode/CourseLaneModifierPolicyTests.cs | 36 + .../CourseLaneModifierPolicyTests.cs.meta | 2 + .../Tests/EditMode/DaylightHeaderViewTests.cs | 46 + .../EditMode/DaylightHeaderViewTests.cs.meta | 2 + .../EditMode/DaylightPrismFontPolicyTests.cs | 121 + .../DaylightPrismFontPolicyTests.cs.meta | 2 + .../DaylightShortcutButtonViewTests.cs | 37 + .../DaylightShortcutButtonViewTests.cs.meta | 2 + .../EditMode/DirectionalKeyRepeatTests.cs | 30 + .../DirectionalKeyRepeatTests.cs.meta | 2 + .../Tests/EditMode/GameSettingsTests.cs | 269 + .../Tests/EditMode/GameSettingsTests.cs.meta | 2 + .../EditMode/GameplayPresentationTests.cs | 169 + .../GameplayPresentationTests.cs.meta | 2 + .../GameplaySessionControllerTests.cs | 863 ++ .../GameplaySessionControllerTests.cs.meta | 2 + .../EditMode/HomeScreenPresenterTests.cs | 42 + .../EditMode/HomeScreenPresenterTests.cs.meta | 2 + .../EditMode/LargeKeysoundIntegrationTests.cs | 157 + .../LargeKeysoundIntegrationTests.cs.meta | 2 + .../Tests/EditMode/LibraryControllerTests.cs | 533 ++ .../EditMode/LibraryControllerTests.cs.meta | 2 + .../OptionsProfileCoursePresenterTests.cs | 570 ++ ...OptionsProfileCoursePresenterTests.cs.meta | 2 + .../EditMode/PackMusicScreenPresenterTests.cs | 965 +++ .../PackMusicScreenPresenterTests.cs.meta | 2 + .../Tests/EditMode/PersistenceTests.cs | 960 +++ .../Tests/EditMode/PersistenceTests.cs.meta | 2 + .../Tests/EditMode/PreparedSongTests.cs | 113 + .../Tests/EditMode/PreparedSongTests.cs.meta | 2 + .../Tests/EditMode/ProfileProgressionTests.cs | 741 ++ .../EditMode/ProfileProgressionTests.cs.meta | 2 + .../ProjectQuint.Tests.EditMode.asmdef | 30 + .../ProjectQuint.Tests.EditMode.asmdef.meta | 7 + .../ProjectQuintReleaseVersionTests.cs | 48 + .../ProjectQuintReleaseVersionTests.cs.meta | 11 + .../ProjectQuintUpdateServiceTests.cs | 167 + .../ProjectQuintUpdateServiceTests.cs.meta | 2 + .../EditMode/ReleasePrivacyPolicyTests.cs | 128 + .../ReleasePrivacyPolicyTests.cs.meta | 11 + .../EditMode/SessionScreensPresenterTests.cs | 785 ++ .../SessionScreensPresenterTests.cs.meta | 2 + .../Tests/EditMode/SongLibraryScannerTests.cs | 342 + .../EditMode/SongLibraryScannerTests.cs.meta | 2 + .../Tests/EditMode/SongLibraryViewTests.cs | 1455 ++++ .../EditMode/SongLibraryViewTests.cs.meta | 2 + .../EditMode/SongLoadCoordinatorTests.cs | 192 + .../EditMode/SongLoadCoordinatorTests.cs.meta | 2 + .../EditMode/StandaloneProfileOptionsTests.cs | 98 + .../StandaloneProfileOptionsTests.cs.meta | 2 + .../Tests/EditMode/ThirdPartyNoticeTests.cs | 77 + .../EditMode/ThirdPartyNoticeTests.cs.meta | 11 + .../Tests/EditMode/WaveAudioDecoderTests.cs | 176 + .../EditMode/WaveAudioDecoderTests.cs.meta | 2 + Assets/Resources.meta | 8 + Assets/Resources/ProjectQuint.meta | 8 + Assets/Resources/ProjectQuint/Fonts.meta | 8 + .../Fonts/Pretendard-SemiBold.otf | Bin 0 -> 1583704 bytes .../Fonts/Pretendard-SemiBold.otf.meta | 21 + Assets/Resources/ProjectQuint/Fonts/README.md | 6 + .../ProjectQuint/Fonts/README.md.meta | 7 + .../ProjectQuint/Fonts/Rajdhani-Bold.ttf | Bin 0 -> 400680 bytes .../ProjectQuint/Fonts/Rajdhani-Bold.ttf.meta | 21 + Assets/Resources/ProjectQuint/UI.meta | 8 + .../ProjectQuint/UI/DefaultJacket.png | Bin 0 -> 960977 bytes .../ProjectQuint/UI/DefaultJacket.png.meta | 143 + Assets/Scenes.meta | 8 + Assets/Scenes/SampleScene.unity | 352 + Assets/Scenes/SampleScene.unity.meta | 7 + Assets/Settings.meta | 8 + .../Settings/Lit2DSceneTemplate.scenetemplate | 71 + .../Lit2DSceneTemplate.scenetemplate.meta | 8 + Assets/Settings/Renderer2D.asset | 69 + Assets/Settings/Renderer2D.asset.meta | 8 + Assets/Settings/Scenes.meta | 8 + .../Settings/Scenes/URP2DSceneTemplate.unity | 350 + .../Scenes/URP2DSceneTemplate.unity.meta | 7 + Assets/Settings/UniversalRP.asset | 143 + Assets/Settings/UniversalRP.asset.meta | 8 + ...niversalRenderPipelineGlobalSettings.asset | 440 + ...salRenderPipelineGlobalSettings.asset.meta | 8 + Native/ProjectQuintInput/build-macos.sh | 28 + .../ProjectQuintInput/project_quint_input.cpp | 283 + Native/ProjectQuintUpdater/build-macos.sh | 36 + .../project_quint_updater.mm | 110 + Packages/manifest.json | 58 + Packages/manifest.release.json | 17 + Packages/packages-lock.json | 590 ++ ProjectSettings/AudioManager.asset | 19 + ProjectSettings/ClusterInputManager.asset | 6 + ProjectSettings/DynamicsManager.asset | 36 + ProjectSettings/EditorBuildSettings.asset | 14 + ProjectSettings/EditorSettings.asset | 50 + ProjectSettings/GraphicsSettings.asset | 69 + ProjectSettings/InputManager.asset | 487 ++ ProjectSettings/MemorySettings.asset | 35 + ProjectSettings/MultiplayerManager.asset | 7 + ProjectSettings/NavMeshAreas.asset | 91 + ProjectSettings/NetworkManager.asset | 8 + ProjectSettings/PackageManagerSettings.asset | 40 + .../com.unity.ai.assistant/Settings.json | 11 + ProjectSettings/Physics2DSettings.asset | 56 + ProjectSettings/PresetManager.asset | 7 + ProjectSettings/ProjectSettings.asset | 748 ++ ProjectSettings/ProjectVersion.txt | 2 + ProjectSettings/QualitySettings.asset | 336 + ProjectSettings/ShaderGraphSettings.asset | 19 + ProjectSettings/TagManager.asset | 43 + ProjectSettings/TimeManager.asset | 9 + ProjectSettings/URPProjectSettings.asset | 16 + ProjectSettings/UnityConnectSettings.asset | 40 + ProjectSettings/VFXManager.asset | 12 + ProjectSettings/VersionControlSettings.asset | 7 + ProjectSettings/XRSettings.asset | 10 + README.md | 55 + TestFixtures/Bga/clean-mac-bga.wmv | Bin 0 -> 3925 bytes ThirdParty/FFmpeg/COPYING.LGPLv2.1 | 502 ++ ThirdParty/FFmpeg/NOTICE.txt | 7 + ThirdParty/FFmpeg/README.md | 15 + ThirdParty/FFmpeg/Source/ffmpeg-8.1.2.tar.xz | Bin 0 -> 11710924 bytes ThirdParty/FFmpeg/macOS/ffmpeg | Bin 0 -> 6090872 bytes ThirdParty/Licenses/FMOD-NOTICE.txt | 15 + ThirdParty/Licenses/Pretendard-OFL-1.1.txt | 104 + ThirdParty/Licenses/Rajdhani-OFL-1.1.txt | 93 + ThirdParty/Licenses/Sparkle-MIT.txt | 134 + ThirdParty/Licenses/THIRD-PARTY-NOTICES.txt | 38 + ThirdParty/Sparkle/2.9.4/LICENSE | 134 + ThirdParty/Sparkle/2.9.4/README.md | 18 + .../2.9.4/Sparkle.framework/Autoupdate | 1 + .../Sparkle/2.9.4/Sparkle.framework/Headers | 1 + .../Sparkle/2.9.4/Sparkle.framework/Modules | 1 + .../2.9.4/Sparkle.framework/PrivateHeaders | 1 + .../Sparkle/2.9.4/Sparkle.framework/Resources | 1 + .../Sparkle/2.9.4/Sparkle.framework/Sparkle | 1 + .../2.9.4/Sparkle.framework/Updater.app | 1 + .../Sparkle.framework/Versions/B/Autoupdate | Bin 0 -> 726000 bytes .../SPUAppcastSigningValidationStatus.h | 32 + .../Versions/B/Headers/SPUDownloadData.h | 52 + .../B/Headers/SPUStandardUpdaterController.h | 127 + .../B/Headers/SPUStandardUserDriver.h | 47 + .../B/Headers/SPUStandardUserDriverDelegate.h | 209 + .../Versions/B/Headers/SPUUpdateCheck.h | 33 + .../B/Headers/SPUUpdatePermissionRequest.h | 42 + .../Versions/B/Headers/SPUUpdater.h | 403 + .../Versions/B/Headers/SPUUpdaterDelegate.h | 485 ++ .../Versions/B/Headers/SPUUpdaterSettings.h | 98 + .../Versions/B/Headers/SPUUserDriver.h | 304 + .../Versions/B/Headers/SPUUserUpdateState.h | 85 + .../Versions/B/Headers/SUAppcast.h | 54 + .../Versions/B/Headers/SUAppcastItem.h | 448 + .../Versions/B/Headers/SUErrors.h | 111 + .../Versions/B/Headers/SUExport.h | 18 + .../B/Headers/SUStandardVersionComparator.h | 63 + .../B/Headers/SUUpdatePermissionResponse.h | 73 + .../Versions/B/Headers/SUUpdater.h | 212 + .../Versions/B/Headers/SUUpdaterDelegate.h | 363 + .../B/Headers/SUVersionComparisonProtocol.h | 42 + .../B/Headers/SUVersionDisplayProtocol.h | 78 + .../Versions/B/Headers/Sparkle.h | 39 + .../Versions/B/Modules/module.modulemap | 6 + .../B/Modules/module.private.modulemap | 38 + .../SPUAppcastItemStateResolver.h | 38 + .../SPUGentleUserDriverReminders.h | 22 + .../B/PrivateHeaders/SPUInstallationType.h | 19 + .../SPUStandardUserDriver+Private.h | 40 + .../B/PrivateHeaders/SPUUserAgent+Private.h | 29 + .../B/PrivateHeaders/SUAppcastItem+Private.h | 50 + .../SUInstallerLauncher+Private.h | 37 + .../B/Resources/Base.lproj/Sparkle.strings | Bin 0 -> 6249 bytes .../Versions/B/Resources/Info.plist | 48 + .../B/Resources/ReleaseNotesColorStyle.css | 13 + .../Versions/B/Resources/SUStatus.nib | Bin 0 -> 13016 bytes .../Versions/B/Resources/SUUpdateAlert.nib | Bin 0 -> 18490 bytes .../keyedobjects-101300.nib | Bin 0 -> 25424 bytes .../keyedobjects-110000.nib | Bin 0 -> 25390 bytes .../B/Resources/ar.lproj/Sparkle.strings | Bin 0 -> 3554 bytes .../B/Resources/ca.lproj/Sparkle.strings | Bin 0 -> 3501 bytes .../B/Resources/cs.lproj/Sparkle.strings | Bin 0 -> 10774 bytes .../B/Resources/da.lproj/Sparkle.strings | Bin 0 -> 4882 bytes .../B/Resources/de.lproj/Sparkle.strings | Bin 0 -> 11559 bytes .../B/Resources/el.lproj/Sparkle.strings | Bin 0 -> 5934 bytes .../B/Resources/es.lproj/Sparkle.strings | Bin 0 -> 7026 bytes .../B/Resources/fa.lproj/Sparkle.strings | Bin 0 -> 5716 bytes .../B/Resources/fi.lproj/Sparkle.strings | Bin 0 -> 3252 bytes .../B/Resources/fr.lproj/Sparkle.strings | Bin 0 -> 7855 bytes .../B/Resources/he.lproj/Sparkle.strings | Bin 0 -> 9657 bytes .../B/Resources/hr.lproj/Sparkle.strings | Bin 0 -> 6367 bytes .../B/Resources/hu.lproj/Sparkle.strings | Bin 0 -> 4449 bytes .../B/Resources/is.lproj/Sparkle.strings | Bin 0 -> 3954 bytes .../B/Resources/it.lproj/Sparkle.strings | Bin 0 -> 10956 bytes .../B/Resources/ja.lproj/Sparkle.strings | Bin 0 -> 8697 bytes .../B/Resources/ko.lproj/Sparkle.strings | Bin 0 -> 7797 bytes .../B/Resources/nb.lproj/Sparkle.strings | Bin 0 -> 5539 bytes .../B/Resources/nl.lproj/Sparkle.strings | Bin 0 -> 9574 bytes .../B/Resources/nn.lproj/Sparkle.strings | Bin 0 -> 5671 bytes .../B/Resources/pl.lproj/Sparkle.strings | Bin 0 -> 5453 bytes .../B/Resources/pt-BR.lproj/Sparkle.strings | Bin 0 -> 9976 bytes .../B/Resources/pt-PT.lproj/Sparkle.strings | Bin 0 -> 5984 bytes .../B/Resources/ro.lproj/Sparkle.strings | Bin 0 -> 6115 bytes .../B/Resources/ru.lproj/Sparkle.strings | Bin 0 -> 5784 bytes .../B/Resources/sk.lproj/Sparkle.strings | Bin 0 -> 5675 bytes .../B/Resources/sl.lproj/Sparkle.strings | Bin 0 -> 5646 bytes .../B/Resources/sv.lproj/Sparkle.strings | Bin 0 -> 5160 bytes .../B/Resources/th.lproj/Sparkle.strings | Bin 0 -> 5339 bytes .../B/Resources/tr.lproj/Sparkle.strings | Bin 0 -> 6183 bytes .../B/Resources/uk.lproj/Sparkle.strings | Bin 0 -> 5842 bytes .../B/Resources/vi.lproj/Sparkle.strings | Bin 0 -> 12418 bytes .../B/Resources/zh_CN.lproj/Sparkle.strings | Bin 0 -> 7475 bytes .../B/Resources/zh_HK.lproj/Sparkle.strings | Bin 0 -> 6469 bytes .../B/Resources/zh_TW.lproj/Sparkle.strings | Bin 0 -> 7625 bytes .../Sparkle.framework/Versions/B/Sparkle | Bin 0 -> 977616 bytes .../B/Updater.app/Contents/Info.plist | 93 + .../B/Updater.app/Contents/MacOS/Updater | Bin 0 -> 290560 bytes .../Versions/B/Updater.app/Contents/PkgInfo | 1 + .../Contents/Resources/SUStatus.nib | Bin 0 -> 13016 bytes .../Contents/_CodeSignature/CodeResources | 128 + .../Downloader.xpc/Contents/Info.plist | 62 + .../Downloader.xpc/Contents/MacOS/Downloader | Bin 0 -> 188672 bytes .../Contents/_CodeSignature/CodeResources | 115 + .../Installer.xpc/Contents/Info.plist | 57 + .../Installer.xpc/Contents/MacOS/Installer | Bin 0 -> 223472 bytes .../Contents/_CodeSignature/CodeResources | 115 + .../Versions/B/_CodeSignature/CodeResources | 1084 +++ .../2.9.4/Sparkle.framework/Versions/Current | 1 + .../2.9.4/Sparkle.framework/XPCServices | 1 + ThirdParty/Sparkle/2.9.4/bin/BinaryDelta | Bin 0 -> 1442048 bytes ThirdParty/Sparkle/2.9.4/bin/generate_appcast | Bin 0 -> 2159968 bytes ThirdParty/Sparkle/2.9.4/bin/generate_keys | Bin 0 -> 1308528 bytes ThirdParty/Sparkle/2.9.4/bin/sign_update | Bin 0 -> 1378608 bytes Tools/RELEASE.md | 88 + Tools/build-ffmpeg-macos.sh | 96 + Tools/build-mac-stable-release.sh | 116 + Tools/release-mac-alpha.sh | 5 + Tools/release_configuration.py | 99 + Tools/release_mac_alpha.py | 980 +++ Tools/tests/test_release_configuration.py | 63 + Tools/tests/test_release_mac_alpha.py | 154 + Website/projectquint-site/.gitignore | 4 + Website/projectquint-site/README.md | 37 + Website/projectquint-site/astro.config.mjs | 15 + Website/projectquint-site/ops/deploy-oci.sh | 45 + .../ops/nginx/projectquint.conf | 97 + Website/projectquint-site/package-lock.json | 5263 ++++++++++++ Website/projectquint-site/package.json | 19 + .../public/apple-touch-icon.png | Bin 0 -> 12116 bytes Website/projectquint-site/public/favicon.png | Bin 0 -> 2330 bytes .../public/fonts/Pretendard-SemiBold.otf | Bin 0 -> 1583704 bytes .../public/fonts/Rajdhani-Bold.ttf | Bin 0 -> 400680 bytes .../public/licenses/Pretendard-OFL-1.1.txt | 104 + .../public/licenses/Rajdhani-OFL-1.1.txt | 93 + Website/projectquint-site/public/robots.txt | 4 + Website/projectquint-site/public/sitemap.xml | 9 + .../public/updates/latest.json | 12 + .../src/components/SiteFooter.astro | 17 + .../src/components/SiteHeader.astro | 34 + .../src/layouts/BaseLayout.astro | 46 + .../src/pages/changelog.astro | 87 + .../src/pages/download.astro | 80 + .../projectquint-site/src/pages/guide.astro | 59 + .../projectquint-site/src/pages/index.astro | 97 + .../projectquint-site/src/pages/privacy.astro | 39 + .../src/pages/resources.astro | 94 + .../projectquint-site/src/styles/global.css | 1155 +++ Website/projectquint-site/tsconfig.json | 3 + docs/profile-progression-design.md | 482 ++ 676 files changed, 106118 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 AGENTS.md create mode 100644 Assets/DefaultVolumeProfile.asset create mode 100644 Assets/DefaultVolumeProfile.asset.meta create mode 100644 Assets/InputSystem_Actions.inputactions create mode 100644 Assets/InputSystem_Actions.inputactions.meta create mode 100644 Assets/Plugins.meta create mode 100644 Assets/Plugins/FMOD.meta create mode 100644 Assets/Plugins/FMOD/Cache.meta create mode 100644 Assets/Plugins/FMOD/Cache/Editor.meta create mode 100644 Assets/Plugins/FMOD/Cache/Editor/FMODStudioCache.asset create mode 100644 Assets/Plugins/FMOD/Cache/Editor/FMODStudioCache.asset.meta create mode 100644 Assets/Plugins/FMOD/Resources.meta create mode 100644 Assets/Plugins/FMOD/Resources/FMODStudioSettings.asset create mode 100644 Assets/Plugins/FMOD/Resources/FMODStudioSettings.asset.meta create mode 100644 Assets/ProjectQuint.meta create mode 100644 Assets/ProjectQuint/Art.meta create mode 100644 Assets/ProjectQuint/Art/AppIcon.meta create mode 100644 Assets/ProjectQuint/Art/AppIcon/ProjectQuintAppIcon.png create mode 100644 Assets/ProjectQuint/Art/AppIcon/ProjectQuintAppIcon.png.meta create mode 100644 Assets/ProjectQuint/Editor.meta create mode 100644 Assets/ProjectQuint/Editor/ChartFeatureCalibrationReport.cs create mode 100644 Assets/ProjectQuint/Editor/ChartFeatureCalibrationReport.cs.meta create mode 100644 Assets/ProjectQuint/Editor/ProjectQuintBuild.cs create mode 100644 Assets/ProjectQuint/Editor/ProjectQuintBuild.cs.meta create mode 100644 Assets/ProjectQuint/Editor/ProjectQuintIconConfigurator.cs create mode 100644 Assets/ProjectQuint/Editor/ProjectQuintIconConfigurator.cs.meta create mode 100644 Assets/ProjectQuint/Editor/ProjectQuintVisualQa.cs create mode 100644 Assets/ProjectQuint/Editor/ProjectQuintVisualQa.cs.meta create mode 100644 Assets/ProjectQuint/Plugins.meta create mode 100644 Assets/ProjectQuint/Plugins/macOS.meta create mode 100644 Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle.meta create mode 100644 Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/Info.plist create mode 100755 Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/MacOS/ProjectQuintInput create mode 100644 Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/_CodeSignature/CodeResources create mode 100644 Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle.meta create mode 100644 Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/Info.plist create mode 100755 Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/MacOS/ProjectQuintUpdater create mode 100644 Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/_CodeSignature/CodeResources create mode 100644 Assets/ProjectQuint/Runtime.meta create mode 100644 Assets/ProjectQuint/Runtime/Core.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Analysis.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Analysis/ChartFeatureAnalysis.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Analysis/ChartFeatureAnalysis.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Bms.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Bms/BmsModels.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Bms/BmsModels.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Bms/BmsParser.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Bms/BmsParser.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Bms/BmsPlayableChartConverter.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Bms/BmsPlayableChartConverter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Bms/BmsTimelineCompiler.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Bms/BmsTimelineCompiler.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Charts.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Charts/PlayableChart.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Charts/PlayableChart.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Course.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Course/CourseLaneModifierPolicy.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Course/CourseLaneModifierPolicy.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Course/CourseProgress.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Course/CourseProgress.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Gauge.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Gauge/GaugeFallbackChain.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Gauge/GaugeFallbackChain.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Gauge/PlayGauge.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Gauge/PlayGauge.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Identity.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Identity/ChartId.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Identity/ChartId.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Judgement.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Judgement/LongNotePolicies.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Judgement/LongNotePolicies.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Judgement/RhythmJudgementEngine.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Judgement/RhythmJudgementEngine.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Progression.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Progression/ProfileCollection.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Progression/ProfileCollection.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Progression/ProfileProgression.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Progression/ProfileProgression.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/ProjectQuint.Core.asmdef create mode 100644 Assets/ProjectQuint/Runtime/Core/ProjectQuint.Core.asmdef.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Release.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Release/ProjectQuintReleaseVersion.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Release/ProjectQuintReleaseVersion.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Timing.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncCalibrationEstimator.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncCalibrationEstimator.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncTapMatcher.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncTapMatcher.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Timing/MusicalTimingMaps.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Timing/MusicalTimingMaps.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Core/Timing/ResilientSongClock.cs create mode 100644 Assets/ProjectQuint/Runtime/Core/Timing/ResilientSongClock.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/AssemblyInfo.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/AssemblyInfo.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/AssemblyInfo.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/AssemblyInfo.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/AudioAssetStore.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/AudioAssetStore.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/AudioBackendContracts.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/AudioBackendContracts.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/AudioDspBufferController.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/AudioDspBufferController.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/BmsAudioSystem.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/BmsAudioSystem.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/FmodAudioSyncMetronome.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/FmodAudioSyncMetronome.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreAudioBackend.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreAudioBackend.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreReactivePlayback.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreReactivePlayback.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongCache.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongCache.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongRenderer.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongRenderer.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/ProjectQuint.Media.Audio.asmdef create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/ProjectQuint.Media.Audio.asmdef.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/SmoothAudioMixer.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/SmoothAudioMixer.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/SongAudioPlaybackEngine.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/SongAudioPlaybackEngine.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/WaveAudioDecoder.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Audio/WaveAudioDecoder.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/AssemblyInfo.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/AssemblyInfo.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/PinnedPackPremixCacheService.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/PinnedPackPremixCacheService.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuint.Bootstrap.asmdef create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuint.Bootstrap.asmdef.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintApp.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintApp.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintSparkleUpdater.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintSparkleUpdater.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintUpdateService.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintUpdateService.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/StandaloneProfileOptions.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Bootstrap/StandaloneProfileOptions.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/AssemblyInfo.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/AssemblyInfo.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Course.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Course/CourseSessionController.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Course/CourseSessionController.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Flow.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Flow/AppFlowController.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Flow/AppFlowController.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Flow/DirectionalKeyRepeat.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Flow/DirectionalKeyRepeat.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Flow/StartupAttributionTimeline.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Flow/StartupAttributionTimeline.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplayFramePacingMonitor.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplayFramePacingMonitor.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplaySessionController.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplaySessionController.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/HighRateInputMonitor.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/HighRateInputMonitor.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/LaneKeySoundResolver.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/LaneKeySoundResolver.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/MacNativeKeyboardPoller.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/MacNativeKeyboardPoller.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Library.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryController.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryController.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryState.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryState.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Loading.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Loading/PreparedSong.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Loading/PreparedSong.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Loading/SongLoadCoordinator.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Loading/SongLoadCoordinator.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/ProjectQuint.Application.asmdef create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/ProjectQuint.Application.asmdef.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Results.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Results/ResultSessionController.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Results/ResultSessionController.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Settings.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Settings/AudioSyncCalibrationSession.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Features/Settings/AudioSyncCalibrationSession.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Infrastructure.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/AssemblyInfo.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/AssemblyInfo.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/BmsImageLoader.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/BmsImageLoader.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/ProjectQuint.Media.Images.asmdef create mode 100644 Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/ProjectQuint.Media.Images.asmdef.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseIndex.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseIndex.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseRecordStore.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseRecordStore.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/AeryTableIndex.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/AeryTableIndex.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BitmapImageDecoder.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BitmapImageDecoder.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsCourseRules.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsCourseRules.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableIndex.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableIndex.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableRegistry.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableRegistry.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsLibraryLocator.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsLibraryLocator.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsPackImportService.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsPackImportService.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsPackRegistry.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsPackRegistry.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsResourceResolver.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/BmsResourceResolver.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/ChartCatalog.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/ChartCatalog.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/ExternalJsonDataSource.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/ExternalJsonDataSource.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/JapaneseSearchNormalizer.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/JapaneseSearchNormalizer.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongClearLampStore.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongClearLampStore.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryScanner.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryScanner.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryView.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryView.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongPlayRecordStore.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongPlayRecordStore.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongSearchIndex.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Library/SongSearchIndex.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/AtomicJsonFile.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/AtomicJsonFile.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ChartIdentityResolver.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ChartIdentityResolver.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/CourseRecordRepository.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/CourseRecordRepository.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/FavoriteChartRepository.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/FavoriteChartRepository.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/PersistencePaths.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/PersistencePaths.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultCommitCoordinator.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultCommitCoordinator.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultEvent.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultEvent.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/PlayerProgressionRepository.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/PlayerProgressionRepository.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ProductIdentityStorageMigrator.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ProductIdentityStorageMigrator.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileArchiveService.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileArchiveService.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileImageStore.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileImageStore.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileStorageMigrator.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileStorageMigrator.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/SongRecordRepository.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Persistence/SongRecordRepository.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/ProjectQuint.Infrastructure.asmdef create mode 100644 Assets/ProjectQuint/Runtime/Unity/ProjectQuint.Infrastructure.asmdef.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/FMODLogoWhite.png create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/FMODLogoWhite.png.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/TeamDeniaMark.png create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/TeamDeniaMark.png.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Data.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Data/AeryHeader.json create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Data/AeryHeader.json.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Data/SearchAliases.json create mode 100644 Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Data/SearchAliases.json.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Settings.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Settings/GameSettings.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Settings/GameSettings.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Settings/GameSettingsRepository.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Settings/GameSettingsRepository.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/AssemblyInfo.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/AssemblyInfo.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/BpmDisplayFormatter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/BpmDisplayFormatter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/ChartFeatureRadarElement.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/ChartFeatureRadarElement.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/CourseClearLampView.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/CourseClearLampView.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightFloatTween.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightFloatTween.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightHeaderView.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightHeaderView.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightMetricDeltaView.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightMetricDeltaView.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightModalMotion.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightModalMotion.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightPrismFontPolicy.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightPrismFontPolicy.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightShortcutButtonView.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightShortcutButtonView.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightStaggeredReveal.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DaylightStaggeredReveal.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DefaultJacketProvider.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/DefaultJacketProvider.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/FpsCounterPresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/FpsCounterPresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayGearPreviewPresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayGearPreviewPresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayHudPresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayHudPresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayNoteWindow.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayNoteWindow.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayPlayfieldPresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayPlayfieldPresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayPresentationSurface.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GameplayPresentationSurface.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GaugeModeText.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/GaugeModeText.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/HomeScreenPresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/HomeScreenPresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/LibraryRowThumbnailCache.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/LibraryRowThumbnailCache.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/OptionsProfileCoursePresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/OptionsProfileCoursePresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/PackMusicScreenPresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/PackMusicScreenPresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/PlayerCapsuleView.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/PlayerCapsuleView.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/ProjectQuint.Presentation.asmdef create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/ProjectQuint.Presentation.asmdef.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/ProjectQuintPresentation.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/ProjectQuintPresentation.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/ProjectQuintPresentationTypes.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/ProjectQuintPresentationTypes.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/DaylightPrismComponents.uss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/DaylightPrismComponents.uss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/DaylightPrismTokens.uss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/DaylightPrismTokens.uss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/FpsCounter.uxml create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/FpsCounter.uxml.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/FpsCounterStyles.uss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/FpsCounterStyles.uss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/GameplayHud.uxml create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/GameplayHud.uxml.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/GameplayHudStyles.uss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/GameplayHudStyles.uss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/HomeDaylightPrism.uxml create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/HomeDaylightPrism.uxml.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/HomeDaylightPrismStyles.uss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/HomeDaylightPrismStyles.uss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/OptionsProfileCourseDaylightPrism.uxml create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/OptionsProfileCourseDaylightPrism.uxml.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/OptionsProfileCourseDaylightPrismStyles.uss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/OptionsProfileCourseDaylightPrismStyles.uss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/PackMusicDaylightPrism.uxml create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/PackMusicDaylightPrism.uxml.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/PackMusicDaylightPrismStyles.uss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/PackMusicDaylightPrismStyles.uss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/SessionDaylightPrism.uxml create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/SessionDaylightPrism.uxml.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/SessionDaylightPrismStyles.uss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/SessionDaylightPrismStyles.uss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/UnityDefaultRuntimeTheme.tss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/UnityDefaultRuntimeTheme.tss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/UpdateNotice.uxml create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/UpdateNotice.uxml.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/UpdateNoticeStyles.uss create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/Resources/ProjectQuint/UI/UpdateNoticeStyles.uss.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/SessionScreensPresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/SessionScreensPresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/StartupBrandingPresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/StartupBrandingPresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/UpdateNoticePresenter.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/UI/UpdateNoticePresenter.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Video.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Video/AssemblyInfo.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Video/AssemblyInfo.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Video/BgaVideoTranscodeCache.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Video/BgaVideoTranscodeCache.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Video/BmsBgaSystem.cs create mode 100644 Assets/ProjectQuint/Runtime/Unity/Video/BmsBgaSystem.cs.meta create mode 100644 Assets/ProjectQuint/Runtime/Unity/Video/ProjectQuint.Media.Video.asmdef create mode 100644 Assets/ProjectQuint/Runtime/Unity/Video/ProjectQuint.Media.Video.asmdef.meta create mode 100644 Assets/ProjectQuint/Tests.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/AeryIntegrationBoundaryTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/AeryIntegrationBoundaryTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/ApplicationStateControllerTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/ApplicationStateControllerTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/ArchitectureBoundaryTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/ArchitectureBoundaryTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/AudioDspBufferControllerTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/AudioDspBufferControllerTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/AudioSyncCalibrationTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/AudioSyncCalibrationTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/BitmapImageDecoderTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/BitmapImageDecoderTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsAudioSystemTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsAudioSystemTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsBgaSystemTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsBgaSystemTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsCoreTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsCoreTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsDifficultyTableIndexTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsDifficultyTableIndexTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsPackRegistryTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/BmsPackRegistryTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/ChartFeatureAnalyzerTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/ChartFeatureAnalyzerTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/CourseLaneModifierPolicyTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/CourseLaneModifierPolicyTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/DaylightHeaderViewTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/DaylightHeaderViewTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/DaylightPrismFontPolicyTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/DaylightPrismFontPolicyTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/DaylightShortcutButtonViewTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/DaylightShortcutButtonViewTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/DirectionalKeyRepeatTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/DirectionalKeyRepeatTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/GameSettingsTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/GameSettingsTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/GameplayPresentationTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/GameplayPresentationTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/GameplaySessionControllerTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/GameplaySessionControllerTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/HomeScreenPresenterTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/HomeScreenPresenterTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/LargeKeysoundIntegrationTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/LargeKeysoundIntegrationTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/LibraryControllerTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/LibraryControllerTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/OptionsProfileCoursePresenterTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/OptionsProfileCoursePresenterTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/PackMusicScreenPresenterTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/PackMusicScreenPresenterTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/PersistenceTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/PersistenceTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/PreparedSongTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/PreparedSongTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/ProfileProgressionTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/ProfileProgressionTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/ProjectQuint.Tests.EditMode.asmdef create mode 100644 Assets/ProjectQuint/Tests/EditMode/ProjectQuint.Tests.EditMode.asmdef.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/ProjectQuintReleaseVersionTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/ProjectQuintReleaseVersionTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/ProjectQuintUpdateServiceTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/ProjectQuintUpdateServiceTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/ReleasePrivacyPolicyTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/ReleasePrivacyPolicyTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/SessionScreensPresenterTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/SessionScreensPresenterTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/SongLibraryScannerTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/SongLibraryScannerTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/SongLibraryViewTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/SongLibraryViewTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/SongLoadCoordinatorTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/SongLoadCoordinatorTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/StandaloneProfileOptionsTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/StandaloneProfileOptionsTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/ThirdPartyNoticeTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/ThirdPartyNoticeTests.cs.meta create mode 100644 Assets/ProjectQuint/Tests/EditMode/WaveAudioDecoderTests.cs create mode 100644 Assets/ProjectQuint/Tests/EditMode/WaveAudioDecoderTests.cs.meta create mode 100644 Assets/Resources.meta create mode 100644 Assets/Resources/ProjectQuint.meta create mode 100644 Assets/Resources/ProjectQuint/Fonts.meta create mode 100644 Assets/Resources/ProjectQuint/Fonts/Pretendard-SemiBold.otf create mode 100644 Assets/Resources/ProjectQuint/Fonts/Pretendard-SemiBold.otf.meta create mode 100644 Assets/Resources/ProjectQuint/Fonts/README.md create mode 100644 Assets/Resources/ProjectQuint/Fonts/README.md.meta create mode 100644 Assets/Resources/ProjectQuint/Fonts/Rajdhani-Bold.ttf create mode 100644 Assets/Resources/ProjectQuint/Fonts/Rajdhani-Bold.ttf.meta create mode 100644 Assets/Resources/ProjectQuint/UI.meta create mode 100644 Assets/Resources/ProjectQuint/UI/DefaultJacket.png create mode 100644 Assets/Resources/ProjectQuint/UI/DefaultJacket.png.meta create mode 100644 Assets/Scenes.meta create mode 100644 Assets/Scenes/SampleScene.unity create mode 100644 Assets/Scenes/SampleScene.unity.meta create mode 100644 Assets/Settings.meta create mode 100644 Assets/Settings/Lit2DSceneTemplate.scenetemplate create mode 100644 Assets/Settings/Lit2DSceneTemplate.scenetemplate.meta create mode 100644 Assets/Settings/Renderer2D.asset create mode 100644 Assets/Settings/Renderer2D.asset.meta create mode 100644 Assets/Settings/Scenes.meta create mode 100644 Assets/Settings/Scenes/URP2DSceneTemplate.unity create mode 100644 Assets/Settings/Scenes/URP2DSceneTemplate.unity.meta create mode 100644 Assets/Settings/UniversalRP.asset create mode 100644 Assets/Settings/UniversalRP.asset.meta create mode 100644 Assets/UniversalRenderPipelineGlobalSettings.asset create mode 100644 Assets/UniversalRenderPipelineGlobalSettings.asset.meta create mode 100755 Native/ProjectQuintInput/build-macos.sh create mode 100644 Native/ProjectQuintInput/project_quint_input.cpp create mode 100755 Native/ProjectQuintUpdater/build-macos.sh create mode 100644 Native/ProjectQuintUpdater/project_quint_updater.mm create mode 100644 Packages/manifest.json create mode 100644 Packages/manifest.release.json create mode 100644 Packages/packages-lock.json create mode 100644 ProjectSettings/AudioManager.asset create mode 100644 ProjectSettings/ClusterInputManager.asset create mode 100644 ProjectSettings/DynamicsManager.asset create mode 100644 ProjectSettings/EditorBuildSettings.asset create mode 100644 ProjectSettings/EditorSettings.asset create mode 100644 ProjectSettings/GraphicsSettings.asset create mode 100644 ProjectSettings/InputManager.asset create mode 100644 ProjectSettings/MemorySettings.asset create mode 100644 ProjectSettings/MultiplayerManager.asset create mode 100644 ProjectSettings/NavMeshAreas.asset create mode 100644 ProjectSettings/NetworkManager.asset create mode 100644 ProjectSettings/PackageManagerSettings.asset create mode 100644 ProjectSettings/Packages/com.unity.ai.assistant/Settings.json create mode 100644 ProjectSettings/Physics2DSettings.asset create mode 100644 ProjectSettings/PresetManager.asset create mode 100644 ProjectSettings/ProjectSettings.asset create mode 100644 ProjectSettings/ProjectVersion.txt create mode 100644 ProjectSettings/QualitySettings.asset create mode 100644 ProjectSettings/ShaderGraphSettings.asset create mode 100644 ProjectSettings/TagManager.asset create mode 100644 ProjectSettings/TimeManager.asset create mode 100644 ProjectSettings/URPProjectSettings.asset create mode 100644 ProjectSettings/UnityConnectSettings.asset create mode 100644 ProjectSettings/VFXManager.asset create mode 100644 ProjectSettings/VersionControlSettings.asset create mode 100644 ProjectSettings/XRSettings.asset create mode 100644 README.md create mode 100644 TestFixtures/Bga/clean-mac-bga.wmv create mode 100644 ThirdParty/FFmpeg/COPYING.LGPLv2.1 create mode 100644 ThirdParty/FFmpeg/NOTICE.txt create mode 100644 ThirdParty/FFmpeg/README.md create mode 100644 ThirdParty/FFmpeg/Source/ffmpeg-8.1.2.tar.xz create mode 100755 ThirdParty/FFmpeg/macOS/ffmpeg create mode 100644 ThirdParty/Licenses/FMOD-NOTICE.txt create mode 100644 ThirdParty/Licenses/Pretendard-OFL-1.1.txt create mode 100644 ThirdParty/Licenses/Rajdhani-OFL-1.1.txt create mode 100644 ThirdParty/Licenses/Sparkle-MIT.txt create mode 100644 ThirdParty/Licenses/THIRD-PARTY-NOTICES.txt create mode 100644 ThirdParty/Sparkle/2.9.4/LICENSE create mode 100644 ThirdParty/Sparkle/2.9.4/README.md create mode 120000 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Autoupdate create mode 120000 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Headers create mode 120000 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Modules create mode 120000 ThirdParty/Sparkle/2.9.4/Sparkle.framework/PrivateHeaders create mode 120000 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Resources create mode 120000 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Sparkle create mode 120000 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Updater.app create mode 100755 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Autoupdate create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUAppcastSigningValidationStatus.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUDownloadData.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUStandardUpdaterController.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUStandardUserDriver.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUStandardUserDriverDelegate.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUUpdateCheck.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUUpdatePermissionRequest.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUUpdater.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUUpdaterDelegate.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUUpdaterSettings.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUUserDriver.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SPUUserUpdateState.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUAppcast.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUAppcastItem.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUErrors.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUExport.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUStandardVersionComparator.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUUpdatePermissionResponse.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUUpdater.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUUpdaterDelegate.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUVersionComparisonProtocol.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/SUVersionDisplayProtocol.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Headers/Sparkle.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Modules/module.modulemap create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Modules/module.private.modulemap create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/PrivateHeaders/SPUAppcastItemStateResolver.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/PrivateHeaders/SPUGentleUserDriverReminders.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/PrivateHeaders/SPUInstallationType.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/PrivateHeaders/SPUStandardUserDriver+Private.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/PrivateHeaders/SPUUserAgent+Private.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/PrivateHeaders/SUAppcastItem+Private.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/PrivateHeaders/SUInstallerLauncher+Private.h create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/Base.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/Info.plist create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/ReleaseNotesColorStyle.css create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/SUStatus.nib create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/SUUpdateAlert.nib create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/SUUpdatePermissionPrompt.nib/keyedobjects-101300.nib create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/SUUpdatePermissionPrompt.nib/keyedobjects-110000.nib create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/ar.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/ca.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/cs.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/da.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/de.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/el.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/es.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/fa.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/fi.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/fr.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/he.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/hr.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/hu.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/is.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/it.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/ja.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/ko.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/nb.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/nl.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/nn.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/pl.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/pt-BR.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/pt-PT.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/ro.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/ru.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/sk.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/sl.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/sv.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/th.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/tr.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/uk.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/vi.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/zh_CN.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/zh_HK.lproj/Sparkle.strings create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Resources/zh_TW.lproj/Sparkle.strings create mode 100755 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Sparkle create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Updater.app/Contents/Info.plist create mode 100755 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Updater.app/Contents/MacOS/Updater create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Updater.app/Contents/PkgInfo create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Updater.app/Contents/Resources/SUStatus.nib create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/Updater.app/Contents/_CodeSignature/CodeResources create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc/Contents/Info.plist create mode 100755 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc/Contents/MacOS/Downloader create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc/Contents/_CodeSignature/CodeResources create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/XPCServices/Installer.xpc/Contents/Info.plist create mode 100755 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/XPCServices/Installer.xpc/Contents/MacOS/Installer create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/XPCServices/Installer.xpc/Contents/_CodeSignature/CodeResources create mode 100644 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/B/_CodeSignature/CodeResources create mode 120000 ThirdParty/Sparkle/2.9.4/Sparkle.framework/Versions/Current create mode 120000 ThirdParty/Sparkle/2.9.4/Sparkle.framework/XPCServices create mode 100755 ThirdParty/Sparkle/2.9.4/bin/BinaryDelta create mode 100755 ThirdParty/Sparkle/2.9.4/bin/generate_appcast create mode 100755 ThirdParty/Sparkle/2.9.4/bin/generate_keys create mode 100755 ThirdParty/Sparkle/2.9.4/bin/sign_update create mode 100644 Tools/RELEASE.md create mode 100755 Tools/build-ffmpeg-macos.sh create mode 100755 Tools/build-mac-stable-release.sh create mode 100755 Tools/release-mac-alpha.sh create mode 100644 Tools/release_configuration.py create mode 100755 Tools/release_mac_alpha.py create mode 100644 Tools/tests/test_release_configuration.py create mode 100644 Tools/tests/test_release_mac_alpha.py create mode 100644 Website/projectquint-site/.gitignore create mode 100644 Website/projectquint-site/README.md create mode 100644 Website/projectquint-site/astro.config.mjs create mode 100755 Website/projectquint-site/ops/deploy-oci.sh create mode 100644 Website/projectquint-site/ops/nginx/projectquint.conf create mode 100644 Website/projectquint-site/package-lock.json create mode 100644 Website/projectquint-site/package.json create mode 100644 Website/projectquint-site/public/apple-touch-icon.png create mode 100644 Website/projectquint-site/public/favicon.png create mode 100644 Website/projectquint-site/public/fonts/Pretendard-SemiBold.otf create mode 100644 Website/projectquint-site/public/fonts/Rajdhani-Bold.ttf create mode 100644 Website/projectquint-site/public/licenses/Pretendard-OFL-1.1.txt create mode 100644 Website/projectquint-site/public/licenses/Rajdhani-OFL-1.1.txt create mode 100644 Website/projectquint-site/public/robots.txt create mode 100644 Website/projectquint-site/public/sitemap.xml create mode 100644 Website/projectquint-site/public/updates/latest.json create mode 100644 Website/projectquint-site/src/components/SiteFooter.astro create mode 100644 Website/projectquint-site/src/components/SiteHeader.astro create mode 100644 Website/projectquint-site/src/layouts/BaseLayout.astro create mode 100644 Website/projectquint-site/src/pages/changelog.astro create mode 100644 Website/projectquint-site/src/pages/download.astro create mode 100644 Website/projectquint-site/src/pages/guide.astro create mode 100644 Website/projectquint-site/src/pages/index.astro create mode 100644 Website/projectquint-site/src/pages/privacy.astro create mode 100644 Website/projectquint-site/src/pages/resources.astro create mode 100644 Website/projectquint-site/src/styles/global.css create mode 100644 Website/projectquint-site/tsconfig.json create mode 100644 docs/profile-progression-design.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68c1cbe --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ +/[Mm]emoryCaptures/ +/[Rr]ecordings/ + +.DS_Store + +*.csproj +*.sln +*.slnx +*.user +*.userprefs + +# Local validation output, caches, and working notes. +/_workspace/ +/tmp/ +/TestResults/ +/mono_crash.* +/plan.md +/.release.local.json +.env +.env.local +__pycache__/ +*.py[cod] + +# User-supplied song libraries are never part of the source distribution. +*.[bB][mM][sS] +*.[bB][mM][eE] +*.[bB][mM][lL] +*.[pP][mM][sS] +*.[bB][mM][sS][oO][nN] +*.[wW][aA][vV] +*.[oO][gG][gG] +*.[mM][pP]3 +*.[fF][lL][aA][cC] +*.[mM]4[aA] +*.[aA][iI][fF] +*.[aA][iI][fF][fF] +*.[bB][mM][sS].meta +*.[bB][mM][eE].meta +*.[bB][mM][lL].meta +*.[pP][mM][sS].meta +*.[bB][mM][sS][oO][nN].meta +*.[wW][aA][vV].meta +*.[oO][gG][gG].meta +*.[mM][pP]3.meta +*.[fF][lL][aA][cC].meta +*.[mM]4[aA].meta +*.[aA][iI][fF].meta +*.[aA][iI][fF][fF].meta diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..ddb6ff8 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "visualstudiotoolsforunity.vstuc" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..da60e25 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Unity", + "type": "vstuc", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e39cdc8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,71 @@ +{ + "files.exclude": { + "**/.DS_Store": true, + "**/.git": true, + "**/.vs": true, + "**/.gitmodules": true, + "**/.vsconfig": true, + "**/*.booproj": true, + "**/*.pidb": true, + "**/*.suo": true, + "**/*.user": true, + "**/*.userprefs": true, + "**/*.unityproj": true, + "**/*.dll": true, + "**/*.exe": true, + "**/*.pdf": true, + "**/*.mid": true, + "**/*.midi": true, + "**/*.wav": true, + "**/*.gif": true, + "**/*.ico": true, + "**/*.jpg": true, + "**/*.jpeg": true, + "**/*.png": true, + "**/*.psd": true, + "**/*.tga": true, + "**/*.tif": true, + "**/*.tiff": true, + "**/*.3ds": true, + "**/*.3DS": true, + "**/*.fbx": true, + "**/*.FBX": true, + "**/*.lxo": true, + "**/*.LXO": true, + "**/*.ma": true, + "**/*.MA": true, + "**/*.obj": true, + "**/*.OBJ": true, + "**/*.asset": true, + "**/*.cubemap": true, + "**/*.flare": true, + "**/*.mat": true, + "**/*.meta": true, + "**/*.prefab": true, + "**/*.unity": true, + "build/": true, + "Build/": true, + "Library/": true, + "library/": true, + "obj/": true, + "Obj/": true, + "Logs/": true, + "logs/": true, + "ProjectSettings/": true, + "UserSettings/": true, + "temp/": true, + "Temp/": true + }, + "files.associations": { + "*.asset": "yaml", + "*.meta": "yaml", + "*.prefab": "yaml", + "*.unity": "yaml" + }, + "explorer.fileNesting.enabled": true, + "explorer.fileNesting.patterns": { + "*.sln": "*.csproj", + "*.slnx": "*.csproj" + }, + "dotnet.defaultSolution": "ProjectQuint.slnx" +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bbaa49b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,23 @@ +# ProjectQuint Repository Guide + +## What + +- ProjectQuint is a macOS-first 4/5/6/7-key BMS rhythm-game client with optional left scratch, built with Unity 6.3 LTS. +- `Assets/ProjectQuint/Runtime/Core` is pure C# chart/timing/judgement code; Unity input, audio and rendering stay under `Runtime/Unity`. +- BMS packs are external user libraries. Never copy song media into the repository, `Assets`, `StreamingAssets`, or a player build. + +## Why + +- Timing and judgement must remain deterministic and independent from render FPS. +- Scratch is a fixed left lane and is preserved in notes, audio, score, combo and autoplay. MIRROR/RANDOM only transform key lanes. +- Long notes auto-complete while held at the tail. Their scoring ticks use musical 1/16-note boundaries, the default early-release grace is 100 ms, and an early break cannot be recovered by re-pressing. +- `TempoMap` owns beat↔DSP-song-time conversion while `ScrollMap` owns visual position. BPM/STOP handling must not be reintroduced as renderer-local time arithmetic. +- BMS packs must be registered by the user as folders or ZIP files. Keep song indexes isolated by pack ID, but keep difficulty tables in the global table registry and cache them by table ID so the same provider can match charts from any pack. + +## How + +- Unity version: `6000.3.20f1`. +- External editor control uses Unity's official MCP from `com.unity.ai.assistant`; verify the live editor with the `Unity_GetConsoleLogs` tool. +- Run tests headlessly with Unity Test Runner and do not append `-quit` to `-runTests`; Unity 6 exits after the run and `-quit` can terminate before tests start. +- Read editor errors through the official MCP console tool or the explicit Unity `-logFile` used by the current validation run. +- Use `AudioSettings.dspTime` and Input System event timestamps for gameplay clocks; never use frame count as the judgement clock. diff --git a/Assets/DefaultVolumeProfile.asset b/Assets/DefaultVolumeProfile.asset new file mode 100644 index 0000000..8cea4d2 --- /dev/null +++ b/Assets/DefaultVolumeProfile.asset @@ -0,0 +1,798 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-8186968272690591729 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3eb4b772797da9440885e8bd939e9560, type: 3} + m_Name: ColorCurves + m_EditorClassIdentifier: + active: 1 + master: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + red: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + green: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + blue: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + hueVsHue: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 1 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + hueVsSat: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 1 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + satVsSat: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 0 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + lumVsSat: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 0 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!114 &-7645455725216806467 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 29fa0085f50d5e54f8144f766051a691, type: 3} + m_Name: FilmGrain + m_EditorClassIdentifier: + active: 1 + type: + m_OverrideState: 1 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0 + response: + m_OverrideState: 1 + m_Value: 0.8 + texture: + m_OverrideState: 1 + m_Value: {fileID: 0} +--- !u!114 &-6951754464447674219 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 558a8e2b6826cf840aae193990ba9f2e, type: 3} + m_Name: ShadowsMidtonesHighlights + m_EditorClassIdentifier: + active: 1 + shadows: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + midtones: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + highlights: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + shadowsStart: + m_OverrideState: 1 + m_Value: 0 + shadowsEnd: + m_OverrideState: 1 + m_Value: 0.3 + highlightsStart: + m_OverrideState: 1 + m_Value: 0.55 + highlightsEnd: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &-4210620904635426390 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cdfbdbb87d3286943a057f7791b43141, type: 3} + m_Name: ChannelMixer + m_EditorClassIdentifier: + active: 1 + redOutRedIn: + m_OverrideState: 1 + m_Value: 100 + redOutGreenIn: + m_OverrideState: 1 + m_Value: 0 + redOutBlueIn: + m_OverrideState: 1 + m_Value: 0 + greenOutRedIn: + m_OverrideState: 1 + m_Value: 0 + greenOutGreenIn: + m_OverrideState: 1 + m_Value: 100 + greenOutBlueIn: + m_OverrideState: 1 + m_Value: 0 + blueOutRedIn: + m_OverrideState: 1 + m_Value: 0 + blueOutGreenIn: + m_OverrideState: 1 + m_Value: 0 + blueOutBlueIn: + m_OverrideState: 1 + m_Value: 100 +--- !u!114 &-3978695733392391637 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0b2db86121404754db890f4c8dfe81b2, type: 3} + m_Name: Bloom + m_EditorClassIdentifier: + active: 1 + skipIterations: + m_OverrideState: 1 + m_Value: 1 + threshold: + m_OverrideState: 1 + m_Value: 0.9 + intensity: + m_OverrideState: 1 + m_Value: 0 + scatter: + m_OverrideState: 1 + m_Value: 0.7 + clamp: + m_OverrideState: 1 + m_Value: 65472 + tint: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + highQualityFiltering: + m_OverrideState: 1 + m_Value: 0 + filter: + m_OverrideState: 1 + m_Value: 0 + downscale: + m_OverrideState: 1 + m_Value: 0 + maxIterations: + m_OverrideState: 1 + m_Value: 6 + dirtTexture: + m_OverrideState: 1 + m_Value: {fileID: 0} + dimension: 1 + dirtIntensity: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-3757877592200609216 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 70afe9e12c7a7ed47911bb608a23a8ff, type: 3} + m_Name: SplitToning + m_EditorClassIdentifier: + active: 1 + shadows: + m_OverrideState: 1 + m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} + highlights: + m_OverrideState: 1 + m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} + balance: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-3386560400091697801 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c01700fd266d6914ababb731e09af2eb, type: 3} + m_Name: DepthOfField + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 0 + gaussianStart: + m_OverrideState: 1 + m_Value: 10 + gaussianEnd: + m_OverrideState: 1 + m_Value: 30 + gaussianMaxRadius: + m_OverrideState: 1 + m_Value: 1 + highQualitySampling: + m_OverrideState: 1 + m_Value: 0 + focusDistance: + m_OverrideState: 1 + m_Value: 10 + aperture: + m_OverrideState: 1 + m_Value: 5.6 + focalLength: + m_OverrideState: 1 + m_Value: 50 + bladeCount: + m_OverrideState: 1 + m_Value: 5 + bladeCurvature: + m_OverrideState: 1 + m_Value: 1 + bladeRotation: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-1083910812058648046 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6bd486065ce11414fa40e631affc4900, type: 3} + m_Name: ProbeVolumesOptions + m_EditorClassIdentifier: + active: 1 + normalBias: + m_OverrideState: 1 + m_Value: 0.05 + viewBias: + m_OverrideState: 1 + m_Value: 0.1 + scaleBiasWithMinProbeDistance: + m_OverrideState: 1 + m_Value: 0 + samplingNoise: + m_OverrideState: 1 + m_Value: 0.1 + animateSamplingNoise: + m_OverrideState: 1 + m_Value: 1 + leakReductionMode: + m_OverrideState: 1 + m_Value: 2 + minValidDotProductValue: + m_OverrideState: 1 + m_Value: 0.1 + occlusionOnlyReflectionNormalization: + m_OverrideState: 1 + m_Value: 1 + intensityMultiplier: + m_OverrideState: 1 + m_Value: 1 + skyOcclusionIntensityMultiplier: + m_OverrideState: 1 + m_Value: 1 + worldOffset: + m_OverrideState: 1 + m_Value: {x: 0, y: 0, z: 0} +--- !u!114 &-277880470225475626 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ccf1aba9553839d41ae37dd52e9ebcce, type: 3} + m_Name: MotionBlur + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 0 + quality: + m_OverrideState: 1 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0 + clamp: + m_OverrideState: 1 + m_Value: 0.05 +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3} + m_Name: DefaultVolumeProfile + m_EditorClassIdentifier: + components: + - {fileID: 1247868777620691934} + - {fileID: -277880470225475626} + - {fileID: 2338690907228448291} + - {fileID: 2335868966179733374} + - {fileID: 3460444317416099510} + - {fileID: 7933203426823181213} + - {fileID: 5762530381802164056} + - {fileID: -3386560400091697801} + - {fileID: 156332030701414761} + - {fileID: 9099548931283147545} + - {fileID: -3757877592200609216} + - {fileID: 2332282131777942337} + - {fileID: -4210620904635426390} + - {fileID: -6951754464447674219} + - {fileID: 8549655192814258499} + - {fileID: -7645455725216806467} + - {fileID: -8186968272690591729} + - {fileID: -3978695733392391637} + - {fileID: -1083910812058648046} +--- !u!114 &156332030701414761 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5485954d14dfb9a4c8ead8edb0ded5b1, type: 3} + m_Name: LiftGammaGain + m_EditorClassIdentifier: + active: 1 + lift: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + gamma: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + gain: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} +--- !u!114 &1247868777620691934 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 97c23e3b12dc18c42a140437e53d3951, type: 3} + m_Name: Tonemapping + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 0 + neutralHDRRangeReductionMode: + m_OverrideState: 1 + m_Value: 2 + acesPreset: + m_OverrideState: 1 + m_Value: 3 + hueShiftAmount: + m_OverrideState: 1 + m_Value: 0 + detectPaperWhite: + m_OverrideState: 1 + m_Value: 0 + paperWhite: + m_OverrideState: 1 + m_Value: 300 + detectBrightnessLimits: + m_OverrideState: 1 + m_Value: 1 + minNits: + m_OverrideState: 1 + m_Value: 0.005 + maxNits: + m_OverrideState: 1 + m_Value: 1000 +--- !u!114 &2332282131777942337 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e021b4c809a781e468c2988c016ebbea, type: 3} + m_Name: ColorLookup + m_EditorClassIdentifier: + active: 1 + texture: + m_OverrideState: 1 + m_Value: {fileID: 0} + dimension: 1 + contribution: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &2335868966179733374 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c5e1dc532bcb41949b58bc4f2abfbb7e, type: 3} + m_Name: LensDistortion + m_EditorClassIdentifier: + active: 1 + intensity: + m_OverrideState: 1 + m_Value: 0 + xMultiplier: + m_OverrideState: 1 + m_Value: 1 + yMultiplier: + m_OverrideState: 1 + m_Value: 1 + center: + m_OverrideState: 1 + m_Value: {x: 0.5, y: 0.5} + scale: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &2338690907228448291 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 221518ef91623a7438a71fef23660601, type: 3} + m_Name: WhiteBalance + m_EditorClassIdentifier: + active: 1 + temperature: + m_OverrideState: 1 + m_Value: 0 + tint: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &3460444317416099510 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 81180773991d8724ab7f2d216912b564, type: 3} + m_Name: ChromaticAberration + m_EditorClassIdentifier: + active: 1 + intensity: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &5762530381802164056 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 06437c1ff663d574d9447842ba0a72e4, type: 3} + m_Name: ScreenSpaceLensFlare + m_EditorClassIdentifier: + active: 1 + intensity: + m_OverrideState: 1 + m_Value: 0 + tintColor: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + bloomMip: + m_OverrideState: 1 + m_Value: 1 + firstFlareIntensity: + m_OverrideState: 1 + m_Value: 1 + secondaryFlareIntensity: + m_OverrideState: 1 + m_Value: 1 + warpedFlareIntensity: + m_OverrideState: 1 + m_Value: 1 + warpedFlareScale: + m_OverrideState: 1 + m_Value: {x: 1, y: 1} + samples: + m_OverrideState: 1 + m_Value: 1 + sampleDimmer: + m_OverrideState: 1 + m_Value: 0.5 + vignetteEffect: + m_OverrideState: 1 + m_Value: 1 + startingPosition: + m_OverrideState: 1 + m_Value: 1.25 + scale: + m_OverrideState: 1 + m_Value: 1.5 + streaksIntensity: + m_OverrideState: 1 + m_Value: 0 + streaksLength: + m_OverrideState: 1 + m_Value: 0.5 + streaksOrientation: + m_OverrideState: 1 + m_Value: 0 + streaksThreshold: + m_OverrideState: 1 + m_Value: 0.25 + resolution: + m_OverrideState: 1 + m_Value: 4 + chromaticAbberationIntensity: + m_OverrideState: 1 + m_Value: 0.5 +--- !u!114 &7933203426823181213 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 66f335fb1ffd8684294ad653bf1c7564, type: 3} + m_Name: ColorAdjustments + m_EditorClassIdentifier: + active: 1 + postExposure: + m_OverrideState: 1 + m_Value: 0 + contrast: + m_OverrideState: 1 + m_Value: 0 + colorFilter: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + hueShift: + m_OverrideState: 1 + m_Value: 0 + saturation: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &8549655192814258499 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 899c54efeace73346a0a16faa3afe726, type: 3} + m_Name: Vignette + m_EditorClassIdentifier: + active: 1 + color: + m_OverrideState: 1 + m_Value: {r: 0, g: 0, b: 0, a: 1} + center: + m_OverrideState: 1 + m_Value: {x: 0.5, y: 0.5} + intensity: + m_OverrideState: 1 + m_Value: 0 + smoothness: + m_OverrideState: 1 + m_Value: 0.2 + rounded: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &9099548931283147545 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fb60a22f311433c4c962b888d1393f88, type: 3} + m_Name: PaniniProjection + m_EditorClassIdentifier: + active: 1 + distance: + m_OverrideState: 1 + m_Value: 0 + cropToFit: + m_OverrideState: 1 + m_Value: 1 diff --git a/Assets/DefaultVolumeProfile.asset.meta b/Assets/DefaultVolumeProfile.asset.meta new file mode 100644 index 0000000..5383e5d --- /dev/null +++ b/Assets/DefaultVolumeProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3f9215ea0144899419cfbc0957140d3f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/InputSystem_Actions.inputactions b/Assets/InputSystem_Actions.inputactions new file mode 100644 index 0000000..1a12cb9 --- /dev/null +++ b/Assets/InputSystem_Actions.inputactions @@ -0,0 +1,1057 @@ +{ + "name": "InputSystem_Actions", + "maps": [ + { + "name": "Player", + "id": "df70fa95-8a34-4494-b137-73ab6b9c7d37", + "actions": [ + { + "name": "Move", + "type": "Value", + "id": "351f2ccd-1f9f-44bf-9bec-d62ac5c5f408", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Look", + "type": "Value", + "id": "6b444451-8a00-4d00-a97e-f47457f736a8", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Attack", + "type": "Button", + "id": "6c2ab1b8-8984-453a-af3d-a3c78ae1679a", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Interact", + "type": "Button", + "id": "852140f2-7766-474d-8707-702459ba45f3", + "expectedControlType": "Button", + "processors": "", + "interactions": "Hold", + "initialStateCheck": false + }, + { + "name": "Crouch", + "type": "Button", + "id": "27c5f898-bc57-4ee1-8800-db469aca5fe3", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Jump", + "type": "Button", + "id": "f1ba0d36-48eb-4cd5-b651-1c94a6531f70", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Previous", + "type": "Button", + "id": "2776c80d-3c14-4091-8c56-d04ced07a2b0", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Next", + "type": "Button", + "id": "b7230bb6-fc9b-4f52-8b25-f5e19cb2c2ba", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Sprint", + "type": "Button", + "id": "641cd816-40e6-41b4-8c3d-04687c349290", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "", + "id": "978bfe49-cc26-4a3d-ab7b-7d7a29327403", + "path": "/leftStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "WASD", + "id": "00ca640b-d935-4593-8157-c05846ea39b3", + "path": "Dpad", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "e2062cb9-1b15-46a2-838c-2f8d72a0bdd9", + "path": "/w", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "8180e8bd-4097-4f4e-ab88-4523101a6ce9", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "320bffee-a40b-4347-ac70-c210eb8bc73a", + "path": "/s", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "1c5327b5-f71c-4f60-99c7-4e737386f1d1", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "d2581a9b-1d11-4566-b27d-b92aff5fabbc", + "path": "/a", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "2e46982e-44cc-431b-9f0b-c11910bf467a", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcfe95b8-67b9-4526-84b5-5d0bc98d6400", + "path": "/d", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "77bff152-3580-4b21-b6de-dcd0c7e41164", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "1635d3fe-58b6-4ba9-a4e2-f4b964f6b5c8", + "path": "/{Primary2DAxis}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3ea4d645-4504-4529-b061-ab81934c3752", + "path": "/stick", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c1f7a91b-d0fd-4a62-997e-7fb9b69bf235", + "path": "/rightStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8c8e490b-c610-4785-884f-f04217b23ca4", + "path": "/delta", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse;Touch", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3e5f5442-8668-4b27-a940-df99bad7e831", + "path": "/{Hatswitch}", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "143bb1cd-cc10-4eca-a2f0-a3664166fe91", + "path": "/buttonWest", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "05f6913d-c316-48b2-a6bb-e225f14c7960", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "886e731e-7071-4ae4-95c0-e61739dad6fd", + "path": "/primaryTouch/tap", + "interactions": "", + "processors": "", + "groups": ";Touch", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "ee3d0cd2-254e-47a7-a8cb-bc94d9658c54", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8255d333-5683-4943-a58a-ccb207ff1dce", + "path": "/{PrimaryAction}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3c1c7f0-bd20-4ee7-a0f1-899b24bca6d7", + "path": "/enter", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "cbac6039-9c09-46a1-b5f2-4e5124ccb5ed", + "path": "/2", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e15ca19d-e649-4852-97d5-7fe8ccc44e94", + "path": "/dpad/right", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "f2e9ba44-c423-42a7-ad56-f20975884794", + "path": "/leftShift", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8cbb2f4b-a784-49cc-8d5e-c010b8c7f4e6", + "path": "/leftStickPress", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "d8bf24bf-3f2f-4160-a97c-38ec1eb520ba", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "eb40bb66-4559-4dfa-9a2f-820438abb426", + "path": "/space", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "daba33a1-ad0c-4742-a909-43ad1cdfbeb6", + "path": "/buttonSouth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "603f3daf-40bd-4854-8724-93e8017f59e3", + "path": "/secondaryButton", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1534dc16-a6aa-499d-9c3a-22b47347b52a", + "path": "/1", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "25060bbd-a3a6-476e-8fba-45ae484aad05", + "path": "/dpad/left", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1c04ea5f-b012-41d1-a6f7-02e963b52893", + "path": "/e", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3f66d0b-7751-423f-908b-a11c5bd95930", + "path": "/buttonNorth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4f4649ac-64a8-4a73-af11-b3faef356a4d", + "path": "/buttonEast", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "36e52cba-0905-478e-a818-f4bfcb9f3b9a", + "path": "/c", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + } + ] + }, + { + "name": "UI", + "id": "272f6d14-89ba-496f-b7ff-215263d3219f", + "actions": [ + { + "name": "Navigate", + "type": "PassThrough", + "id": "c95b2375-e6d9-4b88-9c4c-c5e76515df4b", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Submit", + "type": "Button", + "id": "7607c7b6-cd76-4816-beef-bd0341cfe950", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Cancel", + "type": "Button", + "id": "15cef263-9014-4fd5-94d9-4e4a6234a6ef", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Point", + "type": "PassThrough", + "id": "32b35790-4ed0-4e9a-aa41-69ac6d629449", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Click", + "type": "PassThrough", + "id": "3c7022bf-7922-4f7c-a998-c437916075ad", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "RightClick", + "type": "PassThrough", + "id": "44b200b1-1557-4083-816c-b22cbdf77ddf", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "MiddleClick", + "type": "PassThrough", + "id": "dad70c86-b58c-4b17-88ad-f5e53adf419e", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "ScrollWheel", + "type": "PassThrough", + "id": "0489e84a-4833-4c40-bfae-cea84b696689", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDevicePosition", + "type": "PassThrough", + "id": "24908448-c609-4bc3-a128-ea258674378a", + "expectedControlType": "Vector3", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDeviceOrientation", + "type": "PassThrough", + "id": "9caa3d8a-6b2f-4e8e-8bad-6ede561bd9be", + "expectedControlType": "Quaternion", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "Gamepad", + "id": "809f371f-c5e2-4e7a-83a1-d867598f40dd", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "14a5d6e8-4aaf-4119-a9ef-34b8c2c548bf", + "path": "/leftStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "9144cbe6-05e1-4687-a6d7-24f99d23dd81", + "path": "/rightStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2db08d65-c5fb-421b-983f-c71163608d67", + "path": "/leftStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "58748904-2ea9-4a80-8579-b500e6a76df8", + "path": "/rightStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "8ba04515-75aa-45de-966d-393d9bbd1c14", + "path": "/leftStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "712e721c-bdfb-4b23-a86c-a0d9fcfea921", + "path": "/rightStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcd248ae-a788-4676-a12e-f4d81205600b", + "path": "/leftStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "1f04d9bc-c50b-41a1-bfcc-afb75475ec20", + "path": "/rightStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "fb8277d4-c5cd-4663-9dc7-ee3f0b506d90", + "path": "/dpad", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "Joystick", + "id": "e25d9774-381c-4a61-b47c-7b6b299ad9f9", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "3db53b26-6601-41be-9887-63ac74e79d19", + "path": "/stick/up", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "0cb3e13e-3d90-4178-8ae6-d9c5501d653f", + "path": "/stick/down", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "0392d399-f6dd-4c82-8062-c1e9c0d34835", + "path": "/stick/left", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "942a66d9-d42f-43d6-8d70-ecb4ba5363bc", + "path": "/stick/right", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "Keyboard", + "id": "ff527021-f211-4c02-933e-5976594c46ed", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "563fbfdd-0f09-408d-aa75-8642c4f08ef0", + "path": "/w", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "eb480147-c587-4a33-85ed-eb0ab9942c43", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2bf42165-60bc-42ca-8072-8c13ab40239b", + "path": "/s", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "85d264ad-e0a0-4565-b7ff-1a37edde51ac", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "74214943-c580-44e4-98eb-ad7eebe17902", + "path": "/a", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "cea9b045-a000-445b-95b8-0c171af70a3b", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "8607c725-d935-4808-84b1-8354e29bab63", + "path": "/d", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "4cda81dc-9edd-4e03-9d7c-a71a14345d0b", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "9e92bb26-7e3b-4ec4-b06b-3c8f8e498ddc", + "path": "*/{Submit}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Submit", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "82627dcc-3b13-4ba9-841d-e4b746d6553e", + "path": "*/{Cancel}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Cancel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c52c8e0b-8179-41d3-b8a1-d149033bbe86", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e1394cbc-336e-44ce-9ea8-6007ed6193f7", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "5693e57a-238a-46ed-b5ae-e64e6e574302", + "path": "/touch*/position", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4faf7dc9-b979-4210-aa8c-e808e1ef89f5", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8d66d5ba-88d7-48e6-b1cd-198bbfef7ace", + "path": "/tip", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "47c2a644-3ebc-4dae-a106-589b7ca75b59", + "path": "/touch*/press", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "bb9e6b34-44bf-4381-ac63-5aa15d19f677", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "38c99815-14ea-4617-8627-164d27641299", + "path": "/scroll", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "ScrollWheel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4c191405-5738-4d4b-a523-c6a301dbf754", + "path": "/rightButton", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "RightClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "24066f69-da47-44f3-a07e-0015fb02eb2e", + "path": "/middleButton", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "MiddleClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "7236c0d9-6ca3-47cf-a6ee-a97f5b59ea77", + "path": "/devicePosition", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDevicePosition", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "23e01e3a-f935-4948-8d8b-9bcac77714fb", + "path": "/deviceRotation", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDeviceOrientation", + "isComposite": false, + "isPartOfComposite": false + } + ] + } + ], + "controlSchemes": [ + { + "name": "Keyboard&Mouse", + "bindingGroup": "Keyboard&Mouse", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + }, + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Gamepad", + "bindingGroup": "Gamepad", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Touch", + "bindingGroup": "Touch", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Joystick", + "bindingGroup": "Joystick", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "XR", + "bindingGroup": "XR", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + } + ] +} \ No newline at end of file diff --git a/Assets/InputSystem_Actions.inputactions.meta b/Assets/InputSystem_Actions.inputactions.meta new file mode 100644 index 0000000..e786d3e --- /dev/null +++ b/Assets/InputSystem_Actions.inputactions.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 2bcd2660ca9b64942af0de543d8d7100 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} + generateWrapperCode: 0 + wrapperCodePath: + wrapperClassName: + wrapperCodeNamespace: diff --git a/Assets/Plugins.meta b/Assets/Plugins.meta new file mode 100644 index 0000000..253803e --- /dev/null +++ b/Assets/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2520ca8182cfd49cabc3a8e7a80706b6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/FMOD.meta b/Assets/Plugins/FMOD.meta new file mode 100644 index 0000000..aab6de5 --- /dev/null +++ b/Assets/Plugins/FMOD.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2cac0cde16bcd41db91aae47d021c3cf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/FMOD/Cache.meta b/Assets/Plugins/FMOD/Cache.meta new file mode 100644 index 0000000..11def91 --- /dev/null +++ b/Assets/Plugins/FMOD/Cache.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 501e3597be9954100bdbb5e7f8a574e2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/FMOD/Cache/Editor.meta b/Assets/Plugins/FMOD/Cache/Editor.meta new file mode 100644 index 0000000..f1debb5 --- /dev/null +++ b/Assets/Plugins/FMOD/Cache/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cfff49ae6cc144318ba88c227fc3e88b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/FMOD/Cache/Editor/FMODStudioCache.asset b/Assets/Plugins/FMOD/Cache/Editor/FMODStudioCache.asset new file mode 100644 index 0000000..34c3903 --- /dev/null +++ b/Assets/Plugins/FMOD/Cache/Editor/FMODStudioCache.asset @@ -0,0 +1,22 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d32cf7c32a3ed8347bac48ef5ed56d82, type: 3} + m_Name: FMODStudioCache + m_EditorClassIdentifier: FMODUnityEditor::FMODUnity.EventCache + EditorBanks: [] + EditorEvents: [] + EditorParameters: [] + MasterBanks: [] + StringsBanks: [] + cacheVersion: 131860 + cacheTime: 0 + SerializableEventsDict: [] diff --git a/Assets/Plugins/FMOD/Cache/Editor/FMODStudioCache.asset.meta b/Assets/Plugins/FMOD/Cache/Editor/FMODStudioCache.asset.meta new file mode 100644 index 0000000..52517ab --- /dev/null +++ b/Assets/Plugins/FMOD/Cache/Editor/FMODStudioCache.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b688e6cf157394eb994baa18eb318210 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/FMOD/Resources.meta b/Assets/Plugins/FMOD/Resources.meta new file mode 100644 index 0000000..5531f67 --- /dev/null +++ b/Assets/Plugins/FMOD/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 10238152fc3374e21808394d5539c5e9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/FMOD/Resources/FMODStudioSettings.asset b/Assets/Plugins/FMOD/Resources/FMODStudioSettings.asset new file mode 100644 index 0000000..b1ee800 --- /dev/null +++ b/Assets/Plugins/FMOD/Resources/FMODStudioSettings.asset @@ -0,0 +1,956 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-8876659224941116795 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d92d0baf34dae0e4ea032a42bd6107c0, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformMac + identifier: 52eb9df5db46521439908db3a29a1bbb + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &-6797349823870411825 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 97ba6cc2660c0ca498540d254701057a, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformAndroid + identifier: 2fea114e74ecf3c4f920e1d5cc1c4c40 + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &-3793632861967256216 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bc1f51bc35d549941904cf062bae93a3, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformIOS + identifier: 0f8eb3f400726694eb47beb1a9f94ce8 + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &-2900218176781211718 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 17eea195bdfbf014e91ba7620ee491f8, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformAppleTV + identifier: e7a046c753c3c3d4aacc91f6597f310d + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &-2541522425307482493 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 335f0a4b26fb46942858ea029e030d2a, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformPlayInEditor + identifier: playInEditor + parentIdentifier: + active: 1 + Properties: + LiveUpdate: + Value: 1 + HasValue: 1 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 1 + HasValue: 1 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 48000 + HasValue: 1 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 1024 + HasValue: 1 + RealChannelCount: + Value: 256 + HasValue: 1 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &-2094696130006404903 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 73549a74f689f0849a8271d9e908c514, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformLinux + identifier: b7716510a1f36934c87976f3a81dbf3d + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: eef8d824ea7b63742966aaa0e94ac383, type: 3} + m_Name: FMODStudioSettings + m_EditorClassIdentifier: FMODUnity::FMODUnity.Settings + switchSettingsMigration: 1 + HasSourceProject: 1 + HasPlatforms: 1 + sourceProjectPath: + sourceBankPath: + sourceBankPathUnformatted: + BankRefreshCooldown: 5 + ShowBankRefreshWindow: 1 + AutomaticEventLoading: 1 + BankLoadType: 0 + AutomaticSampleLoading: 0 + EncryptionKey: + ImportType: 0 + TargetAssetPath: FMODBanks + TargetBankFolder: + EventLinkage: 0 + SerializeGUIDsOnly: 0 + LoggingLevel: 2 + SpeakerModeSettings: [] + SampleRateSettings: [] + LiveUpdateSettings: [] + OverlaySettings: [] + BankDirectorySettings: [] + VirtualChannelSettings: [] + RealChannelSettings: [] + Plugins: [] + MasterBanks: [] + Banks: [] + BanksToLoad: [] + LiveUpdatePort: 9264 + EnableMemoryTracking: 0 + AndroidUseOBB: 0 + AndroidPatchBuild: 0 + MeterChannelOrdering: 0 + StopEventsOutsideMaxDistance: 0 + BoltUnitOptionsBuildPending: 0 + EnableErrorCallback: 0 + SharedLibraryUpdateStage: 0 + SharedLibraryTimeSinceStart: 0 + CurrentVersion: 131860 + HideSetupWizard: 0 + LastEventReferenceScanVersion: 131860 + Platforms: + - {fileID: -2094696130006404903} + - {fileID: 6584450883818306987} + - {fileID: 3944497114675524469} + - {fileID: -3793632861967256216} + - {fileID: -8876659224941116795} + - {fileID: 4116145689230507682} + - {fileID: -2900218176781211718} + - {fileID: 2006094704713410788} + - {fileID: 3754044641852712671} + - {fileID: -6797349823870411825} + - {fileID: 5131407680144949201} + - {fileID: -2541522425307482493} + MigratedPlatforms: 0a00000000000000150000000b00000009000000050000001200000008000000060000000c0000000200000001000000 +--- !u!114 &2006094704713410788 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9660e62d6232af242877f0cc2b90c63d, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformWindows + identifier: 2c5177b11d81d824dbb064f9ac8527da + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &3754044641852712671 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3abeb1429547a134480f4f2f1efc7e21, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformMobileLow + identifier: c88d16e5272a4e241b0ef0ac2e53b73d + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &3944497114675524469 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b87314b32fbe18943af496e4b47136c6, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformWebGL + identifier: 46fbfdf3fc43db0458918377fd40293e + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &4116145689230507682 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 93a382382f106584e8f8f62412fee177, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformMobileHigh + identifier: fd7c55dab0fce234b8c25f6ffca523c1 + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &5131407680144949201 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9587379c972e4a54da1949613f11e1c6, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformDefault + identifier: default + parentIdentifier: + active: 1 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 9264 + HasValue: 1 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 1 + StaticPlugins: + Value: [] + HasValue: 1 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: + - b7716510a1f36934c87976f3a81dbf3d + - de700ef3f37a49b58a57ae3addf01ad9 + - 46fbfdf3fc43db0458918377fd40293e + - 0f8eb3f400726694eb47beb1a9f94ce8 + - 52eb9df5db46521439908db3a29a1bbb + - fd7c55dab0fce234b8c25f6ffca523c1 + - e7a046c753c3c3d4aacc91f6597f310d + - 2c5177b11d81d824dbb064f9ac8527da + - c88d16e5272a4e241b0ef0ac2e53b73d + - 2fea114e74ecf3c4f920e1d5cc1c4c40 + codecChannels: + Value: [] + HasValue: 0 +--- !u!114 &6584450883818306987 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8f591a193113347409efedcc618e83d6, type: 3} + m_Name: FMODStudioSettingsPlatform + m_EditorClassIdentifier: FMODUnity::FMODUnity.PlatformVisionOS + identifier: de700ef3f37a49b58a57ae3addf01ad9 + parentIdentifier: default + active: 0 + Properties: + LiveUpdate: + Value: 0 + HasValue: 0 + LiveUpdatePort: + Value: 0 + HasValue: 0 + Overlay: + Value: 0 + HasValue: 0 + OverlayPosition: + Value: 0 + HasValue: 0 + OverlayFontSize: + Value: 0 + HasValue: 0 + Logging: + Value: 0 + HasValue: 0 + SampleRate: + Value: 0 + HasValue: 0 + BuildDirectory: + Value: + HasValue: 0 + SpeakerMode: + Value: 0 + HasValue: 0 + VirtualChannelCount: + Value: 0 + HasValue: 0 + RealChannelCount: + Value: 0 + HasValue: 0 + DSPBufferLength: + Value: 0 + HasValue: 0 + DSPBufferCount: + Value: 0 + HasValue: 0 + Plugins: + Value: [] + HasValue: 0 + StaticPlugins: + Value: [] + HasValue: 0 + CallbackHandler: + Value: {fileID: 0} + HasValue: 0 + OutputTypeName: + threadAffinities: + Value: [] + HasValue: 0 + displaySortOrder: 0 + childIdentifiers: [] + codecChannels: + Value: [] + HasValue: 0 diff --git a/Assets/Plugins/FMOD/Resources/FMODStudioSettings.asset.meta b/Assets/Plugins/FMOD/Resources/FMODStudioSettings.asset.meta new file mode 100644 index 0000000..6456d48 --- /dev/null +++ b/Assets/Plugins/FMOD/Resources/FMODStudioSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a51d0bf46d0fd4e0a8224569e29b7cb3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint.meta b/Assets/ProjectQuint.meta new file mode 100644 index 0000000..3c384c9 --- /dev/null +++ b/Assets/ProjectQuint.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b9fcfc305c32f47c6894393884afbe27 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Art.meta b/Assets/ProjectQuint/Art.meta new file mode 100644 index 0000000..e3335fb --- /dev/null +++ b/Assets/ProjectQuint/Art.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dbee217e0a68d43ec9fcf523aa9b915d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Art/AppIcon.meta b/Assets/ProjectQuint/Art/AppIcon.meta new file mode 100644 index 0000000..47c0891 --- /dev/null +++ b/Assets/ProjectQuint/Art/AppIcon.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cc3b5549db61d4e829d9608adfb38452 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Art/AppIcon/ProjectQuintAppIcon.png b/Assets/ProjectQuint/Art/AppIcon/ProjectQuintAppIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..a957540a86431d3c558d7cb3eb36bba0a04e1e5b GIT binary patch literal 1143648 zcmV)PK()V#P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR950H6Z^1ONa40RR95000000Mp&-9RL7807*naRCoc@y=jvr$#tfe`&yN? z_l^e8*g0r$1u1cv5jnDviJBRiOe2$I`T_cR(l<$CTDXW52@=ErAa*o*L8I5Iu08if z&+{JlxN+~ztgZ$rnK3dm!u|L;?|IL04-b#HOD-;a?6$bLaCUZ%7IL;x(_$oNBgr{d zh@v+^?D~OQn8?pHo+2ZV`W@k1izf-kA&kr221)~;3+au*DV;DH?2NY<4y^@0(dY`@ z?S;qlI4mw8O%^a~#c6=2?UxyI%+Be{ayR0R^Yb;QNK80PQJhqgeyV+=PlwvXn-s^r zT~-r`?3$TPi%S~ta~83?C9Aa656wiG^pzQ9TO3mf{_J$@ypwAj;x~E|20?5Tq9*#; zA~@;*?iS4W=~1eaU~ILyyHzN}ur)0tq$2ENQW|1d0)kRZW7t|PSKytneoIS7lQ!nuD?+`=K(9APDOER}dY^u0=vn+GR_T>`59w%DPmA#R;lY4ot<{&+4d^Mw{}Ao)N(6^5vSOI)7A-}Eu40o$cLSsX2e*I zJhkRR7H@iM9=mW>p^{mk1HI5xb1+6NmYfWnEhe`!Xn;*KYC+2`+ELYiQ?-;>W0Uj4 z*1k=&khFLUUFU7fpo)twsX%IkbQ#AI565ze+(OxRP&6s_I2%L;KvOS^FscrS4j|3O zYRgK;j7DH9s%g2cIPJm-jLJ$AO)KlS=UnEY-*#$t6huAdARr$oPnd#KjM(EP7WPD; zq^>g&h7?+J%h^`NwlAK}ltrZMH7~qDxp-&adeCz7G-zoBqc-tIEX``KQW1_ULo$$9 zMahZ^EyNB`_U>Ckx`1l4Pl9?sBu-(vp2yJ6`B|MYaHpW+Q151Kx@wv2(lO-6mpi+r zIYns7nm8&0u?U}wm9^C1LXD}|rz%rUpH!*Db{7>#^VgH!2VLm&Y?mgYF}XoFB?qTv z{(K@6r{@K0YX)xRVQ?|y*J{P0?+mPwRU1&_NWulQNw6~{r^7EAeF(+4JoeNL-W$U? zU)`o1OU=PIAWUmCoPptVs}aR;P@8F=RM8kGYiLNiX56bgD31+i%0^pyQx*+-Kp1>o zRA@<__?)6Tb_xPjwlVlvGA>84?Mglv&Qwb&Gm60c)$B5vCLC8SOXYgr(ezX(Bsv4n zae1am)e(MG2g9n?e%{5VDA~%btwpYc6xGra<+K7#Erys z8Ium$bGLL|YsrwN zY&Z7vCc`zd4B9lfYa6PQQ?Xn2Mvu*{47h`Kt3kqka6QmV2ulk~b%uCamD54907v|U zt7@>@E`S-CAXa->QRqb>7H(sIzZj1?p#FoVJPxCtIdkC=shqm~M%ha(b)fvEn zC?>;dw%t;;ww^i^DuzyXoQ=}eZ+y$G7s9e*jgN(cWXx%-kg{6*m2yvuEl37K6EbLb zxhd%7H2#y?^-eU-rR8NhoV)Hk{UI)%0cC}kA=<{m;*tY(yro_PAtPnsv~CtBT9Z=c zv!JG6s?yTm^}SkIF0np!Fx4evBI9z8t2#`O4m#F)+VX_JFNP*)R^v*RLgcw(t6G<|IaFEPH#qCIps_s4b zH|952o`)T)46O*C5@&2aRlbOd=^9~=ic{uQ8S5s`gHFc&6w=W2@r7}mrTqjNr94(B zCha6mbCt)AXRHQ~V;uDD?io=>$w_iiqe@`Tn2sgma)+E8C&?T^jEWP@i%J*ai#!K} zpv3Ii=GrGt&aRiH=mudoir=^0Np&Zr@%?npWi

lvADwY8;VRjV=4WYXd1YmJb!~lZbK}a+)k~MR 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

Jw$IQ=Wy7 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 @s;8 z`Kn@qK{6jDmN!TE!i4)Xe;bj{aj{$Z5ulGe6(A>kCE7jAOQ4^X;yT`3mQh)DNKqK_ z9C-x2CuBKuEM75~=r~is_L}q$t_g<}R7cKH#Dy1f$r>bUP<$q5liVmQ#cCW(4Y*S4 z-Af46lp*%my1+!8CCh2a> zAkj2i6h{3}#q@?k*(ep5fp4?&POf6I*w0NJ%g~$7H0>LQQ(FSF+7)EZKtZjW5HOIr zzxg*V7oSlOo+D*~pu&S{tPJ^~F)t}R)jOl@hMM_TT_I?$Q$LjNeGvAx@mB{lVIj|09-Z*$OE|rO&hH+$e&SAQFDI1FQ?u;Sip{&T{vfdFCH%( zcz4SnJNP9fKJ9jNhp$&~yiSllg!b5i_FVjdSE4mCCzYLvc7|XyBDs+4GM`W6eMSry#B%$svIddYfed&eO6#w z1`24DCxOih7vw;CPL_i@^eGu31Ei_m5;-y;N-0$iH1_aHQBQMEld`4g;>`+R`{bre z(NIY>=PDHf2Yysih0(HBSHfJWko80&0~=o|rXsaVp4s?HUxGBv59hZc?{IM|xI+_+ z^$mnIEsr)Uh&qz?7AI607lmQ~kw@K_5YGct&odtZayk)JHuER!O5t$u9A-5dd3RO{>Qt82^z5Y0N;TLF%WIl-B2=FGW@oLF z*juh8b$Uh=H{-lbp)>?lyu{U9)!MEVgi_pyW`WlwWtc`rO%o;=X6Ylmxp3pCNpnyh zn#3D757=RN&V7P$#xvtf&F6CxgVH$L|B`TdXI{Pq9sAOGR6fBfY)-+p@c9tHj? z&=!G1)X4bJQb2qggmmt*v>TlE3OVH?CRBaIr!mlbLS$I2z(1p&Z;0(&s2EkFeK%zQTg8ckx9s=4^Wzy1A@VdT?3A3y&5^OwK#cmK&ZA3lEh{A2$O zfGpC#yMG6wuSWCgRWTGq!8s_l;=#E=MR1%S>NlpjMcHr|0gX%kmBJA8BOax7=rYr zLBWKi(6JSX)SA)h!Xc>cnV+ejiJ)p?zv&L?NA1Z}qb})N5;`kRBu0AcM6u;EoM+VR zm1hJEf_ffMM5e`2s!r!#S2KG-O$`%nWV~CN{sP$`iXrA@T#X!U02or8j)IyEvoA1W zbKF*0AR&R^?NL;-#BVQ`8uPGn0s(Kwl9nQr4G^9QjX{{sWQ%pSkR{Sovh&!zklSjU z5J>W;(X!|+*VmF*Rk$j>0Buk3jQA55!Qe|EDBK|0rVkLOs}q4UixCW@)cc7ny^X2s z*4VL`g%`MGjHs>Wk#ot1+ae&}`X1&a5H)Ca6y7=O`HqY#294tH!my@Gww$$2_D>Gvc`Or}0vcw6ghH1mh`wcWb zet!3p_xhHEdI*V%UQ%J&pXpzpl=v<(Uf8-Nq$hxTqE(z5DU@+x3LGC}lc-~?1Aa)6 zt8$%-Ob}nBHPh|LREx)uHt1a@std(u!*~jzcJrPg0~V8ov9UH9#sVb#LzL7=c?67- z2>n}(Nt8RIp6rY%yLPn9SMh3mzfaZmQsx%Was>~Brod2(%G!z!OSYNrBjbXfB&aP+ zI3adSg0eY=HpxAV4T#p)zXBXt#RhgtHPyY5k(r#zHOOj5L)6qn_J^|_l8HmaOn)uS zwx5fZM@;S=1_gmSIM=(3IgHvL_`$jET0x`m#z0{B!lCE$*qM!@ySZeu>7eTKI~T`5 zXO<;ob#x7fe2OUGDNxaDSX6NBQ=fW0m__v6MLn0O42;tz8ngDULICvNTcg9C5PAA5 zCcns(ihCreOnf&hd1S&kl~tdaRyqh&tpA^aA2fYPZl#GrwMllQOb0r`Q^t;dR12xj z5d=(48m1i$)Lm~2<-(w4cJgd>knLXZXuu4hsAHgGfhk}e+_uDAtjlhSrotP&4mTL< zo7^L6GN7PI0It|cOd3{BUon^k%Bux#D5eXcII@|@qd8e$Yz4J98byze8hfSWJ_3|E z9uxbpjKlE)S#6bSBCOFjrzMGD^tV(ZPo4U;sql#oZ1!9Z)taYGE%?c3{IAO3E8 z7uFp{0AoO$zZ-ttst>_wP{8saDv{mFx$gBe6?jDm>$DCIce@`y{q)_pzxTiY!T<1o z{J~Ft_~FZU-?2nKvyaBwD}&!FbG~XyoID+IvoIwdAOwjt@-Lq*OZqYnqH#-fX1?Un z4$vO^Y)Fl^A>rK+YIbs?Db`CI-HfN2mWIrO1T*Tw&K^P1{hObkxXJgEKR^EX`Hz47 z{jdJ$hfhC#^W%@a31EC5c{1Qv?|eGNotGCCf#Hiwhvb@a`?{FWMMC&>Ac&|9dmTt} zvplpYjnRZ4#~1eeQgU?Gz~WwTK`6J}WS4KLX4Ux{Cg zqAQ8n57wTVs;i-U&p1zD+paiGC#||s7vPW`w2B7Fvy=lZeitSB7cw+z=P!VKV`2;A3>eC4&EVv``$$2{=5E%G`fL=Q@kPrLN#V&%w${P*o zloSO~6j}|AZe7-*sw-eM_fht%1+@vFeU#(Yt+5+5JoyZ!&uO3)njjQY z;WD(AD@PT8rr1R%1N+0J(!INlC!0s7cwukONQa;%Cm-x5U_$uG9$Mqk&IJvMvllmL zYAJxcR+XMaQ1Q!Z4wG?OY?@ejF zH))EXyv%FPIZz;itra+ppw8C9jjnJ^TlnX-K{q5^MWSB0Wo}@mj&vj{(F_(KLZD+5 zROZw(z`9Uiu7G`MXmT8)sD?<_z;JJc2_h<-^d(=B8*+VG6rEC%Nt1YaAf7RliKz%v zxlpl1VN~r+ssv!~<)GRG6?UQ6RmRF47$MT(3 zZ1gF@x(uu~kiwXdmR)(*NBW)ZYxuxUUdpBrh*AnC9arZlhz%s>Q6t((sybA1SJCJ3 zrWxw0=t!Lo+WMXSu?)%>><&3db;D6lj|HUidTM&Zg%LvXOP>Zp%wHo&LvRgsAzCDC zT;Jm_g|vkPS?d0g$rg>s4dFpVL=+J{%u7vO3~h|kQYB_i=%Kx|0$`TtlfU&dM1`P8 z9wL_bmzVl%;@dD%HGX~+Ev6e+^^O91k44=5_>Z>ov`1_INSpO@XE&6)cLtps)w<bO9B*P;QV6Z%G5+@ye4xSfgKKkCm&)&WNIX(Bm05)8i0MysG*psPa zNkst0fyIr6rF~q;B9j(X*(+nGlz6+a$vL?RrH_KT}VkgL_ z6QJ7xhs!~*6ay(Et`8qSfB5m!hd<%<{*Pb&xQk$(0qF=3}|^a*jl~TZ7-Q3|6iV5yqG`JiGtV zaVSmA_Oc|n6W-`C6R`_C?2st9{74s9aN5~UlUFWH?C7dtbajR%uAN$knWIUuDjsbw zBM}%qqZN2&=o~7r(zSaui>>JLls^=-MJb)Iw3q*&CR&_oiU#|6V=iu;sBhF>EE}xF zklGLq2W)xVB}$tZbd-_3IWJ?^V17)^e$BgNcy*D>L&YwmfO)fWQKgNFaxv-cuqcF5 zzzM;MJx&fcSUda8{_*8KPD#MZRFn>x2q%qKnaHsQ%1bic{h41UZjMj8WTT~ZYTI>8 zPsT96-WcoOSjE>nxCDpoTjVW|RP43j8|A))oOh(9HEwpOV@{PChgX}j!a-lc)HjlP z-XNG%y6l@l$?Jkb4{D-iY~55(ZFu(lM(t^(0+1ieXs8*0uL?(CwZp`qx{H zx|YQVbk&fo&(cir3XSjJ2#f$VImpTBxu}96H}*WRdeGF z!Df>tR*_WGf+5TJoO?1g#uCI0s|FUgBlkec1RUj1M8n5$k^&z!9U^U}QJcb0qC|Bb z>AYuIqiTw@RpIo_v*{lsn8^7xqe6=$`G*Wy_v)Sj9p#43DV-@o;dG)(j_exw@=*U> zOi(HYG_2}ZPUV1;yWFvg&zvNwI(#uwEE^FP1bbtlNa}QCkFqW}J9%cU*2St^!aMIe zrY3FQabwsv_0{qgG!%~_`B8!S_PAQ3Y%)Yxcrr_$IkF6 zydrm`g{K6o-P`n9I_;p_73yR~BY}C)N)Ewq6zs+&C3ogeYw1%mDVMnuU_Y2(G~sY} zuNF>lfdodskQy8PfqL5WvxSsL_4ACDx0p6dea1x)1$)#>e?vWJ7$tvYSesITHib@~ zv^c*V>O^gT^wer=UG>`fFK?yy;ir3&na;MvyLbU9YZ-DLhS<{DK4ACqz+Uegwd`oE z00zSU)~FvK_alNY%{cJBLmYS;ipx};maA`3vN`(}ZC`)IgAP--?k2&$#>D;f<^6j; z9K?s<`9z6N+nFNl;>2Atdl~54Z+QYh7e2M+WhrAVvz|ic;F}c26ut>rv<6P8<@uPH zFW5h72lKS2N2gi`w0dDk#EkBgC?CyD$4pUa=QvF7yScR|(vKg$`S|0P4;(*y%bNlp zek|X9lt1_Ff*SmK&O;Ms8HQrbgP( zUE?&IiQ1baQ{Yo{Q!K0zyEZ-ZgB50csOeAsgv3)2{`C8w^;bBU2zu$5zuAZ=vRCo* zkuQ~3Z2#GH@HoQS!bAzY>T1!lJN^!x8QguRGcOs z5$Zb9FeV(Rr4wX<=zU^Yry0hA){j{lNyDM7tEGjg$u*;So1`1>^i^A8NG#74A48dJ zuEh$q5M!Y;96j8O!g)|zfB9q$QeD>cUdl3;zur?@UD`S@0&!HCE^S3GR2xkdDoqUs z4psF$=~jh2de<11qJbcBDW5CU8v+bGlfhSB64={!)ZI@WB7y_P5hN$E6fjjHG<8I| zlWZL-#15_W84;&}URYY*m&OFfy#|zzsM{f5LkonOB-u@MhtyqLhiP(lbF{^Y3Nv7T z2FKwz6wVu%M6UzdK2++=(F~duVA(qKk8{33v~1e;ZW-@PEtp%A9_JFGnusX*96&Gx z=ok|l$b=~=(KiY{4WOo_Et72zv@*Afp1sOUhMn$5y_lm=NV|3)LfayN)ij9>XY74h z(O8`F9Jx+GSY7=|9yR?5tc(=rtf>+-m zQiZ6!%91EvB>C1q&&UU3RoAuNg~97tNFkPDa^aVaTC^zUs?^9c^NCBMQQVa2l%*l9bXNe#_Xcuq&-F7;#$sSX*J~`SVdAM#{=ua=HwJ|wX)JQqNir}0o}>bWp=pNVa5osg8gMjTb95gIQL#GsgA#( z?k|35{xu8%5pI%c1TABEpbvW;0z~O0<>MXJGX zsdZ68NC!&&1vFrcO%SDac@;v1x`#4q(Gkg9`x9+;QOAZ~|0$w+P%{tRXhw|o+}cAB z0`a2-x5b@Uj()71G;oV=;6R}A#SnCXV&vQ^KYN%nX{VE3QG?25#I=jJpVw8R+ z2Ch|F8@w~r>H+DzUenJUpz{(|O^_oSTJM-*u!vPDQdp7NkG#VF!H+zB_{c~9f5L;O z1d#W$1Aft(7nnXWS`ML@8N-0aU^*&&75XS9A@c>lz>%6~lLOSQdy5v4a|FIPM?*#R z+8pGrdwy7C4!yI<^hFFq=ffxdAjYRJAAj|$U%r3$?RVdO_u<#S{xg5(U-_Ls{m=jM zpZ=0R$5HqV0lCtZXgCtZsUes}PtlZxgzUDXwH68K@hHwrs7Y;LN(}J%uz+e}J^I!I zltdZjq`(oP;;gpmRG&uLNOY?vtjf}0)Tt{Q531M~xVh3c8U|*HX_&4EET1O-iRS~Sq3civAacY0>LGYlk6S z79wD0?Wus@EvCYl8Ci24CAVB%VPY}txJt@*MBS78;enH=qLyEARayrSQbVl|vb))3 z?w{uTlK12566DnQ(Qg4R8&geF>zSbQ?^@Mo&&JZrNHM@wD38Mimo%GY!bQE zO1K|%RBaU@Gj1CF?)p+zM_vPonl!J!mQKUt^112e!8_IYk$Bcclcg92LN(jyAa6(-{SB?fabB^+>KsIx2=LZj21fP?5-#x zCoTdmT+oj!4SVEel_Ee@=GiMYT{4(U%}Y0TS>Mm0wIbbsgm&646b^< z74jR4RdbdCFwA@`==G9I+p4(3qGqIul}XR@?lzK}ExF7>D^$E1rTKIT{DM`<{t`b} zLJ=gbLvt6C7C+}=$eSziL9WIba4rr*3aBnMBoqjm6Cx`RsZ>-DZiW}ldL9afk5>5_ ztBo(h=qD3Jj{!@_UnE{Nwl&c7t4k`WRyef1;2|?2>ma2?QjCmymRZ4=dKga~JXpc_{901Z8wvgkJRNt1tIDC9N);Wa$~Itw+5Ws&mCn zx&0Hj{9oZOAGu^PBoe~2lZ~f-rUTdR?0xY|R;K9*(r$B;|6B7SnClx{I@P*x${60w z2%yyB@9f3MT~J>pmT)}TP0^`Um26rOlrn>=@C67}I_U+C_d3w<+xmP7m{*TJ@qPza ze{OqtfrOqA)wX!%x05w|&C$a_t2yl`v=|Ny1u(gU*3$Fi0TL1l#fL7TOR_6qVd*4G z?)&XhUl#+fBBdD|8x;@ z292E+oot2=5(4wvTu^Zt>zvO~9c2kf2orItTsV%3U3O^KRSfKA-B++P)G{WeO^YW; zU5E~vKdDLUA)t#zbg6q7Md`HS_*@hnj8{-vcP}zxcfTS&HnDyAmywMk6(WvOJ5k-4 zrS|wZJu{nM6THkk=`l9(Ji5Mfw0x{%z2P@QQazzO5Skn-*0)eS;0wU-~Jnit>0PY)$*=tUzCQ4qI3U8S1JCQP|6rmBD^+hICA zka$#vzx#6U!)dWy+XC$3go;Irb%lP(~PO*(K{hit~!jM=08ukZgq0y zaSC(m4#P*}&5AoYE=Gvn0Q(C(h)q=EDGhxT^^NS9!o_)kR`U{~(PyUzpRH5J2>JF1 zJn0I9CDnupuW-b4Kuf_~J%l#^T_g(-Ext_#G}|#tmZwkH0<8<*zBVspTqaWDg&O7v zRWb~SHMds&s!$F*s%SLZCmb3cCaez67W@igA*#BuaRKJz~Z|U_L|GBuWlW8YU2>h@bxI_C6ZHYUUz2<)`1soHunB zXI@+(x>#RcsM0jYk%4d{L`z7c-@p$^z~Sb>?FM_t z5VIxPP-}qNzM?S(w0uEmv1gD%?6jRpKx$Et0Z#% zYT=mfV6eiJ{ZLhUbCZSBYaH}zc(l;HT|NJC5omH`S_Ed|Flv`~gxrlx1z>JeIx1{- zK?_qk-}YaqrHlQLUd-IsI8pX$5N8#2f^&;=lb2)M*Q3Dk(?|X{mS4E=f^FOQQ|8*@ zinj;9%6t>RUf>?U_5`UqNiMX^d*%Qugn$_H)z#7i7Lt|}8r>4-%mz}a+lxQ?JY}&? zF~xABHbUDsbL{e2*ZJ2kkj4vm-3z{m z^wU}+JRpq8va3Y{1371n88C83gJa{6p$0k71I4Q`ItrZ_V}yXo;XM55Pk;Ik|It6- zY12Lmx|DKNn`I#>Mv?(wxa5HX{QP;U{3rKKt zC1uyA)mNo1dZfTUVdshgXbvJB$PEfLDThd17j{`tc1GvR@G{1B-5DZX6Q`;Qt>#m) zSp@l#zHn)9s3^=rzXt0GcOceIq|{q0yUBz4gb=u9KJe*asbH^_!tq=wa>ONSP|@)T z#_40IksEAY^h%p!jC_tlveFr?dTB2Nm7522j(`kR7nq9Y)p`;;t0AFfm&Z=b#n!p^ zO`;&Yjv~`RXv-44Ehg@<;UI{o)oUWk&e==Fz3T*muTJ`aG{_(lHe+O^y}i16Xt0O}u5N`atU?F5*(Fik{2NTCXnniAL@u z=8ggY;6`@Hq@~H`6Ay}^+DXZwX}Q7ixss^9B7mnh=ukRnq!Y7kKZXG8YvMMHPZbSG zsQDvvRMcMZQbg1TL`6XA=xNKbfkt}e~5mKi{5o3WfUR+ay#+{t`3MPYb70t}QrSxck zWObcq128!Nljgb*&zqG8+2|j6GQkws5EB$JOl$F%UxMh2GE7W!W2_1!5c}mcJA})lGAC`QH=RIh`puv#^%Vhl$v* zP&xNsxzVU(EyXs&5z7+R6SrI`JL!*K8{7x*Y*-+D6<)oX2QO*rg^7Tv5O8fh(2oz{ zN{O+UIHd`|UKC9QT^_Dp^^y9Vx69~2^zyOEki~rIDq)i%&th&@tL9h>JZ$yh!d{*| zrV;}Kq3Q}`WGHHKkWp*rUf9WG1nn*=%{M=ZB1@pfM9*wXfS3Kxul~il zf)WHU(8dl*81@5^oL)AKu67e^F5xlND-xc-^5(%u!SX$+aOS|4jI0(58E`8aM6R8I zZDgL3S~KiUqT)fysFWHEHZFnXf{GGEJ@B280&o-v}>THqu` zK}H&H~h z&;6(W#eeme{^Ec1*Z%5%^ZgG$>16Y!@&XHZ+*&xxSsa%#)pxQ)}!{Dfi0TW1| zZB!Q76vY*UL49{02yRhua@pM0hB zF=YapdTzP7>uTak6$no#nTrB6^WEQfYrd%W0$I1eJ|W4{JZ0}TAaG#|#jJi(n+B=c zt*s$=ALh=7udk9rEK;$|)Nz!$lnwm`-6(LTt=qoOAGDMUu12A>%s3f+4%U@9eOlA4 zm2-6;MXW^izFcayd`{?qNGX}UPCd9s&zu82bd#%1SnSY)^by-kN5Q^6`$oO&v?N~fvX`cu8%-8^l z_Hi@Y?{MqiJMH3&W7T?w)_C==B}5=aM(8>$aQ?X$)B7A$g|8=nF`*} z(smIRqs?4<6cPn@DX9rz(^h_DB#CV5R#&CdvXa8|fnYA|W=ee5N)AEbAAxZ^kJjfi zA-b%VP-Vd*JWau;uB9ZT>5m<_ehPv$rl|Q>UFv7#WN7BtsR4I{>1yws%CECI`?cq1 z0w%ZNwak%E4b#v#-bV(2me4Qk^-%n*F_xy?n0{AQG}=k+={9nQ?~{-kTzM_ z3Z4~xMxklZ6r!7uzEu+0!DE4KQK^rR4JF!cH}q)SLp!!$LzZQJw8+TMW)e&{I;xQr zO#zkyCGf?eUz(h>X!ldaU+>A9BVD(&)6hqWq6x0tePHg+Fck!*u`VjCG6Z`Sosb1k z9|OMv?76L5U!`^E@$!={cqLrFWuT$3x#A|k+hSk3 zlm{lCdLPY8nv3S{FC=P`+T4qeQReQ!FoDyLcl>$u5{Qbu$mL`x{&L@n)a&w$gu@t! zg1K0=ik=;ia^l!lO<m3us>2gaC!u@ z(SvHyC$TYW)h<4$q?5YYEh|oXDbn!X$|+qKY8#Pq&uNQ5`W)%?WlRhhwi|iLDsKNe zZFf-Le)rSwe(!Jm-k<%K|D`|mr+?>Hzx<>B>lc6cyTAJ{{Kx;vU-|Iiy214Go+}L2^0iEN4m)G!AQ=vf>6H9aHM8aNNw^CDx1}-zh~8NK&!OV8JP} zJZW%K!*P>|Y5`fxsRCV^An_FIRK1wkk5plRODNiD**Z1tRh5ZC?l5NC*~|u?%8WSz zDJ#q4#hLT8#(Dk0s6~kqCqs}&=*9=&gJ%S?q^J-+g6cNe(pA$2$aE!~0%iUXf>;dzl ziL|Y<yI;C4Lnje*VE{G^O;?#=y_ z3voEoNVXi#rI+u*9L)TbL2gpJLKH0=3{aQ`vW7H5zDv2Ah?fpA0rI+m#PQO%Gf25{ zI{jom7%!=Q$g?{_Uw0sx0--?hqdyqO43UF@DI?eIyY9+1P@+|&NhKqn)Ny6#6FWG? zmM0;MSYH$Oy9;1?c_vf5G5al_yl~8o7sYw?nJHC2()s>9lk@Y?=*-{SfZBHANv}Z| z4~3XV$Ps~BieP#+UNZ!aT|NbKW=sz&t>uT1YI$p5?NQAOU;~TZ@+w$njhae2M^&kg z0y!3;XyBlLDQqc6Yo2eykluznl?>EqIiXe{`O^Vg97+2GbezRXer~_%}^9j=sYN?d&1_9X_%u^U&AJA<=I5{oyK_JE` z=0!w)tvlJ8(qAcHq=pclIlyjFWie=b*xxKxRbQ26kDcrCphNlaxJ8qJp=)g6bDRa!Jx@nJ3oZGZ)w%H z4I@X@uzU%$c=FmHyHC4h{DchqGgmEc+#M8N;o;+&2>Ip!e?Qdk)PDN*qaUf~@96*J z!;kw~tw+9P%U{<2_~9qW`J@d;>K{IQ`HV!Nk`GGw9F96vtfTDT=TYLZ2e%+-avS>z zeg1D3YCq}=dZ_tDUi^M|xkmUchDwI%I~#I9-#yn)0+E(p>Y_!BZQBe}%o`GZe0*KF zhdY_G=(l?loQ30nB?B}}j*ONYPzqkPd=jM2GeIx^8KSyD-~3SpEK=TH6oyYIgLJOAfD{-=NZ{a^ae{_}t1-~0=|{^MW0d*>f| za>9Bg6sa-C1z$@YLx}ADSAM$k1);H}t`c@tXi#Y$mLv3+7Nn_hcv7RA0aEkE(1)g- zgjv2d&8cT~^lb)+&FP?x1i$l7H!`8xFUApPmE@2RTb9I4Z-!#5?fw7b&Ee!XGoLOK( zm*R1Zs!mjkt66`|y;AQpalNuey%j79u`$E7H+%x@w@L$d_*$y!9=QU7&bY=Pqc6`X zpg}v%U!SPQ%+pZoRjB?2XqcYE#Eiw@BvM8NF`%d||;0>t4Jsa7LHB=%?!%c)qzIfN44Yg7lbE5nU^cweMOum5thz zVZmFPR2pSJwIlv9(+l6pN-m_~~Ap^H90c##H0NW6(6qBV{Jzm90ZUuB&vc`4X- z2yB?SqE;_I>0?v(X=Nrpc||VRy2Byz<6Qo|1k zDkkV@JZC#bS2P>^xHN8{6N*R$(T);Im4m!8G&7cm45}#A{S^l__(O@ViPSk*=NPD& zWJ~g7Pbwy58a3^+1Ya!y&dl~y6i@5x3iYqUKHlg#8cdEH#+f(`ClV9*I!PXG@i~+T zM)l%kU(joGLDSmdTpU&;*eAV-!BnPW#hL|OOkAwi+*MrLvc^~2LG_@g?&jt2pe|{Z zIbvXGxQB~Gl!Uo4J{{YQKns~T7zra{uRxS7=jo+_EW7z4*s?LCA&iM`HJEql*|t@c zq&3`x?9Lbcc||KdfSoYAV}?jKSHq(V74(o@#zoIwKc?1OZeK}ph}{WH6L+vy9gQN3 z(PjWvb~#dlGonrf|GIzsG5eZ>BMO;!7UO#Ng3JiM^GgVnMo`w+=agp!-l$%pzo00$ z)^fV2%0?fWw^jIDPO0JPSHZ*@~Q zrxKQ(+-X#(cyp7r#5WLfM0@(xi0Cu_-P4csT%Rk+XX(@8Y!f7>jDDTCnAYNJ+kfhJ zfA??wuYZeo0RHX2@NfN+EAjhxfA{bI!N2-v|HZ%fm;cHi{QmF%?f>O(e*eSIvEsQ> z#9x$TW*kv(UI;Gt$FWU3skf?L^X^N7iU#q_6D=!cscb*y;vNvDI!1y(&8%#q)6LDn z?j!=4Hto?xZMsbu%tb>8lyi)m8(_tBA?QP%lyVl|n#onHPv7PIXsN#O9<8CoR_Iq+ z#w3U5W*)w++J+uQLbQ}l=O(3Di?ZP&S0ne->pvV6 zmnxN1&vMG@&6}putJf-WpJV$y!O;V|fTn+8pnAoS> z{{1e5Xamw(0=|4>PTb<$x!n>m%&W2HBDoyAnN+>NM1wX1htSZdP?#@U+l;C9-b#d@ zzQiE!XbT+$9D0t1P*V3orW-hOA(5(-C>mxv0L-B5ptQ5#F;M2aZ*q#+>ugLyzputt zz+XR;XHZ0Kl8m-rg;d$i%dVcxq_%x8w61^mW9o( zsJd`3$}$kTgg}u0owP)5pDRYUu~1lZxgTBpIDI#r3n2fmDJo}5j6KIemD_oo?FwIn zsa710m)(R%W4?^VehkTF%1}k&pzcOV8G_|Ot@gTZ+kbUq!T4*5ju$XD^4;*f<3Lo1d845)d``jGNyhdOo9YY<{h zBECXZFHSneBkbM&=%@AzqD&;yk>kPH$`TY7hp;&cz7g?qpl@su>gfNa>)n69-O~E5 z+50)|94U5EGezOJ?I zb>E-Q_wX1Z0?vvZW_?(GrZeE=Vz_HvToHz#J39I{&lNe}WGRon{sr3L(-{vc6w`xV zm=PGaoWRYmvK|SGbyco79p-DQ_(I&((!Fag99a#A|H+}%H`+)}WdozXD=xRnw3-6# zZ<%9Ge1kl$ki}w6d*YLBKRUt2=XX4QE@ALAWR~) zLdBiVvV{pCfvaKhjKpU*;P|l{gMQeuU$^znf^mz%4AwENR$wHBiZoX<(C(?cnISMq zn2{Oe==Ap!%sw6BBJ%MyahtZ?z5JiGdS?QwW(k$wWN}MpKjS{)Uz`x<_EpIm4GY!r z%yPn_NPDp!3!RC=k4X#m5CX_8DIX4H$2Y!xf0Hn3UYL*ltGmVEFEDvrP1iIDH2Mgg#x}wHk47RY!uv_c#(-?t-K_Ia(`=2 zG_Tg87Vej_%MP?n+$0UP;cLw)!&FDGe9kFuQZf2vfQ8e@f@ms7dK@@K@EWovnqF@t z#^F5%r_~D&xeMSgsmP1pLxGP-NS{Isw!6r(Sd3M8sbvNRS&EqeBn ztJ6<*ryClB^Kyu_5Oz$jnQk?nh=eFh>S%TJ&g$_YHy}I%c(Fe8+!(;Vpc21 zJWA*ci>h6n?zZN@NLk%W+w>Y&SF7Kpc!E-=g8>b_qn9-frwKVLkH;Q$SzkkaAGhOR zs(zse&Hfc4tfRbK%{IAqbdcX%HcWraFUp{^;2B}-%b($6Ltf>_iR7t+&g=l&0UIH# z0nYi@(y>O4iXIi!z7U7(xmQ8;&zfmUHrbg9Zi1!q*8kT%X>Iwg@=8SmW0JK#L$k z$I*Hiz;jSWC@!+mGF{nFAuFsQnC>6pj)0qqcES!#;!N>LNSV!xdbbWFw^yOCS1W$T zoxC0KlO*)b2@@Z6J*%BRW1n+lkPqvMpRmH|RJOGv2Ib|65IGFYD#+62X26gw*jpk= zoLq}RrhcQq>(y;`T1oghnQWDsog_$i%fRqs!@b?54hwC>_pwAJ_i&jM%z;14$~z3L z5Mmz>)!pqQ?VALix%-O>D0FUq4c@(k1A&YatAs)M$nHZAX@cjEyUQ5R#X}omjsgO# z#Dqh{l{-rxA?$;~Rgj=%X>`ne?zg&EbcW0+|9X`&_{C8sr~p2uTdzO_^cJfG0iFCf zI7P7VYj{kG))od#75fNnh3?&w5Ds3;N(-`jq}b1yvq z1ApUx{;S{rKRy4(=Xtv2XMukH7yjW}-}&x8^KIY#WncbP4`2Gt`}dedej|VshAdDI zm##{%eGzDB8{IfYWmPn=yJ|RuECrVEmD5TgtL)5OU`z$YQhypnV4YY7I_6AhY-kq2 zRgc6w;51XM9fD^KWX&aK#o2R6xoimpHjPil?Sta>l%SY_i37OT ziz@LH**7F#l0FJ$e3o2nkG*7hL|wm@6dt&YBu4O!WIYtUNwd;io@#7fNEqU$TW@t7 ze~Lf2mI9JuM$L^{_?4>Zp-6hh6dOJ-IGo}F_VROayZJ}S=>$c1B>rel*|E=nZWf+) z3pX5c=tdPz>;ROL?BwtUa${=iZfYv94+Di@wDne2g|sZ#yMLK1s2(ZEPDR5D1>MXn5n7jFjH!$p zk$#Z8ifp#LtP&cj8QiQFgEJEU>1crZ;TVloSs1heR}gw2sK})*GD{JHW(KLeBWyGU z$}@Ag7h)(1Z-796(g8!+@nu&`I=s5V&#DmhkT0e#Ca<;4Q_yM`^f9(N%Giz1!st~E z^%MksEF)I3m_>B80MYua8QDY4RbMq+QB?^@ zGnR|q#9&n^@M`2}ClD{>rnh_0wTiVYK4%%sbZ}0B(TjZ07**6Fl?2{?w&a2_m@gr5 z76lFsIj&=rOCnY{|&rgy2%Ffw3(OJB+XF zxoQfd&605k052w9Q`)nW6N9oco$a3OfFz9p&xJ_&;d&o)TyKUhl%8IeO%n_^OIP-0 zeG82rcW7@c^5JQjX#HQ9ZPU#pzfEVui^m-4`!Sq<47Y&A!c_}UU3Pf!wqOdydgs?g z(ZfPjZhsHttP}V8tAwYD&Y@VCHT&Ggt2t&Q?O&R(dS>LJ>r@df*HrSUc=00z#TyN% zSO5S(07*naROT-+T*%w$H($nRt%JYixW(r1)&Sbbqusg2Ks!UtK2f$D&Vrl{UcyXg zhdi{t@nzx;LA5kzYpY;;5Eg zk*!*EoMr2;u{Lq`s02radZ@XnkMB~{OoQuuI^rtLkFy4zAa6%7>G=;6t)#N3sb(fi zmH0>o&+=mImEuR6q7|wH(-QjN>40`z6dDj2%jW7@ia=7MH5C6!QilR)ZpYIV&%qhm zm7X>P_t**Y?gOY9_L@8WgHtg3fd^3#KPgb!*J>s-RL{b)Tr>=n?2#4E!g$J&LpCe` z$_0VM;)sk$HC@-E4*gSxv^5GaA!44P>&Bp(BRzlTUR?cktBw4weLW0>jq8`~VvE8l zG(OT@wps)%PT7lxL9~5R1w=t(!wK5Xu&55D@w(CL$B9SLM6XszH!*C<%)NUtKyyKQ za{s}@IvJAv%%!Ig0~Kqk=sDCiEdd$&Jyq|tqHK>RkSj($bln8DIiFHd8-CNtvT27H5K$Dsj)A-B6yih|*ciZ z!zk?uQGR_0goxWpNA_PDkH~Js^nBWfhD)o%s@-n|6z!bN#SYNH-(TN#!Xhy4@tJ0aFwM%MdBe3K>o0;Yu5Lo2$ zI~D7s$uLoFQ3q5;1LP=%SMSwA#MSHg6xmW3%jnc+mYfcn0Ha~g<*4ns3Z}|YQoL$_ z*L6*XtvUD}oG-Z>ovvse$_25}E)qcw6HljgOpB3Olw7}-9ZAM5`!Ga}d>%u}CF3C! z+$!r|bWiKZ>+%<=Li+x&nz@)NM-JJJUtNn-!w&&Rx11ZATa?(PWJNTYftJ%!HtRL_ zfmsAC#Z%G`+ATzJsD;n;Xl5n=>KTWHnmq%A*1QzVS1>>1#J3H&zPdr#88#99Z1Stf(X+EM}G44 zzB%)4?+ugqLmh=}nfh~sObTx{y80HGn?TtEB-Thqj1b)j%Gz%fpzIQ2mSc8^5F8bb z6qDV_+*}zVA(l)52DhnlP5IxDdBbxR^L8E15Nul448|XB?#H1Z#PMfdSX8Nik8j@atE4NMOX6 z&I(fj;-;q!_0nEn2-sYxR1b9=ta%Zh(il@HU1?VU!9z=L zK7QqsJ)5ABYLeGmlhaFUn!6Yxh6xBuYKA!aP#G|`v{>yk55X_kJ?mSgMCap}V(Gt5 zbS$7`8Aw(ho;}p;*>iLJPq^$>?qN`waXcDW(!4Y*R?wP=bG#ckA`7g%o3Z0#K8|SE8C@uCMC__2 z3vuS7jS|oHcyYMr`*MlxvT(inIgtA*c82UkG&T?SGLf}z39uIXmT0Q7En|x;u>vILW+p3Zu zU7q-RdOc-KhMt?LW;Gsf!vaLPh)RF+L*fSk6wIwflB@^IksZJex zTwGO;lWW1|(9h|_ZIv=ko`!?7>^(SkaLrKp={UL0Fp_yTq!`G;=ipRpV~_vD?G0~u z_PJ+%;0J&3M}Fi3FTU_R<$CI=r$6%Zzw-RsKL1aB-8a4K-M{PM%P(1RYvZKQBHfb! zRdR(ul}wL{Go@=0S3-t9!RwXHc^4t!Sds&;sfSbDbW48@omQEHR6s%xhSA>TID=ja ziic|Fnj(}ft>9*&eXEU8&T?G6t2V>MLWX$7@skbyoVObs254eVng|5pYXs<@ zrfRv-QD5XF0J(E$DBWS2wGz=!lqNSbgQn)_Rm?l~#x51lG0&V+0t;Wxt+ND1tbCXLfoYRW*ssE-<*e=CPns0x_1ixbP5Bm^<1 z?YaUZ@|6_lEI4B5Y6k>_>K@*KViEX5x68s4m=f@U)qW!56+O}9C9#m$1zjl$X?!`^ zFt^F1eL(JL&q=*j;43-0*_)$wkn#CpN=3)T94-%}fEaYjGuuU70E76-Cicc%Blh6M zkcx2U=hhq;2|30~4YRmn|b#|Z3+RLyMSm`zKxT!V8j7M z1|v;}anFHF07yW$zb-GHhTV3#gznzG=d(Uf)xHNnq5%?IW}|1WJX{LmbR zkyFXbE*D1bzAZIZRb}AN&55JsQYg$-H|&utYdbU-g6p_e1hA@@PM90O?1!dVbz4o9 z^F;_}8)WB%28qc z(4pcu)kwB%%#^4GmKH?LdTXB3o(F?dr7BNoYp@-AA5WXUDbnO`1j{%kvPm-`*Be}j z3K(_pLY0!=KGlu9`cCTbL+}a>jKjngsa?4X9Qy&FSDj){fH z!J($~)R3(tGb+q}61nW8!OaeerQ~MA(4PPzk(7wE0JrI@jGGO9oX7|{dXNv)yo4pl zfT7zUNKS&(k;9}X(}?R*B27KJJR1;+G(|#D3k#*A(99|WG3CQc-N}}d`?+~4!=N)> z=0|M9dt42(4UAIuqlv8nO8F}Y&Xj~3@imBxwO5PF;)Vs;yLv(E23tLvsTCZDp6FLnA zN-^o-7h#1)Q?!)3-r-XbltxTIW-ktwLh*3*Qd?3;GmpgyahdB3>ZnuP zxWJZD;f`RA(p3;uLUHQ$w60@aRLP`a5k-gf`VP!;r;X0Q73D;0Xf`r*#T1Rq#Xrs8 zOQUA9oSXdZcr{-cq z5tMN1Z@LLgRDLffSFCjqvRgrWpMvA3IWGZundq8=^}9nOEXl{rp~+v73${9_b{Esq zm-VNe$*(WO6iBUj@w7fonGV-ku@}uzJ0d74v>#2rW!ObKv)vK6cBa4Xg*u0(ulLup zNf8Z`NBhx#KKk#;R43y2lSOZM;`tX}_+S3k5B=!-f9$Pqe&PQ8`_Df8^#AeEUw-hW zFZk-O<@*5ddHLm+m|DJd-(wr=ttcfTbGe@*OiR@YwbWN8H_)XmJuiz^s76ajh}RWq z!YyK}Z7a+%6Ya7e!X$@ADm~iVusk+K@OHtur7w+?E=UmyA~(D6FGx6gUi$`p)(B?1 zhY0sNK3byn5ywV}J9YHB6`JPSOO@|Izz5g#-JuX4d!lXa#m&vmsQDUhox@}XYNS~TlcExgDv)_Fb>&RRqFB`k2YxK_a{ z*Y&AFfRi_;KF}y;JWyFSj5u@k*&IbtB)iM=dYH(3ve?Z8GF%Z}(!37Xaxkd{dPh=n)tfc!P1R^2mQ!1m|%FsGLhP7taKw~Rd z!8r1(J~{P6^WZI!9cLyQK$M2SUh^(na9rDa5Wlz!9#bc2ThJ}2Q5UFpYihB|A(})y zzO4JwaZY*eb}RR~k#!f89Zft|h3cu<9)r6m$W#l7Z_hDRMEzZV=S~gl4HNr8iBAP1 zCZ-9~EJ_!$aJjiKOe}OoblKGN3^@Jh7?WP^o~dVU9P|%rt|U(kUq@`<^qw8uj$VY0 zI=Jy}ZW4Hf<^{Z7zAV?8P@`@1W2g$73MEEMb@toKoJK5yJzedRXg?}>iI z!@(ASmFdzP)H#~)g^RJbN^-c!m4;|^mP@}!GuJcO@gyI`kyso)3ZAgwpgFov1@s#0-Y2w*6!hZS{+&nxwR3U_YSttNgP^)zjc&VKbTblyj`{|hiNed}R?IYE;KU$< z%SzzTaGft5g_I_hhv+TnHO#NUk(fhk!*g2FxVBJiO4u_v0-DnS@x%px<4Jt35)81# z-o@y73pOtfY&v3^*Q(Uhm|B^8S$RvC+FIIVaUKQjDnP}$vojYBja3b>;n~qhWhR7g zi?I$*jE&YqngX@a1**%T;KkD@^2XDIR5df^teuC;?>Zy6$#lGp;dxV-j1=X`W?iKj zIgbjKc{yW<)=N}V>qUoZY3MDb7rMn zoVk8k4{*~4F1X0b;TpS?fA60<;L?Tdt_L1(8K{7>F`Akmia|&&FbxI^ureAh={>p* zd~Y)xEDokD_lR#Enp;LYXc8rjZt8=~$!xRBu^^)m`i!s0#&h-BMCE8KBGCllK*ox) z%v&B;jkHE+ln~kkbkuj~0;|rZGYUf}L`U=pbJ|V81YNS6ZWtIl|DY_g@8TSeRZu0K zOijj~Yq{B*AS>;K4M@Ip4cUZg`CD5?KvNT{;nTWeee%SZiwK%#U-E99}B&+TYh0#$@LWN7{u zJlZ+$iZEWe1dEl*>}g>?ROIR}?GY77x64Daf39V$aZ>q^PQ5uIl6gj<3s+<|neacZ7jYPjg7 z=^5Ql$V5ib!_lf`*m?_=H!^=Lo>y}iWWK`V?C0p?5*&vmWJJ4OL4qpvHsPG|X3L-D zfb^6WNhAJQtYR=1C@B+(IQhEb#&3dp`|;ZG*ahmN^#JrFhjDzJQh5j8!A9RT-e|_`38H!zdX;6_#|Xa;ME$~{4&<0mutgX z(Rj#L-kSj)PK--G&RbWb>~lL zL1<2ir*pRMwsYEGNBo?fPivXA?t%q|x#KtLyion}#8E%Eco#JGRPssP(TUmA*dZ1P zsO6xZm6COT1cd2#CG^v`DE(QWyZ4@Y?&8sZ+#7V(zUwP-} zzxV6E>A(8zpYw%%AArJyCg^aj1=0E{+{&a?1{%&X$6dBlP7DCUQx$O>yMAwuB!=3I z`lsF!XmHuIjjS-FrFib12A#V;2nqp$uPYz>=qR)ldO229ke*&p&dq0_s+~<19s<_m zfaiITMRIOcO9?R;`P)A&s2JM2q(0^t@FtMq@(O5h*YN~*&0Kd}Rf5Uc>)J)Ms5eN} zG*O4l@#!$kcZ?(*Bxvl5{_k#}1h3s(oga!ESG&(E7d~5{OP|dGclp zVe1Fq1Emnb&Mq+R6{2;&V3w?IcT=N51rq+h%g&$ovO1Bi*d|pO3F~SX&0i6gqQoTv zVuf}itwdn7j=bd%G|VL9UKdzD(vqjpPSHjuH`6e6F%JP#;{w1oxg9^hIml2s;n6B( z#3=+k4&F|~mI6z`+vKK9GqTaqmfT?GnaMhemMHS93!>5NTBxY}#X)guA!IM{V~B9_ z*3N_{ob0WOwg6O|bNLrjkQ_$-SZ-jzT??AvPY?~nv_DsPWFFE52IH*8oCHL`f{DjG zXFC}210(0}<|SR4gG8Xr^2c!@k8G4uFQ{7D;Ez2gsouP2vZG!%x+f#UU1;ObFlMTp zg}is#v9!GQqr7#BJn*(+swh_6E&_kskj}0+ipY= zuIY%p@op3s3B%!lv2?t+Y0JQjHAEGHipW?X-)yMpUS=YGs_Y83h87AMIRqgoI{~sK zNydV3U{G{-%573gY_jN_jZ&S2INm{vAu_Ao=HJkWn9pevhO4D8cjtkaeuRUORtD+h zhvtQin{p_$ z)4`Jfase^C^W;6A^pW4;&A??3jvV;%TP6p*`NW01LhxI|G_Z`z>mtmbDwpu6&K*vc zRfdn>E%<~12j5-vrh-mru(Dc%HUH+t(yhqVlabL~7&}`#7s4S`v1el8?P2PaHoHzh zw%Q@Y8*_mDSqn$TArO@kd!PJsBK zVsTpY;w%`E>FI!|H@&V3+sJn;F3T*rGZIU~(2Qa489L z>3aA%M!HxibwrnF!46<4z#)|~?Xa@Uo@mFuQT;(Q>v%+o)ie9}l%B;r?zp1PVuzTt z85;>7GqmTOymUP&?Kd@*IuRyptyIqN0xyo4u z)SQ5#Rtlq*I-6V>q4uf-&Cdc1!#k6+;G9h_iWMz>Tnu6zLV|67mWG!@5Uvk#fYa1w zhaF*j7`qgXK57Ee5qfQ$4~8KNYdBxhSVjdxfMf|`xo_BkLq%JB-R|6XaLv}WUeyJ& z71{~N#Xpj1;a0f?H*qB95L47vUKRs!-!R$GOs-YVJYmMJW8L_QKG@bgSG@hnBJMgq z)Mr$s4F+5vP(=#@escmZY5FT+#XXl~t^@_z%(mZ&W?bw!TXUR2D2$3pyuwT^JRbR( zNT;1!%S!8OWC~RZK>53H@|fBN|9doR9|S z(r2RZsc98hkf18{m z157>6^~AFr)#7$6s$4=}a`-I^Vbl%E(c`8?oR0Nl4ZZ4hB-IR4LfAq+{RFr55mg!7 z-18`FK&o4gPKS8#vNwXOC!=k{KvSfwjES=jryj+<4d_gug>93ta_gL^qm##oBOZ!5 zxG~zt4O9BrK&|-2K9&)lCmx4V7`k6GrBx^qISW*SC8m90if#pVDOslVLUj7woo!bY31*UjN!`Q=tNC8?g8l0NRch!tZ;x%pPW0wZfrwcKg$J9q|CdVUXtYH7>Z3=L z=;>#l{p&yYH~+T}{p4r8{l)wD?mW2n;G@6rtH0w5e%IH2<2S$iOWynNrB5;&cY5DP zK|qj@O#)`HHBm=_`XeVd2S)8)Jb6ZQv@4vqsLb@|XHP_E>}VRxWU)#?v9)24hn3Q- z;V^*h)o5jK*;E8AMe?!ZxQ6H|D~e7UonQkxv5so7L&1u(oNsKz$E&!`SV#dFK^jO_ z6SV5HSppQfnMX!tMu}TdZW#+~D{6TjL>*Ou&J-ID8VHqLDz(R-3JH;2Ci;NDD=9K= zo^Cq zYQi?cF=KIGXL!PJowl{3VFkElYt%&Gbv)w76e7{ApjXw$0E-f5V*C`!S;Vfbb%<8} z>;v#2lJB4(hBjZ3+3(#?T`mx@SUlYG**&YSz#N+5`LL$WJuspdTj7|HOkF*KM8TEf z-}iUz6f!Ac{oD+*gE~^)??9Oe)>~zom6&qAR<4t5&+{iAaucovb3{lkLsLC7hM|!I z_N}7!6599;(#xMM}a6 z!NJbp8cO!it)o6Tuk`7{(Yx24(huL8i^@uE%-}neN)lPmI>!3jJRVN=+i4|k4vc3M zr!$GEcD-meY$IaiDydZ@Wv88rFmuE9bgAe!qkYN{I#Idyz4fxzlMHxxu6R_2D8(Dr z5U-%uxuTs|JL!OJUuO*gl)WlD>TWBKfzwT<9MpKkELpu4R~e~x0ykS)*icyv{l)d+ zzzdWl4Qn37&o-=JWQ(J>4iP+v9*!i`pN4QlKkdHk5FYa$xc3_b)Sz!#<$y?)pT8Q^T`KSJ z^U}ocS`J{$aOtH}RlF#BliXAOQN7AXKSWSWnqUx$1!lEYdjOdd6;6O&Y7GYwet3pE zC12!!mmh&&p}G@j0aNYchLZ)>n`dL5m<3Ve9&0?-QbphW4<*m=K1@3?^Tb+&+?cdO z+aXPi7T*(3-lKeRM(hvtL)1?cG~%B#CEgirK0K1;p53(&P!B?L{hjYvSta87P6lMb z?)FnZ6`&f$a6c?WUI3Yff5WtJ7FjO{fNn)S)V&UqXs>fNS~-cAiNfQ%tk2bg4@Q1> z&ClLUx{YTm;`p3T0f7)(E?7}W8HU+SUKP&Jb&}Ed0U1Vq(B&=9Ujy*b!zTj1#qjFA zJ9qipp#Rw)u{6%tm}ERhVjus!U8q$Y4QYum}fSeZrQ+mKN!>(`c!? zC#$^9z?{0wHWj*13XD8zc+BZFU12Cy&TQguL`O9hOSE-mBD)l>+uJF-c57OjOzu*d zKg0C#f{7zduA>Ycd7;j5C&6gBX@5xG%0;?Z!U5dei%(}_Yu`K=t!j%Tbm`f5ae!$EdVWZ6J2_-MZd9I)HTJ zy8tOCbF@|%;2fPmLbsBPNhqcSEZt8D1%_tNbrT~&AYVJ%UMjwpLpB0hkEY%{gF;G? z<7hpc&7fu}a}64q63PRFh*uj?U561}uFZmyW-}!t1<|=+9UlDn6TlaC2BivS>X?Pe zWhGd7S?a`@U!^lu8^Tp%T_d<=3~%!N6Q_s92xRXe*JT<~l^!LJUIK;q%)^mNNBr7+ zrFp#IvDj&J8mQuqgkZN#BG~T^*Oa38O8<_1bRfHTtA=N?0VnDltM z?v8)4jD6A2TpS4~Zs4_;0VB8Nc|*+$Ao%rUI)CI#0>O2al!<@>FOMrb-4c0r5masa z1(^N%bLKm%4c#F8!#HDvLsh@>Wlb~Yu^hDlB?8VKke!0j$}Joq(~G zuH5{LKGGl3NYw?oCgjb?@Cna%u&X_d`T0OC@2s}T+4JOWJ1Yy`D=(Ryti z?1fO|qA^O-$iSN`F-@)lo8g(LY%4|-|?G0<{{}V)oK@2uMPL{BY};Rs*vhkc{+E{ zba)jr{#ve6VtTC|%d|`}02R^7!xBtVX#(!(W}%bN>t^Stl{7)vZ#lk7Xar3A@pxyCLRIZ@zbRkCn| zDH6|Zx`cCL=sRoc;67mp5f(TRJT125yK>cPNUrqdBkOZrIkKwg7JOs?^5H0kS2XTk%bad=zKlaqXm#=H$qH^m2pw!)e_NE8y6ee zKIg(0@tI7o`td0VqhW@WZ)ITT47(}CfJBUh;rS{ZM>fud5-mGN^o!Q9Rcf)iy!Cl3 zaa+q+OScx#Fm$ck%wZAo9*<05COuB!2{I`0>G>~Y2Ebf>jCAr?wJahC%GQil0{W>P zNId4ZGz*uZeun1DrW;?l+gcOnJfTQe!_wDdbDfhZ1S; zO|KZ|dpEY$_W`RcK{#gc0;>StmyaFk#us(fgw07%Bh}MOZ+tK5_3!^dSt^ixH?9#Cm zBBwb8ZK0fO=b-K7sCH5}ARYDRxSF=i2Gj$dnLvcBagyly%4yrQGHC>*T@l1Pn@i!m zqO|PuOHq3i^MeK>`0_nJI5i=fQ6#}7Z5P;lm@;^MT5V?pL@~atH@J2XI?Ucn^ky-o zYIUAa!H{XP<=LhHghRFUFy^J15J+F2{X`n~)v$qp$oLG&lmtL5ewN*O)f0keh{+iB zOexg$&kpL2Y#dMQzhrUmW*32yprEpqH@$s$udwAuRFEBdy2J?e(!w97i4sUYTStf4 zZ<-il;HUb2TfldT3@pM*!Gc+pQTii2JF9(zq%*&!# z@Z0T?N6+HMYmIZacy!Fyw)nnLbO>M-JoDy~h!j9>rXIZ%(tCFhnT6n)0_r&xBA)~L z?CR0OM-OPvJpGsd^85bI-}%Y6z3t81mF_?F)K7i%m*4)mzx$7W_1AyaJ3sf~%b&P= z_ch)F1ab~R8L|2l1ra0l#I}a3z(nybV-4zP%H1E>lfCvPFl+{xRevO8a;!3Eg)`Np?5-QW{Np1Yu6TGlo*Gj39Ce+qdyeq!71;(PubH9_hBS zOVTwr($7rWPoa2cX;NydV85O#y_K^&&^SC*)2u*FP z#hEIt@Z*DHt_KM3Ch}Yc21RXM4QEaZ>qV%NCIB`>G1#O@WTKg~&N`M%673g6zuHPY z?F1gvYOF`YPqP=JfSDW%-Ljq!LmByuE_LmV<;1y`QElPM-UvI7$Zg}zfhE4=wguqo z{Bg7eBv=yxR8?9qln1H$?Fw!&{r-TydaCNqv@{igDVkZxgtNs&-&dd> zVBvu;Ev=+|?{ESe1;I9G#>`sPDi_?a#R0b_8kN7Fr_%Y|6~(AzU;9+=@C=}tMMKW= z;YWx3RF5YGnHZNs8x&S#;Nydf#bDRHA}9SGv4|di^5uJXA3S*a!FPQ3pMU?4eBiBb zeSvp}ckbN(sgL|$U-{4d%ir?t-}QN4_{IEPAZl>uE^$lG+FK6~YxN+ex~dUe7M5l% zDkQPGK+m>GQG`|{Yr}MDN{|uISO#tm5ZhrroTnlkHLV>&ND3r~Do#~Q8;uRMxjlZU zkDVmsbxd=`I~TpF?>>MK$8??aDEKV4g{^OwxgDB80G*j9CtR5r4c9?}*;Mjv5xT;Y zyCCPq6xU+}(bZ6mXy7eE(8?@Z(H-1cY$Zk^ZbFQS@+1p}sWj;rRh!C!<+TFlHeVNA zp!EunPSB+~_R&!h=)nv&@70#A$*9ZohLIY0Z%Pg~4Ez#BJuPmsQ3MSFy@>62& zpVpA@GbdgWw$*4Ii>iY=6&kyNBs|FmYt5(CpvQ_rNfH35&RFO}w6XFL&88TKW7Jm92D_=x#oAh-y$5Lngs=WRkLBGEE??AIfB z8;HJ|8jG{Mh!AtmXOjFxhuD?V8QRJ>fEgH5Lucex4jJ7A~LoS(^5! z+<8MH?-oEsYlp_5aZA7eE~g zO8;y}+JMe6GKunc1Lz&qEi%~;vE!T_|JXqN2aGBTVut}OxLrkTmD-8nq z*;`kkAS-^;!&viUVaSnt9Bed##M0@)c$C6%xR*9puPloPPYEx~sdlx|R5O(<2^+8% z_lOX%XeKsr1K@|Mn_E+7=(qMbhP1fPMf@08_fk;AJDjGQlGs1bIa5)4_|B)hvxdFNDIxnx2q5Nr|jN zRA_nivIaY8-C7qDsdyI8@-=92a#2o;lEP3~kg5YmyAAZ5L5K<+qgOt*f^LGm(SQ@F zF^EDOWHM**CU@5pM*n2?9TLG$3#Rd2ay+%n0ErG_9(T#7MQ(3MXHmLzQ#U(MbRl7c z40G2Or`ER{k=3;(qSRb}npIW~o8!1!gR^@X-MAnd+&0mXC(RB6S=1=)^u3FFnBqu@W_57fL#W)cZFKH}zhj7=&dilr- zSSt8ViXS%$?ZAmI5i6px zsH>04$BtMpN6uP$GPJK|DsQi~8nCFe9R2}EU~yC$i=z1~BgWk4d}e&p>g3H@TC}mE ztJgS8%meN|PObjhX0O%q%6dxQrYjHw0fSc+;|>9 zPF*%*MaWuSvJ{`S&=i%nq6k)GmIR-RI~iU}yzqv{SxWfps04Ut8Ycym?mXl%43VQ* zIIrbUlK5%wg%1N%?EqfqW{9~@GxRXBb}e(S+%Q`yXsfCYF>w>nHM~NiabZiN;HsT6 zQIpC~t*1+c<jZJ8*I*%5+P~`r;`K7fUE=<#@B#KdK{#)vKWJ1O77=( z+XV3j2Lr=;WTX{F3_v!vol_d~mR)vZK^TM9Fvj61Crnb+Gsl`T`DF|;U&pY#Hn306 zV=|3(vT1{~npFwoF?_5KF*6P6G|K=)djK~a1Gv-y1;Lj^vu>1P!ffL)P>-ms3SEvx z0R)jWta}OM&n(Z{=`+HyF(ffYyPDX|T}o=t>K0F4IU3rydj)5hM4CA_F21cMRhn`T zL0UQ{W~7EgQjYo`Wfr%?F>vA!K^$E-8_p4W7!03vBb0&qvgefL5`=PL%N(;}cXHpc zgSm&tC6bO9%vWBfSqdpIW^l|y6F_?obv0XM?Y*vr*}=HB9goT8sK{X%>1GiVJ)C9( z8NtLByN*v`CtLLx$cKk51M#ojOvLR43yU4?(^yS$HgjW@*5PVkX(YP{jP$6`x3IeR z09Y1=uH~2>UnQz9jq_%0Md0!{9l^~tSQ1-S1VhjkDYo|{(bQWL>u|8=*>KU%-35Pd zVESrV`85zYEMXeOgoP-qnyWQbNLyKq3MvCSG(K*WdBsQMbDTn>YQ)270j(o{)04mg z08xc7V$}UnhWM9!+JwyD!LEiXpN%KWLc9A{S%`526|>3(ED(oWmfh@ z(tIn~8i$}MUuG>VHjS^Ll8Rm33LGzBJg=1NJudHhPQ#HkAw)mL<+-OEOd=S$we}n# zb(Pw@vpK>{9<=rQ4L<|{rpcT+J+_OS1`52?2<(37u4npwOVSU4yMDYSik-O8k5E3vPU^<%_Tz@z$ztB z@06^IIAXMlFZ?W|s!j;a>eS6^64OD!(eLqxl&x~BQb_9TUtV%#HE!;6P)zT<7DoXR zi7M!|fUQh&d*YdUZ}=Dg*+2N(-trb6a7;D?)6AEv;C1D)4xw$9!4n^7iAQL(l=wRJ zr^-I2NqGOshFSyN(!f32(89X56bKAW+O?H)i`;MYIXk-*o25wp+@Q@%n1e)aOj?Gmv?}UZHTASsLoGOY zgGR&1k_*9CjuOSSfs05S0=&wG5Qz&{6@(uDaxKV);9x9I4EeargQSTb@oagJRAWXh?IC)2c( zXeWdyR84z@=N*e2ur!R}I?ypjPH4@D(OA;0rowqexUMmDb&Eh#b6WZ&XO!76Zm$UcJ3#}=Vq^05I zt-1eAj8WbKTgVx34tT-%^*f+O#+;1o&;adW)KDd9M&zb+iU;0ZQ`S8#Dwzs{B zay<3aQ-Am8e))^v`}@A`zxXfT`nGpIeEB8*^I-n_hl@Tl7bV7JY;}AYc0GAIH(B6E z7Zt`H$IB1&q&az(jGd7QXL&Pj=-&G9C4aGm3FbQ!z5UB|cTEsSC0L@l$WBC~wbfri zE!h<|ZpN}Wv{Z*Jx|7+?7vS9U+@&ILnTl#ZoFi1!OQmWpJdVa3d6&fs0PTtUcOHD| z)!+OV{;99}qguHu+7=r@v0sLcTtcwqXId9p!?Qj~ zvN*9Z_p?;=NRySW4w_}}VERr{@mj%?j%K4GX=BeiUiqoco#?tXYE`ni9CyW`xuE1s zJK&^tAem5s^xqC((dk+p!^O!iG!#`T?aS zGk>Ai5E#Zobuh-VF!)V4$CKc2F5<+CX#P0~{#ycF0k8K@8VPTG4a%17v057kRFA_t zXcTbUsJr|CAKM$62I>6r&K-re9#|O~)DkK{<=^XC=qMT5GOF{n)i^r4yku;oVgZ+x z2%?6J$oUW*u??k_k|b@?g=navSQ+Dp&ARy-4}Gj+63NTL5PU6**~{?epQB;rbEKw7 z{yvG~tIDDq!-h~}ge^Y%#ZJoeWrH{bdnKtysfy`q&AxK;^>LAbk0J)i0bNVkeGV_y zgc2iin7Kff{0NSxq_sFG4!n1AEeMR~r(S!c$OIM5UVXE}RoI%~9pl`?LYnP zwrp&@^OfpJrAQ*}Wn9a!8L%g^uOne><^7t-6MZd=oi;bc63Z|fp_zr|b+}yX1)Qx% zlU`=d7Xq3$xcU-lUrzy(Zng{HUJWf5406AD&e`?a|1^gK-v@a1*=MQ$fBWaY>jNM7 z;M?B*B9`~=J@|>g`>}U^(eM4XKlfd~Q}!w38&BSox1{)cIQDy+Xk ziy!=0axTLgBMC^Bqg5NY5$S``Rk8XjEHt+1Jzi~Dl%-JG5za7XC*jv2FU^i9G`O1G zvO5A`olwctK;*w?c0Wu!1$l|f zVpguE(%RZi(E%76sD9l%E) zB!?(C#FKXtz^SFXpj??%TMtC86xYfopgK6+(R9Js_*_D8>H_ogCdDkqXaJHjfIrYQ zPz+2%J`lk8al@{$7<)}e-)7a}Pzn+^4wpp>%Ph({s-~WWw+p)XgXt0!N!Jrc5MwbU ziKGjL%~*Qv%|%;pd>o5pr>3nEj>4KCzICb2F_VQ1ZACh{GE&nhkmz_xN}Xc~%dY9r zcManc#SD@O-3SGp^;4K@x$xGKVz5sz{s2@V)7x#Z-7p?T700w2c?aQ|_u1 z3AMgE3PVo;*bZ=@sg)^vA|{$L?9XoczlJg7%cHhTgeyQ3tBkAmY;(zFP)So0#&=By z5+w{*t2g8g>7!uiUk>%D%f^xKAm7;P&zDRD@sEsRprL8R9Sy3B`0NB0OW3lN>1sR5 z@c|=nkEb7)2PG=2m*+L&5Xt)4UUy#o+y3?At_T9*S8Sy?L(?%(JC1Gx9S-}}Cw|LD)X4# zK$-p$MBuQg=y~%FpI1Kj^i%)-ANqH{{5@a%%FCa8>du3^{AiMI0Qgn_3v25Q4xxpK ze<&6pTCKitm9(TUev+>(K^%jc3`o-nTL~SbSldz6cm5r-%@A?P8lNw{WKwsO9($%F znY(wNeE8C@|6BjsAO0u4>|HCRSHpCV326Z%xL5sSdbXnb$Q&yv(`0IVW*V zY3P^q_BQF*7($g5a|ZH2;^lg+?NKrpIY&Dz9Ye+!8-{@fMw&=f`_eJL*+kESUcS57 z;*apEgv^LQ5 z`95M&(8BLZCfN`h)E!)PrtEUXP_|(B)1FlW3G$PQ|BF1qWRQR0j%uS)I4Ox=+ga4m z8){pqpcWW`M$KK*bWFVC+fc9Pu!q(;Ffz9u4735onKj<R4sNJUfaR*qw_`Yy8&H{+xJD_>u|b`` z!+aO)ak%SJHO4Wh)8!MJqJPHWa8gefzbk{nn#bb^xAhN3)E=ww+zhn8xq^zb)WSWK zxXwwia^td)hHbK4pV}Q;I<>-Con9(kKv^N1*4a8#5e#A+8VynZ{OB0av1%MSI|1C+ zHvvjyXHa#=6m2rq6On@k$IQ#t0WWbdT0PlK3{o@1iB|!9<8G){fQ7TuyZIzH;x0)xlD2Qf1TG+HBDFex}caLIFS%$XBh8$1HqG`qJ z$v&nLP2T$N0or?y?l$8Ql)Htu@VUl;xJ*I!C=@s>`uIBX7M5jmffnn2FTy8T>`{Uc zAA`rp+pDOi6RjvPc{K0#-XRRt3!7?EI^bBZ0$U!wb zzEM4UTw|f=Ez4K=#LURI@5@Wv$9ioIVjORl8coHK$ogE%kN!Aw0<+ATYdw@9Bqu=o z>`lig$!ga%fR7%%djILC@7}xno!{~2Kk~Cb_tv+(iM#7l51#t*pZcZue#P(qhHw7X z7vA*NmtTI_Pulq|kxx6uv9w+b;E^CN)85X}?Og`TTN003?$WB`Oq&6zat+J493+{6 zSKp^z<2ROn?H_&osb`=3lYjI-dGYzDc=~_#&NKI)ynFwNdjMEKr@#K^#V#3tqzyi~ z8CQT?P*4vu!X;J7SJhU~j4HDWp<`EqEe`0|mNK0#`6PPV5zjTY$e>)X^pt0yP0KM$ z33u;f74k{fohR;p;x~Tn4}9rA|A+qYzwwWM<2OF`i@(J0W%$;Y-*p$&#cnDFTa?kg zu(MI8`ZU*YF0SNDLtLQbWRpW0SO5C1ldjQewqgsLhvht_-nR{bb1{d<*=qCKoC!tG zv%)q>R#o#b+%(Hav)Q6-(F4FGA2oEoE$dSNqGvY>KnlXaQDrn z?iLmSZC(u&*2(B7TlGR&L4c}wW5cS?sbXf(0+9EEN z9ylu+$TFW}Fe&g3Y60@eesNLhAbHI=jx#2Mn1e~^K#2LtuT;w|wj$8XM4*P={fF(M zn}Gt=C;OaqeB2aagS{Tco~sFt3G&0|GSpjptEP2*hlOfVgfuDJj&<3nFG27{G}L(k zU^k?6c>#1x71~JthJ>?-9f@D{6*LE*oZgEmZxnEVsTioCU>M_r_T{js3~b znb;Y0vSGPE1}KUr&@YHHM8bUIb9qXP$lG75<;#dw#Ba@+hgwPC(M(K>|Xn zNMxGCQ%eb_bL?VoH&vPgQ+P_mm{tgmE+hWkdGg-FN1yuO`+xLb`d7Z{Kl*q6!`EK^ z&8MEc``nXHJ$T~IQ%^wZNq!}qx0-@2=s-xGp^`MCb{oZ(OUD|u&Suq`S&AI1+lnWB z5{$eqZRi7@nQA$=%!Itcu7f5%Xjr-0X#;6rPrSyvnuo7^;)~w?`Cs!#|KvNKdGjYf z{?f;P_1Et{@f3eMo<9oI`o@FFvQV!NRJcNgXV?5}?J z6jm|4XuPz%($2MGY%hJ6WI|@Xu?$V?T$N-+xOsV`U%J-P%$Zg~$e{_KYjZJQyM)z} z*)-QXVb_`-x{IS)Na>;2vpy0$O(>oFqd+6))B$XCEwo#+pZHMZbTu`$&K%76v0&JZ zvk^`v%Ns+(K-h1Nz`_fkFGjF1Wz-hbY-vluUgOk~lxIm;cdKscj8b?{?I}9Bx$!=S z1pTOnU0u~->@{jQMBo(WsuK7{^;d(VT(|+NAgXcwGsL>*tK58|5xf30kQFX75eLs6 zJ@$(t9u$DW+r2x4T^Jc6YGp_g6#tAp_AW16UgLkX)KPMMGk^}=_}BF+ZM67yHSH@y zwEm-iywnO}GB} zl5Yl^f(bD+DRNpA0Dv6o;-`jTHugboBGL}`}R#=?}CL|=tkimjxJT!0 zokQv@3#LsKA&Q$98XwQj%q)7Djxs7_vQaf&`kboT1w)t&2xj6DRu_n8uOdM(7;%Pn zV1o<0_H^^meINzyi7zCHiQ|Ax zPGz`h^hjwyuSRlJ{{&R)tjmmzljK~j^Bx#6P3-sE0i}opFWE__kd7wHl&+im zgp)TQY6UEj95Lqw3i9)V6F&G7``n%D7f4|@Z6gEPe%iY<H&*%(TVmMl7>EYV{Iq=FWuRQ<8=U;jB%Af!4zxWG(?-$=2u>MnO~#u`v5KQ%3+D<$Z&z|N5^1fCs`%g*~yG!AREfQre$bM zR5933_)p2+aOeIT?*9G1|FK{De?I>2{jq=h-~3nq^@soX*Pr1JnZl1Z2Twe4uU{Vb zkp~pwiF%7DaF=-zE^ZAL%8MkDv&54L9g#8Tm?~rmqAmu~0vUFFdZ#$KYPRNCrCdbs zKDiaI)EnlV*IxP)|G^*oyVE^$mMTw;T`gN!f;VJw3rs{*m9TwJNj-}xg| zDZ9!A6afTOkQrT=i)Kce`@OqQpL4qNJkQ$iIX$lI^PTtId+oJ;>$le4?|v`ecfRw? zi+%p5WmY2(9LLbuR6h3?CM+xc}j5m9+vk^6L&CKRK*q=vGV zYX%7oL7?l_TXgIEZxp16m~IA$$cu!|7AlI{c&SB|6`rQCmMGkHp!+`!l}ti2886DU zM9Wto#dVucYGsrjxnijwdwhDc;9%jO;#i}KZ9>l;VZx2B>Mcf?Gdie6Ns2zF$Py>M zAJ6EpQG%M=D0xw|zJ#IXHX;ixYOam3!-H9=i=Ag!1PzpW9m939k%1^@oV6Q3KxTd}9W`YyU2NP& zeJn%_$_hmdoT-$0G8Dqd+H_N-+Ez7bE>rgdsFtmUI-_x*W0CTL#SERsH5oKGFvJuIQ-ZM6E&r)q;1^_@m6bV z<|JNOkV}t_F;GRVULsH!LZw7ts|}v53x~k>s74TJt5MHvj9i*1H3XNi?ZqXLbH(YB z`HYl${Mv9lRzZTJ@hggL_Ex8$eij0$nj0_hb6dmDTHy3~zd&pT(?3m9^)2uz>LrEMN zb$zXLrHK#6P5?dT)dfWsB?wvd@)be=2#wU(a~>|p9_r|GSsstX;A<$`xrs)>C3wit zV>lLSa?of9;8ed$(d&Jlc3~^HtD!^gQw~pez{?*}8n9N|E1>5$>iRG>d1GzYlQfnD zSWZ21 zH%eZ5qSkEHR&_FGLQqIYs)or=Q8KXDTQ|ofWNA9XaU?xa(s~_dM_o(2V$09z!qtU4 zlF6nqx=_e|rPPsf-Y!aDASja%b^SNjPBe0)52qNjU7=y>Y_Mm5Y@ZE_z9tGPWLRT! zaaQpA`XX#ox8%ucW)K`@Hs%Xtnj~s6#$rn#`EVhUdHQlC?R3f)aF1Y=jYoH@3aLm(oEi3T2?Jl}(4?$!RPm z0V-kZ(32R~02ShA94fU6sW-&1&Y@=GIoF!mHLzj;8Kos$?&+6SFAVhSOfx4L#J4Ajn0b zpDtC!h*)b0pJFsI1I;c@7kyWz*&bU@q$046c4bM*$KF1e;HP~?*cXz-3K zJ}hGT`B#6a8PE{YO3wDWsMGd`n+1;wKqW)36{mvhq@dyng5OcLS9R3Pi9^E8keC&% z0n}*$P}Q%>NW74eN#Ifd9kA$6GZs+~j^>6;Td4oPVgS0^3N;&@`JhH#Wt>!b?o4G1 zU!iJ%SXornO_j0t$vwUeSh_ly5s|N z&8HRXGI7I;`Kkf(0`Vlm<0GGNk&PY*qg%$1G$IfL6L+-4vkp+xOwbW&MVGym)5>OD z7o#{d*rM#{)*fW2_)x>ZD>F5cxbz{`112U1lhj<@S_x|#h>KW?qKY=SEx`ml8qGt*O?I3R;>`RgoUR0Hs3BVlh&Kxgm z9pqGppFHvku})aT)_b-HO`ZquPyxS| zOsvf0iH`jiA(~?3ij4L_Da7NFWEf4$64B_(IJGO5oLfia@8p~i=vZQPtJ*CAcN7Nt z)Jp5LIC5wGS*wstHA>%9I#y$^j53nCX7Nmhf9Hn7zo6CCsWcrIaHyezl;3%&lC+9* za(uJ3j)}?O)pI?VI6sHZ>MtQ;mZ_D5hECZi1zsYsR#$L}Idkw9B3-W!HATDsts+D) z(p=TxO;zh%Nz0e~6z6Mq%;J(tQNz7rDvpy{4UZLHDz#mH zGE!29S)eAG{i~B2tx*XY!Q>@{39rdCK$&i#s*AnD76SPh*y+gagpFe?$CGQ5&xldN z3Z-Q`$uJPRnj4K7W?O&`qa!$ONR0<@D2@F>5K~vY=$)cFY*8iw%Y}Y>=|)Wnixy%n zUF5Q^;n8;5V~(v&ej|g|RE$CjE|r8_Yaz=EKe$}%Rcw9)0^E?0)5I9NRJ6z+YaGEi zo*Q`n^qsGuOP**WQhny3YC2=@V;~xHPD>7TnoXE4>DHu1=)|*QXSbz%R&~#^J#c9r z1T3*I^J)V@Zs9q!#z#%@yx4e4m7V09DQ<3!PiHD9YZu(l_zgr=T?ri(Sx5FCOI zbv7oQn$d+6a7d_{Fv1LD0U5eTeghy~x*P_JVjS|I>AE^uS}Ee*m)YgJK`&pv%t`T= zf93rTeT44?dVrMf>|Xq>-+$x_|J>W&_3mH3|ADt09r6DGmY5P03A#UnmH3s z+s^0c)UU)8H)80!1~%x@x0AiNrGV*jVQ2T18?QX^&=Z#rE*(96c;EizpZKvK{?l*$ ztV8}DVDErW0qt=P;R~AqHqiC|4gK@LvSx%`Ng;k3@WMO5t;91#v zCsH*hsF^xA8I4@1qsBr2ky!0N@6|tVT%5n)1=H=LcYN0m|LM>FcWylUD%AN9;KL6; za(u+W6f>>C0Uyf&jLB5Rrt$lBS6;-l(h9 z3|*5LyNPb1B+FfyiI=L&NL5Xpqg{>=;5?qt3OPLvjr(Y@!LbAAs_s(I4O$_oXToBX zz7U}}932Kybg=oq7j(uz_g0M3#{+8|6e8|ufl^3kp`j|NyUVmSd0pSkrn?l)v~)xS zR%&@~yN-Q*cjZKK48f0d!ogaJX2B-_Rz!!LPh=X5M~UjxoXSQgD< z_R`fcjT!b{bNZ)bB)EaWjyfhT;w76{tcz!)uK7Jy7m!vm)|a|5MV%-);&NQDcJN-m9VzV;f9Qrir9t822|dWHZ{17mln8mhn9fJ+D=@kt=xB- zo5@W_2pxsG%fyWsS!QFK$w*BR{e7RhukV947w z3>{VP^ox<*1Y~DAglk64vidDr!th(WE-`HBS}V7n!AB>dCMvn%M_FgX%(Bo>EhVQ= zdRJn?lMjaGXU%Q8Xsj2x3MS4f;vh(zeAP$Fx~KXwh+TGy5V}EwS;%6>oY_D}CndGY zf{{_d8C%x8HLH+_(F)kLt!*o^V%dZ_OGPxi+F>S79zLM+Zp7)@#lF@+^U)_SoMicf4a;u0Q9_0I_?BcRgJ1Kp%A7|4r_M{J7; z>roJN>vO?AK17#%DE3&qnK37Lo|%Aoe8BNN=V48QI`rfQg2{<`)7}ouA~5;HPdK;> z40m`K*P824_3d>Uhn*!MlGcoAHQKR>uV7+G5pYY4uyLs}4+g2TDJl>JTAcTcV5js6 z=lPq`Zq63kF~ZB%fjI-RrMmu*S9OJ)8Kq0la*FYSLr?H0Was$s)}>3AUOT+;!4LlG z6Q6j5&jO)!>B6Po{e#E8_{+ccoj?DNuUxtJ)~%Zjl-CE_K;Tb6qd;nWI;CdCG6|E4 z8BvFdS(5@qpc#$~W@B2JTYJ#^@E?8n=)80I<}FNKCVZa4RGn8Khi@W4oh&FO?{Ak@(kB-#uiEqUK{Ea)ViG&yLb2}MSklQ-9}>PXzvGgEpLPq(H;Q|Pi2 z2ai!uW%*$e=thcd*92CBlBc0#KV=YSHzLeepZ&BpGEy@OUdwJLO({EGh{>PM^~!Jm zJ(rqMqvMG~SL2YnT`Fel)i8&w@7C~Q$Z{d5#a=!70WSKA*!*(5SJyj9K}RkCvh{~f zb5*K0ER00;C>GkSMgyzJ)LM+hb$(7_Mu89+ak0i5e_F-Ig2l?#VIdPhVIhslq+~Ef z5>Gm5i|5{?RjDBekELZVx9nAn=;>Hn(RABda|UL|7KEW!MwXVj%Evf1VTNQ8ivg3N zwIt=zu5mP~GqwD%?hsr67^+UXrV@Zq3MaZjilbgkPFgY?RCiMwdRLK)WrQp$M;mGcz^PLpSC@v~i4bM5y@(YWbdYnP(l;X_vju-Ql;k(;FKj zK;^VR?X>>7N@*$m8jM9`f{dSSP&8ZJ>&4-5 z?DObARA$ihYQ&WM4-?mpkr6M3Eg7kFZe1>&9>lG^YysqOKr2TGP^;Rc81E9t#iA|p z*cX%_fWZq`g6^;CTjv zNgVA`xkWwp;u?hmO&JssT#nPX_w-zAvci#>p5`8b(*V`F@C#WUyv&mp6T(9XJxG$M zSkd()zv1IUJ75r~<0ZZq+iK&)bdy-0BCmeU8mxt4H(ipX;G)|Fw8uaK#wc1K>$sHx zeG1WRP_2`|fefN=AApP~2$No*DPD!26=kYwYjrD= zNBoG;xxxLZW5oRUS)d`In>;3#Gp!{lG#YbVPs@SiFp#HCb*X#uCrq|s8_k42j}P;O zp1561O5MB&Vbx#A@&PW%^}9Z>#P@#QclGLx!|T8FOYi;YM?d=D8?V8AZ~ww?|KY=5 z_#c1uyWjh(54_7*0x8f733Ybe(ug|$Gf-A?_Rig^!)WFpMIh@SR53mIa^0IXtP*4 zqpSnRR1uEyo>Q~Q0h?4hP9sP(YaEnxWvk5GFplDr>KIQXx;2ss!!0`can+g{Q^#o( zrem&(4Jy7R<&)&5$UsQsuDKm?b2qDVh-PsZx3K2X_&FeY!vUrE=wjl|*kQQwNKNpCQFHj|5lW!3w4N@~SY91FlZ=jdC%3^3%dhC{)(g9?ot%wn8 zD!j7NTGZ)QWEoWttjL#BNhlY^-?O5*HYh}Ou;e^FaaJcZ6)9nG^wP9T&l;1`Grg5> zG&W*cp~}pjL#QxIQ!2sGECIu?6b3Gi*~8!9l_`ajFMH}xzBJFGv}W(t5p8WZu~dXI z_iH=qTGHcOu?!6=8?SFh0vyqnh4n(x~Vw2sf2HkOVJE;tWUCW-HHG9@9Tn z7z(Xa9`z{OCA!I=wlQDP*=#(rj6i#`%FbutOv^j{I_O0Z{SkUuTV~`^2v8S~yy3-Pk7689lnkuMteu z6Tw7;1Edn35Tt?#Jrqp4uH+)h*bkm6wkz(J58*V~dQeKu`Cz1Fxqhl)xO48W z#2TKF1WXTSH1auPz3j~FDk-orz<}K4RMcb{%h&N~;JUK$CS+XKwUm8N9rLcT`ai)8 z?cw3A%lBNlet7-Y{>NW^{@JGu7qw^=b=Wg-eG0q)5 z@yb_x)mQ%b_x;d)dzVPp!OlKkrs*Hf_V=}Z>55Q|J5V-g$%sWzaP zBeEt;IIAxBGPLWoo*#9y-~&IV;Ys-=Z}rfKw>oC^VY{Onw8^~ta9ogzV~<)BU8 z^|7iRO8w|+*lb$aY$Ij26m**c%CSNy@(y<;-N!5m`%ohA}N5;*0Dh~ zX+7k75-_=Rapp8cmq?~%Dz?;LagD8gYn2uSD-Co_oncvQxAnghWuvN+*ZmWsYsVOj zd_n+OCiEeNrCQQyso@ARnPq=GHnC1m&Je-7mJ_BGe8$dvq$r?+dDiK}9b*8O{P^d> zv|_1*%z%j86qOvOL7L47{nh2!uT{fLoMsU$Xfuwd^IB7i9(}27A~dn?Ycj)rxT7`8 z(HO>Eow>r6;(}A%lngXxQ#V!)qSjSGDLGzNrbZ4nJNHe0Q9k`Jd(OS9S-`ZDX1!ARj3o9RfQWt z@*+^-k9!Tre7v>w+-6z_-#{s|H7;sd&0vFRrC`vinL0Iu_KsAw4M~|nYb708V>#N& zIylT?&Q!=a?JZP|kV~nn94~}K3NLl>DM$)iEG1!;ogC@UyCr&1nT$)hP_GEkpw9f&ps9h}Y|m}Z-MsSdm~y~RGM4#EK-w}e)W zP1A$ZLM~LZr*$YJoiR!Dr_;>aUukAj3V0ZInY|WDAFQk+vOM#I-jACoY*>s6 z0-e@(c-!kVQV7VwaOuvK^J0oCW}oJSl$Rr@G?}L#*;M#w5MS>@NRu395QjJ|qH&^@ zhEiaB+VF7$&jAF2TCNOJ1RA>csT1}9tL-om2{q|Nn4RfLJpj9?n8Ln9e$BUF(bdXd zyquPyJQBT+#&l+5y;$%)fW^ z2vL`yGU1u)ZU6NzQu7~Enp8-m8u+gvf^JfimDt(|Oj7+U5Z?{@%Jr8%@PQ9J|MVyQ zKf(Mx?*7H!`GZHkius%zI9{&0)G?i*=u`&pbvrKS<5&WnO?(5_vyH` zM9BSE*$aO|V19CQ_|)@H-F*J$-i3>IZZgr%pF28t^yDjF_w|4E$G`W7?(nb5&Ykxg zf#!XHbG!UE4QG^`1Ps4z#B5TrPYvQ-I(0C4;=qQ=5~XZIO--rKQ= zF$1!4J*22Fq>gcCOJoZ>LZgWgnSie6ba2pmD$*RZMRNt~4EGMg2O6YR+$zy0 zT;Ev*40RovA9FJPy+iRk0CX5ruj1)*R=YzRAJ+Wrz7(WI&4Ch(B^)Ho`3#}q&s4E< zTwoBmvv8oW=AB1|9Rb(Qev1S(u=pu6z7wwNKJwTp_?;1 z`d!>|CX`Xm+rDx-D`;;5BZ-*^6dWoU*=m*^om-)xW?%VDC$jaIay7# zojQ5O3|k}1RcIwa9XmACkuFlEVwEm&9Ta=Xj0+HQRWs7AG8U?x2}0HuRZKWqqzi}$ z;m_>^t>slsadoK|itgB26J4+lVJa>U`Ax~%(pM?Ym(l}Je`bt> zLsl^1{ajkU&`PvJ{%mbAf?a=EaMb6tD>T(V7F zJFl3dP8}dcr$s1<-(6m(V#eqV^=G3TAC=sOH+1CJqUe;7k?` zlRXJfX9%f)nzwkyi>7v3FF1EJ9D6zl%0H;nc80w<;is|I9fy0`<6FfWAVOt;o|T8(Ia2>Rd0LGd*6TM%6+$vc^`o1*F4^nt}hwraRP4`tlLGg ztNPljU!b;AG5V5Wobf;5TVbDh@u^RI?6KV|dnY&fTLr#0{M@;lC&y2}`i*b@Yv1!t z-+lYWA^$Gm;`x1E7a(+Qk1s3of^VG8L^DvJ@7m@d0`^Q%My(NU7R^|@ZTM#EAoj}0 zu412w0>G>)9wA=5!beJd!scN2;Kl1NeBEF8n(z9~?>Kq#l{+`Mn{lS|Q4Qw$hd=tz z;o%XNgN)%^204`8-?8)9uuKTUQW0Ig>}q4h2rS4ud#$h#7u3uRCJ6HZnsJ4(K6fU_C2gn6x!T0&h1X2e>DHw;n|GIvQ<(~Azc!nK4dore z_?RQobQ+f{xj9OVgc5@iuFAw!iW^$VJEc^#`0g!<>2TpO*>>6F@OdYXnp2R!k=H=# zT4I`oWc?c1^&BME_QA#s>%pwL7Vzu33K1)VJ9B`=fe=;cEkXv{YC`1Vn2n}LdH@6h z6mWWR+!CR-q>~e~Xt!R?l~au#)OdaYk2!LqW(GNo5DLnUnAsN6IQQ?rCj%hTBPTZQ> z@-)_CC{72r;i(^>5a`En1Z>xwIc;0v`a`sWL%pIewWlTgR0lp_yOtX7@A3y=9nK16 zoa45F4A5u?IRclYl^V%roAy-GOsre2m9r&Es>Q0Wafy6s82?ZdY^ydx_Rvjle(Cox zp0Ah`K!br1K*bPH#>_l~q#BPC&tkwLnqod!H zO6F)s-YyieJRIc1coPoiwM1TL>AnXksp~gym&gse1YWo`Tc` z5YJv{xIiMO*9R!Wu%w{jLlv|en0N$-+{(VgRY@Fpnla!`?`zzCJ5#8alhfgRi5x|Jt=rJ^jfK z{L>G<@Z2*GzVSW+x^Us(cmCk9Kl9~Z^NyeRnacT+mfBo>r^@E)Y7k2md`47RqC1hbzM8-k2kOq8>7k9o&a{VT1DY}!*Dx<2Y!aA+i zT52gTYpv*;tAB0)ya$ld?!kr2*NBC(f*2xNe{N9A0$z^61S zaHiSKC=b%uX15OZNRAWl3w17CuX_9Y9CVkoF3`cJ3Vh57MsSaM=cru>x*Qodo8xS3 z=wNtEtAXZ;151ffXx~e{l9g;s?8rsUvyGaQZpqXr&B>>!vVAb{@4E+CBgWLrbeEfS z#P^oNE|Wh8oBaVKt7<4EK`&!~kpzKr_KpI}1Rr)Iz;kzcYXo%O5cEAkijSXN3w`=U zZ!px!B!cps2gkBB$5&spTq+ES8bHu=C^Cvr_f1X=3OC_i-=hZ-=IAT6ODMmge>G^L z1@m!;Wp8*HR_0oPV)v{Sp=lx%rwH!>REcli?HTw{^Cm!;VW$6$i4dz`5Tdc^a2o5p zZ!>|G(_@9&YGefiNp6hCsT7Re9x|rBHD4~I1E*?OL%8aM9~m|xkGpNVye|cDm4KwX z#e2Cjy$!3%)@G-=<1CE=6S`h!hm1t4wk&8WsIQ3ZFNTRg14?6o(sqE0I+|+JF$;y~ zcY`uDEM)>!Z$j9xtiY*h7$y2`K-t>3G`vZ^N*a&-7>&eb!oS=zn5Dms=conc#v$7* zsS7ol22aN+JT=qpLXt3q)K%MxHaN9J+lQaq?4BIhB3F+rJ06+jqL_t*6 zgzQmrN@-%I+q`<*gcip#T`mMzHdCRWs2S`=w-J?*LczB}tti|rWGeOwH~Q|37el1- zCZf8aqv}xAuLq{uBh1a9C?Xs5HilwHUUhpCkDY=I!DDG_Ayj+F3q-M*aa|l_vi0kuXoj&x%E5xEl&&$QRf2@RmN!MDy&*+8n|L`7Rv-@tt?h-c3rKyp(}(2k3S)*hpbGl zE?w2J5yzj(*p*~w7#1c3CPpjl_zkwPu-7n*HbF|D<6G~lNm3qSeu|KaC+{^6sq?jG;&aEV2>j`(=cJ~wGE zy!@hHjN=S&Fw9_d^(r5eBcpuS{Q!_FVZ)4> zUh#c8unL{VS|8dnq-l{|eJgq;sk&G|96;K6bZ>mP8iGi{QQcKUu;wMcsUs~wml!c~ zrAHmRI?(GPJnmw%Kw{Jw>UTmlLNS~_dDPA@Op3`Be%x584C$m)Ll8qf4V6#_1Zl!x z?8OCL=5m|;9Aat9Kn~bwH&IT#>Df9D+8jd0?!Y-3j0c_mQ*NH|a~$$@vb?u+dv~9| z(`ODLxy=`1v3M1xd4=0VSk%(>_~a0Cg{S0Jba>Jc=W69nZY+~MzEpQeif+^suc3I* z`cb0r0k^m3N;seQnUR$rjR3-Aw$21eXeuA-GdHWSin$c%PR>3N$WHP@IZ9jRhY?_o z`MjbcalJO|jz(vbo`M@;vu?PL8a|bgC*JGs**X3x^mO21*E zY||(b)ZPe(Z&;x$3N=ucMbxXC2puW*C8vcI2SoIF-Y=fPq9;k@jYfEDzF6LPQ zc#2TtN@r-J?=NL`8>M`1AgxHwliW@SW8|R`$iTae5jwpzax9!L6gKhKh+iZeqPU{z z`lL$i(5%5;T9UEYRA)7;PO(qyT2&oO6cx}}Y(X|Misx5w^Q-A1DM)t9mfea_CocGI zD;eh0!_Clm(olkFwvZJ6qzVi5$5Vt>G~(&LEqu&r2ZEINAPADSDZPKVu(jhq% zP&xJ?&CEmWOfQ!k9B+7Cm-&tmT`I>jPcHcm8X|i6I0Cg`o6$*uho;_SCbBd<64)&@ zxPd;%@pau)LzZ4En&G*gfq|~TNihQPVq++DI!l+1o-zncde8uXVQXGZuLq!^>7@u! zD52&@CIea6vX_B9ybR2mvAF5C5vNWOaAay_mKPL}Ls_K>T#K`aW&8U;94Pt1o(Uwz}wzai8tQ;?wsT828X+6 zo?Jce?j9c=UE16I&;RDvzxkeP%+-rK`&=1$^B|GOV}+t+^0YfrwkbHZOx z`^Jfr{pcLuXZp%hFFgOkbG!ckV`hck0FuKh)#XNe38SdHRANyLCC^Z(v0O}Nvt`ZF z2#VrROPp!Z)4CjZY_2Wd*2put`wALrM;|x=yH+#BjD^ z%`8pPWF=Q+USY4!)z+V2#%}sJa7>w1^s)C?wz|NCaDMs?X{T!SvdM%T>5Ve0FZI?g zuk86&T-Q0T{<+m2?ySJC>p{1O$I5ESOZs`xxayN`ABz zC~afj^Dp$ZK^Cx{$o3!T`iMX1`Gz6gwSLb7)&_Outd-%zlX@O2G*_xBg0kkzlp7)n5f+x5}%Tfj&42 zDO+NNspw-oZNzHRw|HWk@+czS>_nEuwB1(4RWCKQnKBg}1>2Gx;_7yg0vqG7Ri$zl zjBTwDI{2a0#I@N?`xMa&=urmTO7m}Zw?i1`o_K`jKR^c zSstmzr@Jp33!!SwQ_&Ve27om$HhuRxfY=KR=PT-k+e#QA$= zGJD*AGu4CcJ?p9Hn$A(hA?v*JP@3tNqsG1iTqF6Z009_wkn`6H07`UrK9b=Fzi7|A zny!Woxz+=XJPfTTN;`;+woKGZH*uR~I(;Kq8sJ*m=|gvBIS2sP_}U&-?C_CBIfMh> zf4VW3!w`@gssgg7oHbkH$by(oT*uthq^z1!r`hx3^;eX&9&4vT`3JYFM;tp=M$~ZKk~_kufKeA|I+@s64zBH#BMe7zWh9%_ia-kuGZwqosm!#x zmKhcQM43m0eqh;eCgsw9fA8YUuf6cMzVYqf^>@Gh+%va!Z}ORuJ+5n1WmsiE=&JQ3+_c+$oh{h8BeHx_E-Tmx;CYqY(r>v1}jnqMEXazJ_r zYBYuoiHjq{=OjZ;9@miCY;}yMG&nV63&G`M{329PI8805=F0Q9z?$AgRAA{wbkrhb zq8!Tlt-~a(6^~7U#F_{PLMYmBjV^sUccc`{DtNXEEP}gB!Dhd8vRPc0kpT?AD9w>$ zG)W6fO#P@CiqI~7e70>y5Dq35f*$WV=}n+4T-f|{k;PjE+Wgy#{X2fF%qAE8=eXhJ z^JNE@_(0s1gZ&5Je19L*_hWgC9dpZwjF55UBZva&r^nyit#He*rBjghe8FsuBuiB^!F0+Y=%@f(K%>8`C_q)Qb+wa=n#;5?@&dEA z@SC>Aplq505jli%L83=Z@=Jg+6~Ge^H8sj*C@E)ICeWBk++^z0q=@*bci>TRT$d-i zXY!On^t=SJP8sj2@T+^N!leLH5fU~I(Z*1JC129om1>0Y5Lu8Ft=;yNqN$4-~n1E%B z;UF)>LK?|1$&Q#8wOrvy-0=&v&DjQP9mWz0gRqLLbjH_CtcBgEV7c8UDm0kFS^U-j z=SsZ55z0fL>0rOAi(I1xP~9z+Iz)#OzRA^d_|dJoS)>>E;3A)>gR!>=X9o&DCW=Q7 z5X3`+yBz3r#ur6#P+)SP=hF@a)Tvl^khLG)(G5jM2sgK441fOARbM6zh0;L7Q?Q>2 z$g$|=ZIDeMMQh|5sAo6a;!woC1Iuf$daRp61PfL?X}IM8wh73LEXevs+C>6Fjqs3H z7yEo<;$5gS+Z7`LfRw(;!iuCMrweQNH;D%Ag~uqWo>+5poET=7)UG@=cmioJ>2%Q;`Vq66(5`4HXaIOD24T3!}d z^PG?+8p2qYbo9-ppQMuO@zL?stM@)6ZVJ@7|N++lRN#|K{&M`nBKq zH-GeT4|AI0Ef;gErz9|XF1@|OtPIDgO)P|znVTHtPWS%mrtw&GnsgdrTbu^`iH= z=e+vo@&ASMm#@5fbm!{+?%(;QFMZ1!uioUVa=rhD41l;SiN)2SY&z(50DTx=hm_0RSeK1G>YtI!Z3j1 zHla@y1;*Hrmkfy%LLj*MlZ+2xp-+RHWNTFne#U19@TrMrLqN(KxO?NZ*P8j1Tdo(G zG{hdOQa%Qf>y$&Yq6?E~IxRYKKnS)%OOTnut3R(Ecv8;=s*bQx#5hi-!nMGjfnM7( zHioGeynUik(w3<|V_R<;<_H~r(NNhPKU17Jwv#PeAtTye#q#&Y$TkGUb}V1=Q3IhH%T(m(OI4Ow+%+sS;Wc5M zMU(!LVx2O$Z0QMazT8h|kZV_A~->BwA0m(;E%8(MLvCD+r zdeM+Is)Lv!JI_{7792{>X+9zVp96Bp@d zx+tTpf0AZj{yD8;Aw*ifT@K}vECsyEQloN+AnKbFH8ih{j!DJ!Xw?w{a?>|J2HJ!k zzd`~ahRO7NhlY_0N|$_TL_|%5xkJsvX_O=aP_`(|+>wS2PMLIhRL`=mxx5=0!OW3f zNJi&d6|`gvtmmgeq~*(rz_kZeBkN@I({?VfkKk|kLuF`q)*6n}wTOa$f zuldHG`Gxm$3C{lq;1k1rP!@M{)R@iS^RUGNenN-2{vnPOYO<4zlbrzlB9z0!n=f2{ zap&sZ?VXd`dw1^eQ6Q!&-yF&6cXa3eEBAlKXMXz0$*~V6U2)c;<2<*lz;r%UC8-65_Wp=bt_Gfz8S<1$8zlJbVyEK4_W72R4>Wd9! zx|?zX>dH{4&eZx;Hvif95jb3VcoRW!Q$eiMC zm$#GryOQ%44t5TBUEI5R-_4y1_wAqm$KUg1Z++vX-+$=ogG(2=?~JgRI3|7k@OJ~E zTOAK_fH|ySHhH7fQjf|!kXn?(0mj7#9t7l98W_2Ni}u|5n;iZzw}6gmv2+yEnncI| zuMTm&Y+Eg5hsqKWYj}@9S0G3%gjSw9n0*IgPm|=S58onTEwdAy{4h7nw?wf z-gu*$--xnog(~tmP_zCFP;x(=RWumQ4D@LM)Frp7jF#C!P zNX@tUJd#l?4fI7u2f6Z!$ck285m|N41rs+jm{g@=K@Xep6C7sfATZ-t+Ti^$q)rvk zc(J~CMq#D~78EtFC5n~0MW)yUn_E{sR7Pu@f&da7izU10svqT$+R(9$)?Ju%$#MeM zYRXDPeCW-+$ozDe$W0#n`s@=UB<$9}sfgzI(E-_TYuJ^P(;9y{z(`yfP22jwvYVsd zuTAhWQXLZJB^Stz7Rj2_@EBws;kO0Lp;wD4l>JIIK6jvt3Eam!x4lL}fnJ4ma&>s( ze}OG+3P)}NSfkZLSy0?NvN-XgSwG`ZQ#_hq&7D%{umzs$c^|afP}Hib?>VnTKp;(Y ztetOWbJ^HGj&VNEWEMF@)I1_3fQZb<(fJlWB!q*5+NQ4HjZG(LHNC2O@O6rfw|!}C zZqzB70+JvQt8@C;PUtn=#6itP7cP@PFFnCG@er0gT4V!y=-SZ6CgFR@99^eV`5f}7 zPOfq#8>08i@()J2Egw zH<)8EokBrDVd9RFTp6v{pu31VdK#i=YMl;bk8jU^`Q_{X=fC{FcCPH6yKl!U=6$aI zneR?3^e$h%@@b#`X}&S;m+Irs>*&T(XmLu1ql9(>x+lWiq|%IP2K8YH^%#w21Lsk# zPL053HaVdJa}pa~{Z&{Ene+V3!rrSlp84+Y{EoN%7k}Z_ z9{tF}N4JjnT!lwVzGh0r%qYf@LqAMRpC7uTAQ<_k#rVpFjUaTpgm^=!9u5$gH_YdI zOT?;+;dT`=(0&3oXP9n|rbC}2%Eb`Wl}-#!Z<<(U9e{pJ|z1n`Op<@xCw?7^01Pr!gh~OJ?(tbEkcfwazwc0U26D)qsR_ z%Jl+FJdusaq5HSMT)KkLj{Chugmef-N&+i*=D^Gn(yur_}yF9|YRr z+5eS$UOv8aW#`6uokp!*{Mb7p zV-X(8kP%adxN`IsB%z2)TRW@TZ z+88*_7)RL3a-YyvWuVxucyi@fI!!W4ZPf}y;%P4$G@8>*vAEUIqPx80b5r-@MQ*K#v%X~buE z%F#7O9WexPX^G@nXej8o6r>hz#Kq5pqRdnyikf4CMC_5Etp>Vok{PS5$)f8FH9#cL z=_waMXyD744e_k-7EBq)lk1s9J3X~uYPOMt!QN{tv(93{;yWwaWLT-V?YJ_75r-z% z8cO+PYcBH8+OslKpBIV5uvUF8g2`II>OsjEyi`+Lo*ev&fU}es6Ghc|tK1>+?B29*Mi?|U z9_6BY-6cXep+Lm_ATlO-69_)R>xf{g!EElXb_MQaG!Qo~q6@A6@%|EY|{e z^ch9d5IHZey*R7+H?xH0sEV*=oh^q=)rB-UI(Y^Ovd69d7_r3r57oNTEK0bJZ_Jx1_J0Jh^ ze~Ej5_Z}QvIzB#vBcsG|ELn--qm0?aK%We7(n(27jJUa%26Ki4;+}KE$^Q8d{x84w zzy6E={owQN=L0i5{=c*DC-pqR@s_~e-nDDj;0v%mCn#Oy!EG)}69jVB9y!WULrF>= z+RX`+ROy$VQEQqs7D_71#c8CTgzMPd*?Z}g=f3*Ozw&#&_q*;qamar$^KjTDFD0Y7 zBdz`Ow-0YV`N_wQ{a#=HH(yNfhu5;Vyvs6dT&E-XhH!K@s-YKe>>PtKSFS63*1o2fF_>Ckc{E85+k@n;R7w373;Bs`Avpy;QSliPPzLaXZvJh-OTL^%je+6x}uqA-KWB;z4XR^6HG( z_GtmUV?AQfm`GtN+d$$*_&)3})TPg{WSYz&J9&cZ&@8^Gc@N{05vBy+CF^&p`R}Cp zryILm{rmqn4lZ6lI{A$I_TTkwU-&Iw_U7Mv^5%ygy?N#01il&SWzINVx4y9qqbV!laI_7hqN=GJO$7-UajJrg3HH3&t(0FT{U<-)r{}?lxtKv?OG^QdGT`H_lt(mGPi<#QUR5ef1)j1lD ziq0k{8^GF|lW++Ukzs8;YdE8VO4(5%HKa%!=goY$ZD@M8b2Nes+W{PtlAKNm5H-M1 z=;A0M!48;`*;}=iY@umo+lSp#;+5J&5eq-ZP+z6%NjK`N8c+AM;VC zW0&|eL@5kaQ|!|YcUtAw^Qi^QjnA}aH5hmGqpqb|`3EXbD?9FRnk=5l)z>PA2E`d- zygOv5=!_I`A>`#nuGMEr2Tri@RR$!vP_6MoJamNG3iUbLmAF|WoF0y{PQll;*`;_H zH9#T_yLFYoTo<1Mp9S2@G-|M2`uH#%47Lyb@d=ENIT~?XY01{$@!{FjneJ1N8CU!1 z>~YRyl11GotWJP(#0hFp&c|ijS^9~UUwf8pqYc! zN}7%jaSqmpZ^cvMLfPb{=daC&8{vq=K*2<)x=(XO=i~JK0dzp-g_Q>>|-DN;0F({ zzjpQNJ@LHwTOWG#tH18;Kl0PR#Fv7eoE+}$=dBu&rAR1){gDSFh=edp#6A3WyyMl!Y6q+%vb2AJy<@H?6^y_4rZdnt+-h^O73XsH>N@ zlM}VEbgOACu_`cOl&Fr@$fkk!=+!C1_>4VGuO=L+LXv6v^0g9>H5d^InnsQ?J-nzZ z%=LuH2#$>^WhqV^s%F4hYWIYrDzHwLrr-DuRVBnxXrJJ;OZRHOq`RJn7CJc8Ut3J zN53|`l%wR@w;3WS)cg*J3{sR7FFh#8sESp@`Lkwcs;X$-L4%y8$F$42TdNkUY*83E zszTK1{-5$v9I@tEQ4Ue6xcmYt0~vy&SaQuE z;fZJlKk1+bX;oFBt7_zN#L{qW;|>FdAg+rRVsxd*_IKY-oc?VF%*u8VE3$1}v`BHbx+)AOQC zH*ceO=L**C+ZPWmfAZDmf9jn-{jop!$o^+t_E|aq43+S)*g1FqTi(cZ)bSlZ+ZOP` zSyRR=`BU&K6*?9`Hg%ayH4M{aM2ETyi++@&nvq8nG1_s;nVJ{&>T(0Hdw6u?&wlBb ze9oWx>|3AW!$0SD_!9F{tOk(0xasEE?uFfFDbGL0nqs7Wm!mTiUm*`U>lVFBfMUyf z=}MS}j5Y<5mj+OZq~<7$bo9K6Ldxu*i;c%PdI>R(iTV}|skt!|%iZ#3n`NICugPPW zxi+a3pHpBDemeRhVz2oc#_?bZS25jzcp#H9)u@HGhDdkH<|q($`xpk)BQj&gN<-k9 z15$?UUgfUpNYFa9oj?o2w3rbo6sNI3#QeG)u|lV8wAi@k<*;HrXXH`Mkh#qbyw*&r zK0L?j>eZij1>6?-;NP18=J!6|w6%M3@xt@hZ+!k|UH;j>@daP?*_UtLI(ho=&ToF~ zRh0Q~(258z6EH>nctu9T9E`b2;@Llc8OvWg@>+gVMsm^4cv%*lk|MvH{nCzLQ#+|Nv>FR$2< z&xtfPE=D$2WXKprGN$%4tdFgKtdledt7C~2g4sx9BVc99lBlx@bZc!G)3x{D+ACFs#Do`+%-s(t8!q(Md&?x zS9_c-^lYz{xzU|mh^Z%ge2r6t(PMoXxwlXi1cUFW!yO z4gGvaYB*^}oGTA5sbUSX4jAmoFtq|FvvT#F1<3-8f`>(p@#1)q)kzvnRFg4jfPjD( zPhK)YUa*2FD*ST5@#~y(`QkNVVl6=#Z8l6#U8c_K1M7RbQ7SN zpsdt^gg}o2rz!;ka>%8UtY$Qp%&pWW>nn< zfeJa~3-}*+;Mzm~=J$W~1MfdRx_;^M1?J@8;q4Fozn}PTzVAnV=*ND}N0+;|4=&x= z-}7yKCWMZo9(jSw6;q?x?f5l4q$2sU!3BbUu)_Bj9UY%sx^mBNKlI`M{=fYno_zd? zy|-S179Rxiu6gIqr+@Be9$dX}czmmGFKK46k`S%5DJIB$@mp(h+Tq+l+IY=?7IOnP ze9fETpUMZMe2Q-*_$t?Y|IbXF?&Ggrz5n8k{he2D?;heW5YEmsQ>vWsX8~9CAN|B* z&prPfZ(SV|YzLd6c_dDX3Ia!tet{U2F9VqZza_NotkIoHuE!>VAY5!wgBqeu-@c3; zSA~2?ri0EVv6&O*2s4P+Oarq;!{gHonGQOYYRy1nc$%$TA@Y?|v>fZH53%Ma0&_Sq z(+!-|MW&H0^l)lVQ#yws#2F>r%O+pLCoT%?U96@)+$a34JH5Jdg16e2qr`QQ5ZtAG5-o0oX}Z}iw2hS~F?)^`3K zNOSM?p}tMG+X@xUwCv@vZq1Q0n4q*OR_@FFc%Yt0hf;^f2SaWgF|O;02LNEr6CnKR zpA2@wh8l|VolZa5vC&gvMt2!W0fiL z*PG<3>>4tuU|Vjz>Z{Gpj94)1m?c@mkNvvBQHSw?K-MZ~m>;S`SiZ6nN6+jqH`??R zD5d3FnU*AajtcvdqtYUmR-FmqiS^mkLMUz1B^V{pyF4m0ud%YRfH^qz9@=mPfp6J1 z#-(kr4o0>DZ}Rf6pbQ@R93r78g$>@8VdfFQ@o6B1g^~iyE7i4Bq?x6uP;DvZPP?hB zu5Fib5KEFr3APY581^J_UqgR-%I%0$*lbCe{Vo+bMa@f}E!6`Tg2~^WrVO$f7D&Pt zU82<`qpf@(qiEX+jAr;sn_Kyfvr7d>pr9E!LUh>R2dkPoiE#nyg#gNaj)0xL11y_( z1!$sPM{2x|L+chNidFNmBud8?fHOH_;AN;0j_y{QhFvCe;}n6_gtLY1>8om%yr=1% zo*5do2}&+Ub8(El{G(2b7Yj$Z^enVe zhf(1REOlGv)+NS1JAo9hrupleU+jaAo$Aa48+)ycWePoG-jc0`j$q&c>l|@P+JXRE zpM;rJ@!f7HGiD*%1BQ(Kyo0CG5f!x-G@~`=NYqR#qPaEjO>SK*_61&^b0?I>NZ7@} z9YuYIjr`<75=+JK9Zh2}y68Eb&V+XGOCi^o%_os05xdTUCap~7yXmoFDb+|h5&K80 zlX|hrv%Ru%A0z2v*mT^RpWx``FNdKo5!2bFo3;{>%}}!`Y+15P z1%-4=pKFSvt}OVM!1vyJ?GHcx@gM!6ciy;pwD;g8)_sW`AHV6rH(b7Y`SA9wBW_;& z<2-^EB@nBHmrOV7g_=51nu;x%P`0#9YUQ&@q`0XeR~&>}MzKkY!`tUR^D{p40$*Bm zltTw(%sM!D`(ppjor`zQ-?RVpCq8xa`VIbO!0}pciVHWxL_=Paw3yVB@8y1x$Uq8c zd&p{{KLpGpj|+#A*WJ@9BN3Z04KMCy&oXSy$=@U)2rH$~9!9{kZ=qS8+c^;txy8Ot zHXU=>Y9ASMGLFy!&mY@ZdUV$3ATO*z8uI-4kdsSvT~xd=zzR zNF+%Eq~W}`P@NE|Pft=4XV2`#;Uy$HS2J_OCA{9#qm#Y8_IqVd!U%^0JX1QU`Vk-x zIdJ$$8@1j$>)%@V$e({CvBO8o_V@B?b&Y5s zQl*Qr$J%)(jE^($A{i^bv326HiEYUNkJASKD#H)`dqp25$5ac@+PRb~Vb2+G_&Qec zbzn9T7GtOFyzbhkpThLbgql1k(O9z10^?^2N=T{Rjk}R=jFjqsOlI#f(9Boe*X*pP z3RPs;z8ZI4U1J8=jz7+d6f+~7dn@konsw7){SCwSHD7gofC+bRwR~$ z8D83Du*nQLGD%$3Xa$>2or-9c8J%nf@}cgiXJ!~%(Rw1C&^QVg47!sdZPPJw^&>p$ zKv&Mh`X7-<)}5I`oEE#G-smn?ico*O>)~~3HWf}i|&56jP78Fw<|2sT{f82aRsM#EebboOF& zMK=}R`oPk8NKmqrfNbEJ+k!PAo>8zeu!^zv6E|7O(gF-XabQL%lT#IYDAG`W>XNbP z_)2DebOaGL>);x8l2YO&OY;WirE;m|9v$eZI?P#!X_VD2rWms3yfGLqZZNOq^pF;@ zHqt`z8x?AdHBCUM6C*v2wINBLjruw=<~%BK80_#)hWg3z$<_O>{mDlj`WOH6|8{V3 z_r7}%F6{1Jzj5-R4?q5`-|^kw`~5%WG0^LfA8Y)VSKV@ zF6c{CGK0*TZ9~r2YU-AmsIpVH0u#3?&B2m1;HuN$4VCh&(&k4Uj>o*EG;-6}1i3 z-6lhC)iptyrN-zKirxJJ%WMO6)J(KkrVQ4$23 z3_6D$3Jjyc*nlRM#Eb+%LNJg-5J7=7DE5x1LnIh6{-Mb*q9lk$9gctzC_0H$iBv&R zRNY(m)@{D_w0WLqu6@1_v(9_=UTdy7fAcrzT6^vN?zb~cvs>VN-wruU!UsIejCRt+ zF@L=8OT@UfW%c8yzPRzvkKFN-5`5{W?e{kHkpE2$STiOw&BuW1cw{T) z>SVVXC_1nmda7<{RE)yJK{q@dj9cV(ryM?`6doN++SE$w=0+U)BAlkx#efRK+I5<= zlImwPuErxmuv=!r7o?)ADwX2TSce;BD;X!rW{o%kgWtwFnmGllvQp}G3wo^Oo$#nq zXiRrrm#7_GmI-H|%-Ad^gKG+h$(4&TZC)qJs|*3TqFe^`L}k5DC;`!=9x$$*G%m8i z51GghLDUV#hI-8oo$$x}3vL}y%xRU3=cASRv4%z$3pDuIzSPmufm&hI#1(u(D=9V5qA?jfc#E(ZAvLMA({?lj)cqQ3$v9mMs@0jM$r%gf#_V7v304cu+`FB94Ue+bOrg4*=q21a z5*1P__I$DQO1<u(+%9X!HwfTM$3 zcP@YQ1JC^4FZ<8G;;X*);)36|(z`3E!zGrPCiXdaHxa<_X+Y#--7-Rc0{Hyo^32EC z+2!%!-}vAD_Fwrk-+uViLl=h^_s;GeA0NEx(N~FTZCdg$S#?)eCE3I;ZH}B#Kr8bx znyQneWkIU`q3@9|>KyD=J>ukMD4^72i)>H^vB^dn48vRqMkHjIx(7$btrCs`n$7%J z+0`_t(Ny?bWOXgq=L^uy@aH+nYfnzf( zZen_tkvj>d$C{%Ng}vwLcCOMcy3{>T@;_4?uN^E}7(Lr^={4)=cSV|RY~BXvMpR$w_rXF&Ue=hoJ#EI{cBN!QDbPVpx-B!Zc*PQXFvCXKKzS2yEy5 z(x60QHc}PeED3^4k5SPlN~qJc+hsYSEiHJLIA>M%Z3{2 z(veY|M?n`o(OafV5nV=8m7YZD_CEMRR%i3)zNVVy$DL(3=sFp5qC(GLHq~m;?@0C- zXAP_%TQTnSSZwCt9Ue`_#tjfnL^MaEL>t#ZIbz6VlwhQ4D$>w02(r>{ITU%TdWH;f ztQK>6=R!p!E$Iznsd_;sGz?sHnavKcQyc|Pbf$pw{+g_=oW6(ERZ`a&s}Q>m@`3_^ zKl-vqlwu6h*Tb-KVDAUvAuU|agB=Ws<-_iQt3xTJbDzFCL?LJ-M$1NKIY!M>gh=eB zXSU}X9>!8yu;u$w%#@0uPZlGWL!_pX+d$o> znw&<19#7V4nFgQf2~1hb(S)j$$u~DyVM(+iQc(_X1Cy6}bRjbji%^R zCqDYS{^KwE9bf$3%gfuXNoNh6pzvr=nwQ0yDL{~v(joA8*jzMbGc!`F z?TDdC=$7(P4Q@0UmWjaQk2Z<0#w2S(a%Hr)$LFM^3+={?r>Sw{aNOgiXva^ zU+}zzR{*ZR`4vC%egFJ_|3ALx;OH6{A@w9%SdTA#av*1e~rB3C5R+PuY`-N<>n+9^wgQdO$9VO-Zh;UD6XvZ&0O+;C&^AKJ$hNwNopZa z1i=Qf+8zlmt!6_uE6+nF9XG?=eYECS;$tcpAoS!z2U00J7|QbNCG-B_-qGPDSN}(c z`$xwtefx)pN7t|4K0m%Vxc*JQ>pfrdd9S~>bMcZF?>ry&?LxOtF8y!`TQUohxJ z(0S_1X^~fGbyp6Zr9AoZ%+bNey5X@4oa)GI{{o z72jo*#u~60Nuw{;CazN#B-P3Woh)_?2H!cxQ1Opi=MX+fGl!T@pn5-51T+R;7p592 zR5!GVEUFP6#*K?zt6*c|rM2N8@wkwB#H1w*+bfLm=q%aRX7hlBnsioVqEXXvq2ZgsAOg$|8S4@FzboZ}4Ts&_haAf*S27t^9J zk&S?cjt19xV}qh7IL8ll!b+M!ebkyS!Kb4qD;QgOz%5nb^q6Mu(Aa=98HG4mms|Yg zgXNG~dr59*c#u^WTn{K`s==J_XwK0LD^{90$khv2rY>9sa|IROZglC_fleQ>>*SRe zI~Q-!O4E23aX>gYT}xy)s$+qgO@Z07@fZmB0cRV;m@eH>$`(7vaU{~rkt1a!=yy#! zDEgRrNngvDhK`2|hIR5#lUkILxvImg+>XFLYXwlPRL+PH$H24lah=7YjW1FzO?AC0 zg@{Ka52xEUePy?<)=(yAG|ZW?z^pjuphhq)c)Q-(k2Z+ftYkL@onoaSeL0(EUn1zG zW8nR!669W@$D0;6t-z%iVgw*DJls>$sREBfK{;DYX3IQvgc|yGiG%Am-u)?W=bf+j zc8_+CukRdO+dVqw$5b!)fyq7$06{3BjG3e}7Tn9M0=xOH~^hraHc-v1--zyA0m zXLnDZe*4>YkNR*TPf(>;5zFi}hI-lR&Pd0E;?{F2o#;6R$i{>K)v}CYqtN(fM)w+P zJ$RNI{EO4G$6xjMV>cc-V@^*Gsy=r84P$=CU}xv(jgNff13&+5fA~*);^r+b_@Im; z+LFvGX&23PW+>S(fr_nL$RW*I%xd{J*(SC2)3xbZsm^9a=J9UGVJKVBtU-3U5!~(~ zT`N&eO)Wjyr{XzZnWVaeV~+&E&~y^kFCNctQ>WW=wsUFR=QSe-Qf~Y?_qC$%{EEz^ zTyAx??rzRllmyqDCZ|^Bb_FqW*j6)RFee0$bcsGtpdsPqO_0juO!X`Af?x93 zi{}@2JYNXRJI0Ro_Wsq2C;!h+ed5SmpZW8S-PXYh~#m;{guizJjT|{cW_%e z3U7`La7+fLBexywMa+COrG<)ME*H7*D*Bld>yplanJEpAnHu8wofBA-f)do|IHZu) zBL|}N)n`wsW@2S%x}*gwJNb zY{JlotU_4$HZ}5Zn}_s=DXK?t!LTzt<4Tl08j%#kxQkJJP4p#vnhQrkU2N@3M}Jet z07<8n%%YafaLy_`VM)6v`nFa0D*;!r3@}`*Sj1=7@E}_Nb&c3wieX}rSt89BhI^|k z-&O{kIVYhh>CiA`OJVC0N=`wTgPAgBcmPzybvZ02Xcog2gM4g$)y?tdz{xLbunVKC zPUOSd4%04MtCo~GGB%K{hkg`=NQb?TDcPlH=U8m7wYorhoDFUct}Ho-6dU2PG98Yk z&0IL4YkJfufrN*fGM;pfuhrq?6KV`fC<+2feA2{r!W96S3T$I3+M3zQ)xfe+UG2aL zi1&@zNRUSxxS33YrL#24AKgBvuckIR#G=oEF;l&c9brrA<|Sm`{_C>I zTdRkge0{hkFLxfGz_pl8L|#?o5DmHU-WqPFTXRmiv_;ZO>KeJCH<0slE@8m#_9Q9u zqM2`N%a$e=f82Go$v<8slov>v3AQk)8dqPYICHs{iv_M%$soTk-?{zipYrPeqr4z%;4EN}w34&I(O`rf)*j4P*oeHsOa6?yf z7&d~VfIrX77mvOAl|THxANsDp_BVLKLpPpZx^?e`yQKHv(oe#2dy?k>6W;?{GlY8H zWyJ#%)5RanrOMY1?wpY0`8vJ_ zl}j?d8{OnqE_pF;pY&WDz2V{KZ@%!2U;TAI{%<~T?Z#u=(B&xd;wGq^P@mAZiY@+U zqAka1;`xCRA4fYe2Efu_K^$(Vn%PPF0Opk`gN2!(SZNfRWth`+n;(I-2au2GGao$b z($Catl0^#DNoinNOp+$3g#?j8GALO_T8$Plfh2=NeJbYWx9=!dZYBiB5;2lowj$Ue zsL%Ou`YWB(@pL6Yar(t@` zq9?vpN|$RsZ+i4LKChkezW&4GgKO7zuRqMy{~MmX_NTt&lmCNHedLAnbFThfrivS0 zT=)Nz58e5>kKMk;{gCrh9*hJ07--t&1ZEaw+d186R3Jahip~+dLBIq#_Y7R|C=ndb zDhd&At6w<&=Ei)!y3-&;uG+Fhke*&=7|^h_#KQcv`VkAMKoP2dFpK&O+ho|px{9pg z@L=l<6=r@(WBN=x?wcBHwJAmRUR7*Z)QdkmuMy*1nzA4E4Xv|Zm(J)st^mUTMz^Ak z$-ug`jNH9{JY=qy>5{+ZmFjfm$R3JKr}R{{YNjLhkZinEO5ti&(<@K&xnXM$Q?K+W z>xiQ^$HD@jkKtfc()DP7omlwN4m4Uu+mxOo2o=>;&|X$FILi1NGXt9O)i+`xCn#%0 z$Os57F})91om5Al@hVbCElnF~#T=HG7G;>s&||wjk#fpC;6e{$Z)NkLlmn_YqN-@)K;^i7y^w1Qeo3x|Mwh7}@mS#CY z2UD`5ujL{K;31l-@LfpiWw~Enxs?KN_dC()u(V^9F8R>$^?-3ipQ+W%yR>riewkEI z@yaVxm{Nl=k6v1xL3HqjgIkF;wnipkVNKkk;5}c$cGuqnK+dM2FIOoBTRy2QaVS{# zt`C}e0mMcaojDX)bfGGzAIb>KKeoE&JwCjV^~ZkVXTRuIz4^VL|MW}u&dv_6?d%_4 z_({0K3%(|W@5kUxK|*laaK$kO7amK1G#xWEB(kPe^hyL0OFW=Jhf4<++5F0;=XmLw z>@$E21^gy|t358x{)Cpn$m=%q4XBSaK&aY$!Nhyn1sRDj zY&3?I@e5LFsi-pD^vr}Y+@NSS0Ihb;l&Cs}B9G(18yy{(N>9@s9eSap$26G~>f_N@ zzT%($Y|JOfx=%I)1-Sbkda|7oj-U-7SpeMT~T-N&1Zy6dwz|zU=?>oX5$%^SejaZv3sqo1$&*S$A6rQht-RrU8`7Y;vfk9(Sm5YH_ zUAwt+`&<9(Z~4I={NZa4J$mor)FEyF9VztGH_zoK*Q9Qdr|IApg+Z#el_I2>!~~-V z>D1jAiZ+P{Z|cHT3RcaKn{K-3?|VM`jn|()y*%UMcc>daFAZP5e0KTI|K+o1_s;fr`0gve{>o+F zL(16Mk_0z%N3SH96E6pSE&?gJxs7K7=c1ngSU{)0IZmvUNl=!OfQ8BNEY2juSTq~8 zJZm`qGc{LEYoC&+Oz1?DhCHHFb_N1qjZvRC^saSNPR?R6Ehu)CSyfWXxM43W?UK7I z=Ax#mqHbCl8i4c~RD{_%LZoPd*!G5SRaReMORgRosIlW;rtY;`yJfCujtE`rbSpGK z2vw1_jCG+j0FMF@$CYZ+q&rhhQdCVx5mA<*K!cm2d9_QCISE*k@h}463}!6Ln#eUJ z(2UwxwW19J(X0h-of1~)@-&LrxE)LtesRghjH1HyVR)j6SOCk~52FY1?7HeMh$L~% zvR-5m8K|WG*bF%^5A*CZsZ<>mww^gR8O~=o4Td5X);#0C3V4n8aM}0>Ke@@uMwsPj zK;yusVblwUTv0SpWkT_I7&-__uP1*_}*zC+&4m`7-Ng#y ztsqN^o?>S~gxP5g_S%9wlPHHMy9_irjApW3*5%Y0vQW(47=p8)s$>F04nT65nC2X= z@)3tr3~vP$yqTR4wgC3c&Ae$cryc^1xA=neuOZc_YUO9W92)+d zs;Kft8b2&>&W)JOR0b8Zle^`%^a#&msZ0u7%Be}o4q49_xY)^5x&-U@5wVZ`Xl`6R zoS@f@L+vB8yL;{WwV(X?JMa7PXTJ4!zvqc3AO16c_h%nEytc~^CY_<;8@zWfPIoU4 zIsbS7@N~iJEqpN{T@}a(As8C8a45;?v1a&^nRq$*UVXSi0ube#w(>^^>d&mO5tj1@ zIF#vZKpG-nDnYrc)dDw%GB9V_@&^Vh*33GUJq1eFy1vYkC%yV?YD>8fq8D0mY+&%v zAgVPP*4m*4aL$j85`WRxoQaiygr4^FNs zz3x7`_SgQ`zy7+XUh`F7`lXM(@`(>W_o3s%BcDEt;fTN}K$om&GiPwlCJHc-&4_ee zPu@{cZ01Tx*$Zxh@0QqP+nn&Jb-uKPjPv@=@r{E|dCz;8r7&<-frEFHz0!KbeG(f(hMS-8O*yRov z_>^{Na2K-?tV&%8H)wfx3fhelM(6YE588tj5`<8?y%})V>U}IV=`$~ z2RaQIp&Nko>-DLCby{Xc_E#BSLnU&PZJk*&akx|*7y0v`%Xzt(xu=R>D>?EUz#l+2 zZ0ToPEFMiDIIn!r1?@S6^^`j#R61%4)~G~N*V!bjOF#EUyv^Ur;91AKSR;+4D!f_JFVa=hcFPgNwc6+o$Kh=hwXUD}MEpuQ=X$?)02#)sHq60f^@p zuioGLv1jl7`1_wbVtVnzCp;tOlUd$1;+eKV;Sc<|dBEL%d3MFn)reUdCXT)XVZ2QP zyQo9Vd)6CU77z|Rr3Po{bb3B%&>0RhQHs$qmk#f&3nih~Y^X6XAo#l?bbC!=Ze7Fm zu2~s(c>60qf;DDj7;cW9!vvvTP{CHwe3@pt1sf$`E8*aBd`NIo&sNiDm;}S6Rci8T z3(86vGk0z*CDb;5qaXk2T*9D-joU4HG3s(NNi%c_w38K?Ws@4()LBd?o z$TvL_7&>%AF_Nnjb&FiCDCmE5tW`GZf}_Z>A}YMP4nswZR!UF|*+xq+%{f$=1BiK> z0Xd$vw&3*3dhJTty3cYg-=3l5#&ksyFRp4X=$13Q<@X_F8%UR*J-4KK~BL62K~ z%oL*KfU`q4%SO25roJ>u0#Ea}D~O(^4y7M6*GYh2$Yh#~1A0h?>QK>vpZD;JY8+taLg`Zv zX7ZTZ|A+tQxBs2L_jkVM@Y=(CpqcMA;@ul(yxYR}ICTqm84B~;ic1SyuGgX|M2EGj&F`Yj*D{X+L}W+kQ~-VRME1xvZ)J# zH<%8&``-qtRCX0mEt1#eqj2kP~vYOmU1UoLMeRROu@fJ9Nh9ZH178zb#zn0uZ&vR5NSa3Dm{WWYut)x z?z~vlp4LQTHX49Y$&>SagH=%<>Ubl>VO=s$()oAJZZ%Qoifp*ev^69&SlwW;A$V@L zDXamtfQ4?Ge|jw~m7rN!s?%2loq9DUJl8)gow*SjCb;Q(Vtkn)5+OVeHSqOclaikA z%a{NiT7v;(a8t)tY8vwz0smg%Ipn?sEN0;#64;=`oogiVH6sixjLI<7OA!;A`A1(C zTC3lP_iST4uy&RcJv2|e?C9+hyFdPOw_dn=ezbG;HNW8vU-nzx#tRk~{IKjX-;8m% zdwksQV)ng2o*4V8P$+nEf<@t%M|{hl`Pogj$rwGpHyA~uFJTT2%`K9`X+wYW5XFKj zN^~9lj0|m4LTFKU;&#i3B+?rL+pBOvgRl}iuO^#Bfu_^vXBsi=1&WqN1SVRe?=_qv|LtV&u(1(jGk|sh0>;dT!J08z zxCF`R>=dakiQjwIloBpugVBi(rme2dAA9K9SAW^pyzNbI^0UIc%S$mtn;ym0I7R{Y zW)*Dn0lMavFc*WzdeXTb%d5%giSLwvLdvWm8weSDi z|L9MC%eP(ZAKlx%xOsl}&gE%eZ_4Ex5e+r(&iXK79;TuSH-Zl?PH+9j&-_jQ;cxxo zojqO@i;$!q3}-tCyay)^gJ{&`-bP>srGTB#mH|r6T$;)Y0bo*vmQcW zRBvw$6LW&s%m7q_)^^&{9pmUs(xt>GsfbVS#6Ux1v=~rWxJ1zoOl*q=hM#-M3#Tgc z$ET_($%-1i>o^^#Qi2YL&8DY%aN=<^ar|M@bzV%wkF+76F%AGWUwUD6soEeCA{Xr% zQsdFxofj^5 zzW*nm!}}qL|r(r4ppVtFhx=da*StzANC}0^L2eU$k z-I0*FV@LOzRoEz-1-3qWjgI0#vUUAohp`I=4PEnxK&`x6V>zSO8Eer-(pC_dq_-Ioo9@5Pg`qU+(Cm zt{VG}CafJ!xrW$LN;gB*Wt1M)dq!NtkcXdc9o1L5xLsNfw-0mGz4*{nMe9aWQz&b;LRjJ;8^EWYADyR zcgs2ADT5f34GBNaH2MulUNoCS)I9oVjq!M;uhis39wtp|{+n|G*_o8hwqA_NmHskI zOBXth*bIzvsYz~zVx=R0xg7b}D%i3e1Lxl6G69iI`Ad+~Gx5~W%q4(KR% zn+fThA=DwpNny6RDh3&iXb@Ho7%;~y@Ay-NCWixFJX7d%*3iaR6sNBwB-28?mRko) zk#}D@v?SW;7Aa3ym>eD$MWX!*y?HzAGzg+>#Dv30Lq565k)(aDy(FEG8$0j5(*t&# z8Z4(hYZ*R49HR-D83UJ)s1!J%an)~tu7E*ussB?yfAd59=p|RoJLg~h>tFjdzwMJw z_YO`Ej}ITZ!51d-#n{{@^Lw%n4}8X8p1AAO5c!eB$q$S=yD)~}!ed;!woat$fX_@L z9z;Tdt*}jN+ag1wl4jk5tQ_`2j3<&HfaB40T1wbX=4q?qC7ZH@ zZjb`TO1h}7z0S?NdtJrA#>7k{a+!08Avmb?t|GhmxdAx!1z~-rkj(90`)RQ?3NI6e zt=_rl<7H)Pvx-y885?_zUpM2jRZW(A`*-f${N#7N^UHtVm+xHO;e8OM%7Eg#d7BVx z1qI?_4+lOZa^w4d=!gIQ_kGX#p(@B&z4;LOo`SBl(zW^Vg>7dMGdPi{T^`uBY0d%xo5 z%eQXbzRd;Wa4;>yU4o2>`8PxTTa~I|^&X`rOu-=`n@%hT$hw7LoA8`(!^HUM^|7iX zli)mb>%jm-n)RCx^mNa`ITayw;F4k=EO^<(Yr>|#y_KnkBiG!AMO3C z58wH~M{XYC|r4=sev2a^D4=GOdF1>MNmnjA-k zvBSVH?C4KPb}k1FU{d^8u<%(-xn)MJ_d&@6%_bG^I_t$S!;Z+P`y1SLC^BLZ7s*VI{7Yf}#YsdZ0=c$PQ-CXkaHP7% zb+l$1AOLi&qFXIyYyWiWSLdNIZGmqRrI~P37;8b0h`R(ErhA0EF71;MfL0-(t&W=# zN3VWDnR?rZShIwVT38C^oGh+V6@*o-tURcyI*H_wCce@dOSg;A0NL0eL`1p%<&rL? z?UM^;Dw_i)B*o@Dgn8==i#{rYMrGYB zG?^SO4O`Qh7%WobFXpOP;4?{;hGSyXPBwRJ>^2>xz(iarrQqxiQ+!mlYreoLo(7AG zKJn8%6`Do|a+ek|uy|K~CPhu)MKyj>#0L=3W)>Ho(UzEas3__H zTUk*n^lFA!RVk~%nnAA$j1F4(^O*H?+NCa6v?rJM-u}+FUw`!2Ujbt>9`BqSo*lg5 z+6ymz;#nK24Vw;Fh4!Aj6^B;ZrcG&^7%2di{Hf~?YxQ1H~W!HE~W9I6SK|B$&;|cGD=C7 zdYMZcW7@>&o~=UV)Cq~}cpnyHr)N7B5u)LtfZLGAAOy*H#fXJS)8pC1fkqC>B!~*S zEmvl+35X|AUBb1e6&o{PUhSTKdK=PI1k`BlP_?kdc__6v=rvF5lj2P5#&r3zW9%_; z{*QSEJSTK&c0l#3@E57pn9$NFv*AgU~pvyRxXRQ%BK#j8j&grZXw% zv2*=V(Jj_1lhTNcs=8o{;X+cE0>G8Ao%CWZ>`G&7Xpj2rYmQ`0wqXj~Rd11jK+f6R zr^S@p@LZ?hGz@ebC7W8d^@4FRB$yG>v%xJ#2sCcQL$Q`wa=HZZ5j9j+0^Ki5I&qef zQk9nt6~G2}qY#A4HWjTJwknL5hjYGy#JRRk=s0aIWQDjjfJz|a#jM&i0~_%bv4L5# zt$4g#+YC zEjy1fTsvwWdwk|_#T;%&T9^L|0;jCeS1*GzpwSq9x&UsHUd77ug z6TRQHqT+N9GA4dm$Sm5p8gB+Og9#AoNgazW8SSNvQ)9k3=WNLB(0}&+Puw}-``&kM z?e4yO@B9yb&Xa%qcfaG&hxTt@92{JKh!+9&ct_9?KNEJ~4}|#}VdN7YcnDnn{g05k z>0PtR`-I&7iJ=!Wa-v@V*~?O#qEFi4=HMxt2^8)SxHT{p6TlwvHcC2TSWtE#5DZ$SV{U+BB%p9dlP=;Xr_p}m)0I=g-I z+SmTUKla(5@wxZzyu^jh{@%HF=%j&~rz?@>!H%Xpsj=OLva<`L(Z$M|LjwA-g9I zZ(Y9pP5;#&{=eV%FLtgU-#ov~*O>BHp8?=ofP9YxDf2`sr2j0H%_8Q5UurZnh*NzUY66*)vxV}yEaWZX{{SIejBY?|$O??aRxXJoAzBB#502Qp^Xp zAKlyi*sY82|5q>Xp7PxYd=|zh06m3d60z7b-i$u!^hlIeX(UB993BsaGDaLW+Nvm7 z1a(?VvkQLmwDR5;RA8@_D(b6gcyi?VkMKNqqT3^~M5w5Cqar!rVxf#b5l|qD8Fw5s zkB4wNdMIxk6doj4ir$~-Lq9@MQpDUW;Lz1{SRD)=9^6`iRoqtqg(MJT!Z>;`T?ikKtQsium&>Tx%D3ef;` zlI`eu=-Nbvydy!6JYOH%+1J04oADUPlA@+rVdseu7D+ZY?RDMEng!DqB`PcXD>rKM zU~n28YLhC2%&-315Q7{LaPWA3anb1M0MHKX_x-| z8WXNMIB!HvyEy{907qd!7(QxC!?;}bS&}J&(CkcEm`#6acqg*sL|ye97rAg3FpeTh zj6MDlTVFj~0zq*;l55ReMuL7*5`mIJS0@2oS_Gn>2530u%P@ZU1J6Hy^W#qbH@j3usq0I%Ov&A#I@1}sC^M_1v z>ea&>fzppW&oVecPc#vLgK3qEW7}{PU|U*kR)HZ?h)4G{-4?_C z!SgTOIXHOqEB>>ud;M#kIzPL4;FrFl%dny%1w0FaS4%jKIEN&_G3~7Vfa`9xMyd- z>}{X^&bNQ^b1&XJxjf`6gBlS=OSdaL^TSHso#0R)+kQe&(>$gfcgCANGtxt|94>t{ zRoG-@7;58=R5ZaHH}ISf>bp?zQUybRM03{$Bu!}ZWqRb$RNiJeil+8{!H|NsbIf!L z%g%VMi{bKth2nzA8X6Wa4wci(W0)D5s%(8tz;{)7kTY}U16Q(Yj=96ddSEgGzD+Yz z=N01$I2%Ze()fb!AeV#Q-lJ!sh})5biBAsb8atPw1D@mEz1V&C+g|bJ*Id7K&NrQL z<;vH=c_zr2v^W}=;tw6{zW>9wKllr$eDsBIedj;F#DbQW{#>8?0f3?*+BMWAyenO) zHpN!@VZzLXQ7O06Y}?rww@%RLmB|jjG9nqHZBRU=z?K*rbZiaHY^LQ)xX>F$h|AKt z=pNHX2JP%ebIuhF@ANhY2yuP9SuF-Sn$g$SkTUkT8~kd}zS4>|8U{kVa!<>MQv%dw za3;-R?K6fz##)OW`H#g&LR5X+ibG51xIN4S=v4=)xdKfvK znM>L8`zUJS^ZbzxrY4ch(#6pdwwS9*2%b?EPO7lf@5s=2R>6 zGz2t`9xEB_NZ_pShGYrNu|gbS*M^C#l`g%>Ud-$r0&2C8=$HC}3NRi`6`NMbz<6Wt zkw$Siy-(}~0$bD!pOaQSjv4pS6Rr?^F_m794*IwSR74Jxz6ag?tjN}od!n@uY_o@@ ziD-IVQKG*moDL00EDE$$F;Z>0^+bFelo^k#0hWP&_@6`-mxR`8LJwikD+A79lv92Z z=Yt=8;RDa#z2U0?J~OzxyZge~`RBgnp+EaYZ+Y_ZgO|^CeG%a3c!xI(9Uk~zV!nUH zvu4znOM)rsZX`-CSG6PuAJW40;$>J3T4P78@tsuxwQ%l-lc_-s2>m2v7I&6aPK8Z~H4S1eVciI7aHr;9c#)u0*qbfw9L zwvr%4Lj|W^**Xa`?`jk!Bm_b?v3bCJGtDR8q#RO7$vp3J7j8wNip%ERky$2yzo*MzS>+^uvej*`wDUecfB1^8F6i zxU}0ne%+0K_&2}jJHP9H**Q8o**!lwKjpW~PWsXoGe@rG2p=W}!9y}6p4St&e!8~1 z`^es-JLgAt&QAHJNbK*O@NSW^@1)K|4n9h42t!=5UeSTK7)zV6amWj@*!8Geb`y&` z@#s}ins#-p>P49cNLJKrH9t$3Mh$BEp+P=LQ*i@K7?vW72jd)Kk`2U4uC|Ip58M1( zx3rr)+8ljrlo=IkMUrw+3ApSxlkuvmUBwjwmI%-)QnaRPtOs_v8AwHz?a&<-0f1pZ zL#upA7hUW9+v$*YS%5t5*I~NQ)hY3LvciB@0Qi99PyCygkJ0e- zy32R>a4vk|^y1T>JpQ)d`PQf3c>LnsQ@#?^F9zlPKYSV(C|?Ai!kcQmM-p!-6ieT& zUQ%3oJ=KJZq~w@YV|?IY!;*s86=w~!au6~_&2JX+UM(>?4n!Q92`J#9z-arXf73v? zy2^tlsJkpE(~lDs2qU0P_;bQ+(bKc%4i*zShLvar;uTTzKU*0B5v-Zl3xCpcfTxRW zpe&w!g3J_%P&Awlg+tC5F5_#u;8cP-a9_QEa$YP7g+%A76r(hJIB2}Bfe-)QyT{w% z3H;{md#`)!^}pdQpYg=ejgxz~4)^$O9p>I2(`U=EY!Jkc>ta74!Oz8N;VvJ^;y zQV<4f%9N5QR}xAh7BrrBapbUKXsHo8W_i;up7OsAb(BC;uW6QYW=^TR&YW906muTz zbO9hFwtEe5QIUbbe+#j$!x4*EQ15s_!K5VApAYbSqn1LO(Q3Em$c8n_UbUmv26*Mx?5d9*X?M2Ij1oZ+TZYaQD5*F<=o8+B#pks18g;$qiW1!tZv$%j z5Ocg~?~uubA!=((c017NnYFAsXPH3;$KnyoE(&X#)V|?4I3@=Kr=+@UZDvL)3UrP@ zhOUPO4l5g@bV_9B%%r%YnkRZdp)jf4 zRMqKEmF(cgexo6nbp|MHm76H>a$p*{rny}b861o#psi`0JNaXe5xT=9zWHb}XirB# z*~3MkL|O>IuEn~bX{cz&%qQF96jcb1@t{0lF$Bg_wsJC7^*45=f`_4*2ulh}3pq?i zT|F}(_3^q{Y_|2nBgnZNHV)Ku>2bnVFds3oYL6yb+n=Z!QQmKiSJhDx4i5C{4I@vl zz&LZb)*dCOQ=uVt>!Cgk2@Moktzw$Gkiuy?-RvqFASNCigvNW&>GH-csbB?m#x5#j z!)VDuznnAZH0h3>TB2=%jR)jB>`BEHM}2R)FOz|%S70WXOo{W&qv6L(=U7k;2(wRVE-j5z*~pBCwUC{kIUWnFy~@eBjo~=zxSL6(2T=TTg$aq; zw9)Qj_~O>>o6kP`+{vB0{37$u|H6mA;8Q>QuYTpXzWcG)-MxF$Zz-AYOY{-}xJkM} zIZ}*R7j^@fH{naU5tEDPO-`zhl3_UtsFF;cb5e8gxy*G7fYZTUWqJuA9mhz86*;iP z><{bQJbn2+pYqAa*N@Nme$pqdf8+xn{3Czh>z;Y><=vx0zO0eM&g&zm3R{l}$4U^Z zZ9QJ7?;q^*ma4~(j{fw2`HjEl-}|k6Y~cLx@Xr3h2S57kN1pvSAHivk@YU}=Z}#CZ z5iTh*C)G_dfyg}8PPfM0w3rzF3j=KiCLtJ@lp8S+igIN*Um7!$t*%j$a;0X2yymdL z2nkOZ!Q!cmA_q#n&_scSBCp;`H$U_`)jc$fBg!jSErnpC<-&4y`cPtC%t3~Q!6!60 zr_UJbtw^anSmeR63GvA!t_2j*Y&MHo7MvsWlwcK|i9XU()qo_dL}h;^g6gR8t{|E?gi>YcPQUBP zh9s1;$pkby;`3k%AO*p^YV80ED^c;4RZw@mLTF`PkHCuFMq!NYZ(OvDW~dnHv`2_m zkzIL!mvUujc7>yj&Vz}ntxYuAjW$*cC6UxgU1hOLN}%DWSMUaC6*PQ>#FTMnP@g6M z_F`v2l_?|@8%l=A)Yy)!Z8k$XeS|*9(Sq~JfTkGBSwu=A1hThbt%j=7b1-G>5XM${ zkHwS;r$+OPp85n7s{?-Okw+?@_0RZQvY2_GCk$sSsi`U(^YB2~IGVYXCRD4qTrASr z47a2JH^WDF*KhjOnTnN&R0|=Za!PGfJmj0`i-?)bGfJSCENqpqy|1Z^w!Ll~K@0~H z?Hrehs3Z}s$>f@D(T1%!4$K-sCF89i(rbR}Yy0O~M01DpN_#YY?~Q+wYo~8o7Beo- z!OyXq*nmSCI1r>EsvuSJ&;+-OL<68-R!e>va_JyJ?!6^0>8<0{(J?7Ww4oO}-_AkN z(2w7`7$v96c%VH~RGxEWL{fsSZdx#p%rzL`Ye>DsQNb)p9?UrcHPz9tt{X25bWRs4 zv%G`n>8vodfb-$e;mYr`fX$$#CJ)i99CbeKIB>(XzCPBr)OiMF7h9$Lo@tNqdD!hn?4t8P%cK{&r2L zFg*k$jo_YS}P3~@=zxd2^pZ)Zw{`vpnPkhE3Kjq%ZT`rQismc9PvYK~u z%R@XPHS6-Ra3o5lF7|Wtlq_r`XAXx4Z%<}gp7-uC#@zd{Q03vZo0n;t_$lq0i0fik z#c?kSZYk?vIypai*L$8mIygK$K01Bz-k<*ZKlT0({Or-Shxlm(zU#+dFJtH%eW!Z| z1?CWSu6cdM|UN7pWpu4_kQK?{QNKc`49cVojbSr0^H|cy7deffV_%Oyj&LU z@S_7>^ELI#wWA_&A5DJQ8y8QL$Q3$fr-!Q6me3m1R?CEyC#^H^X^Ztq_y_~=a7G>Q zfts4Q{t3(MxVolD>X41Ly~_g!cmT`>FZzrk`y42CVE_O?07*naRJIMEA`Rj=L<`8J zO#PX9iS$_(Vr2r5uuk%>D#@s=aLgarB8gdDxgahj+SfVT07Q@&|72N98&t-SwP4B8 za6lLZpMmfepzi z!+;xa%kf_C4_m}ZB0&JL=s>gd6ot%W=~ORG#;@ng4`$T7fCR&@CM zVa+u)>onWVoop3lF$ zgV+E=al9hd)R5ahAO~YvC>$5bZ1LaLplf+1QZ#P8U{GgSNs*RI8cAP-$B<-9Z1h_e zoQ_P@2xkkv+G~}#npbL02y`)3u`rp+Sz4)q%9E!>rYjMKC) zY|IzN2AU^fr!4hfyt|@fB|{P(PDThDfI%>o1Io@}BLx&V_b?!dxmVM^O$VvzgDS#} zuxgqsN_%x_7}TmThzUlNXIav9+M@#+io$hj|D(&}nRXaJ9NO1p}A>X;5p%MVMEFxj^R1VGQMKZLOYh@2u89YMXJ>Ku^U-h3*z* zPed-Be!hTH41U$8tKp0-sxU8ud=ck`H|EA^0k{EGo~l)o=!`jAZuw_xkgBk-`vU!x zbzV%tK4H4@;KgMq(A?#X{Rc1Hx%{b*zWgxnj>WL&060RAYr5ypFCRPF{rq3@s{Mz2 zTP$Az>6PrpKl*BsRj&jEAR%;l2xv`YlgAZ- zyTVGU4#pVq43VS28+((|8TuTlGmFhY!lBC?km%P7X+xnUiK_SK81WerROGC#$w3`< zj=nQSapRzTu&79{4S>{YXkgY|B*$*kFGz$k(Q4chCO|xuY2*P z6q8a5RA|C#-eaj7pBDfgf;^GH;O^ZMzTooq$?2Wl{pWu6*-w4TyT9{~{pDZz#!tR? zmwVsGd{As`G>vid;Xv>GefIdoof=mkl#udy}7$8mz~tGovX}NM;-Fd0g?|#P~uS;D!{0raw7ykbD z{-eDc58dA3hno0m9zN&b>}5QIfKsfGH&M^AOeR}A!V?v#c zo)C1aN9wa;H(3@3dwB{#K4H#Fmxp|W>GOb#8i9)B)ks zP}leN{`!x;@MpgJ=X!bZ^o%EzCS}Hg?M!~t-u;Wnnjel1;xyvLKYBfNH=JT;Li){a z>Nyb`Z92xpN#f|JojX(G9G0au`#g|iQ%>ncy3*UwW*yf#=Dj&B9`ads^noC!1LO+1 zk~v8F62c=`w<^z?x;#ykNOi3Y7Xr>)po)`%7#um}(w!9qKF3I_EY+J=X>MI)SefhU zm`@+Ucs;q)hd>@xbCRsC@=@LCKqnsA@yNcj{8b3Spse{MNL8&gaE3F)REsz@qgoSG zJFFDz-?-=mMiq?2iEyzkr5Q+rNgriv`k)}nIXMNo}vEHb#z6#@iDl$uT0c53$8g{sixAm)s3@y$xGT}5c z2dFk>SIvV!Y4Vcb#vm1&_j;(NR+x=(Hfsa&8vcc|v}u9fg^{sf%rVBCqw0ami4fEt z$2 z*>+f}hbQ~8$y`o~lic42X!c!I={4bi_iuZthd8}n>uoU9izWAxn2tQH)G}HeLK_Yj zBcKZ_u!GNomZ?xC!h{Hit|zsW5DHZyXWf!Nkp?hTS|&n?h?tu9Ds=GyEba>utCFyW zdjt$XX-@`NrY9q%5)AJjQqbgsgT0Z&>#8Eq#-tJ6+;ucBogjE#dmre$d)j=g6Mz4h~hIrRR-1(bsruUuGqaXd57kRPc z5aUu$fDqvl>1=10FUx!FBZp7C;@TNY0B?}-2LV{YJPk`nZ=H{0r(KCQzD`Xt&wu27 zHC{VU&8?`es%WfXU5gAEa+tIPRq6$-VXI-wNLM`5-EhGP@8hcjNNJ-frBs4575p( z63NZ0?qs2Y-KE9~nju@O-z0|WdDnC>61c+(8#N5T@f-^~y!znMhc`O#5!q*7dhw?} z`tgIq>(Bkf&%NXEH~r6l^e=wq+kg38X2Zd;7wBC7`RoW*QWYyeHn|1A)ac34q*O(5 zNZIUS?edk6J@V^5=hs}n!F$Ynb4KUVb>?jN*MqmY@q=Ht zKmF~$^bdW>SKay83%6dpeZmJ|ytU>BOidiS7J|~4`N{x9Xs%-3yT+!1s95qw5Hk@s zT~JGz*JRcV)A#Io{5x(sYxR{py%R#HEibCe1p_@bb@GG=1E-TNFeUuJJhGh6O%*Nj ziJtoLLr`UtA+)bS_Pzzdi3Ws6h*u-@MZ}?V?lLQzBx!1F2I!JmRh#jUwB8iluQ;^| zESdeV9Rqb;U!30{t2V?lF$jI5q}D!M(7>5zJpy9p{n);aJ8Rk&h zQb*QQbAF*j!!a<`G*oR&;>ERl6iVR!V zU?Rq>ap?nPf@fwkwnoGmLBuudG)AeFIxv?N`jk^vw=n6My8@Ak!x5nd#S?%^4wEm8 z7e@Zo>0e}PMIxQ8t;b2Fr^( zDS|QS*}kXfKh8zPbvUSr0FyS<7(-5dhR^BELeW zEf)MWn69OO+Lf{rP4GeHdwls=`$OOQN6T zv$1a25um>93(FXJ46iwkudJiL^ryaH9kcs51tLlrHGR%yrx)N0f4a@K+7>HM=>HSucgz8(dZ>d}#Ry^|+{*M&yPhg}HEI-t}%at4{DBK`Y%(la z<(DpYZ*-=|2M;KBBHO)tzVqZOk3Z?D>n9}Z;E=D>@J$?D^0X~eMpLC5dphj>IT7*V zX!wxZ>WL9q%r?&}d^X?}JRnpG$XJaFZNxKPs`=cbry|z-UcCGT7LAo5Y>AUWYJLvX zXFFls_|EEus^Pc04*h(+A#BVeEN;NieGPuObp- z%MbC;jAJRNu0K>3a3mz-W1dZ!j_fBC0q+;>@G=kT{)hn20Qj!My}i@DyFc);AAI)S z%|~DN>QDUiFFgI0cYgaf{OMo&u3vF>``#gM_hP7Y0Jf~%1Z%Z%+9WSudt(I+~uHS@!sj} zw|>%_pLqRie&nD1;J1AJf5%;azOVm;uleux|9J--nJsl9tbCLO#LSw*{mXsc1b%t) z2Y%aoAKN*)`P?TSd)2Exa_jj!r+1Ez_&!jtOT6IfZ;pnm5infk(gk@avQ_fnW4t<& zCfu!=bFgVSWK~8BIV+v+)N``4PeWrum{_Peo0>RED$Xl(dY}-jgQuE&as{YLlM}_1 z4l9NE4WqeQ6ZLMA%Bj`lyS)yydE6x^R}MD%g=e&qkO+- zug1c|2hI*Id0almZ%Oa$ecShb$}l zE!~=eQbNa9Svsc88pWfxF`ae?Gd+x20|zupojg*lX(vxaQ(C&_gZS9aN+KgCYDkK8 z-_%jY=sOai_;g4~6KTdQHsQrC)6@qfZ7nDrJ5bTB0u3sXUezkq-Kd$OzRZadH)$Bc zCb6%i(dTsPgi0^+aWp;?D4eb#qx*tktH+vU(HfGjPyQ|br6r_^Yf-|$^n)>u^UfyF z98Jx83!BaymW+XFx}!s_Il`#etZ&9lyEKqu!}x}eVFx?rO2*2GF<>e=Nydv~Dl!Gx zR>Ik!r(c{=$}n@(%*MjiSd67<^FH`^fp;1r$-*c46&gX=P}YSHRRSmBuSG6RnkTZ@43UF?-PZtO&5z)LKy3&@m8Drr0~+q`r0W?iGy}p!to}Nt)K1@(zl^95NIhzorXghD zqhEfS6-ztsK}g(x&Vzv`w2nu23qdxb8p61sHL0M>#a(IF#%NeT^8lsLARLRUEgdUk zni~kc5lmI>i3+&6lny~E0H8^KmZ*V7Rke9)kvCM>ceaUI1Bxss)2W6D*4T%h-7xe? z8c`<$O$CV2_5{-Tdjf%uw>Niiy)>^N*D#|Oelq_Bm;G{H9buH+=CcWg`c z)iWl5oKn0|nTsbAOYDt=)k-^A`ZgJfTQ+9cVqLNwDjn*f6u37=@1z=MWz0)7opNND zoYnlU&CdDR;r_u-ee}+c{`}2{xH7W1&H>N-;N29rFD@Rtw)ab)x&eJInoNW_SRem2 zI48OtozqZ%YsVHrb5nIbR(mmQA- z5c1^c7+S&Flb7j}oPJ>{fI__^X*!n?u6URxVvO9h(Z8}l#+fB%q-@!zTAtbPXe6By zC4?ue{F*8BPcM(I?|tCqpZeqf;5&Dod}#O4YcGHJ`KO@b`bshko|i{iADl&hOs4$kG({2qoYFPH{vgVNIB(6NGFRQuc_O&w|r_M@`6iLOwo$HAt@z=WV&d5vSY@NW# z4ArcXSEnFkbO&Rs6_R$^vM^CHa%M0`_xT`h@%U^XVG3)zWtpdLc;4ej(G9#c#%X~* zF*>Jw`00Xf88AC>!GJ^zJoJ2z88<5Wt}xzl##{gQ`1XRMBR(vC{?6APf6nWVxr!$@ zAo&W{<>m1OzaV$<>fN0e@9uo{-};&V>H9wN$noCcS1DOckPZX|8N~ z;P_g`Hk{+ksV>Ca;Bgu{m;jO-2`3q7!(K4LJ_-W{|bb ze5yu1(T;k6Y-$1Obocl&a%HnNfzwDjX_Ufx_0O&*jGAh8QP7)oPBRwTn!4HJh4~QH z3VO&&TOFs?st;{E2;9K}HI*EI@}Xkr1wRBgt47yuf9HyEGM2=ktj6`i1I?5rlp-5aeyb{n(gKY|H5LJ)e80OBb%|WPY z1QurvOqC1As^_STO5I{|$CHq`OiH}dSVFpnp9fZW+6mQ6i)S5y1wF6^Qx1jOk~!gv zi4YBarC2&0FR>OvSk}TWLuJrF$QD6 zcnBmDk{O1@T25vXV&b&|#5h?=hOEURfe;dgFtI0Of(=QQGX~oMhB(G}urU^3>{xyw zsr9m2)}z&GwW_-6R^7Tazu*7cr@G0hTj%b__xOL0z0W@9o_nraYDwsr3mx1##AUM} zl_ZCMM2(Fm5+`2O8#F2=vtYhf+eJfP$)-OqZcVFVR8hyXpzsU;8Ce*6NC$yFjmj{Ycv@FhsGNTxww-3#tEus$>YcbvCV1K82`qUV zw=Lxa*b{0+C`{X%KQ$JBw&j=uDI`jOuxzIYNj#jR1@hx2F`~Y=*c&bv7q<3KKe5Su z-`p9*w;l5`T43ZOaA5EW0Qb;}2oxTfIRtx%_AXdwSf36g1{XzgIXdDKO5!Qv%n4d3 zfkyMPWX9Hu4$x(9qh;7RAt{48dfHtR#4`5ss$FSlH#zl0Bw#;Xae%#zu{@|{I-Yj3 zPro5Tj#LbK39(K0yM+eidL3+PgxWOGoYoR4^!ai|f!yxowp!V!)5m*ZON$bMUFXwB zX6STWnpSMH09Q#?IbLQDOphONK~RU%>=KCjC z96cEKHqSkGY<>Ep?|jFtS6{ooSa1(`MM9f-u_dcwRSXLN!cQ=S0D;3Mr-1A{IpKDq z@%Hxi?qdFm+rDvcxy_=C$g>jh#R|;v3@HWLD@&->XH02KIb`TCfw z8}KW7G!6OCXt|s$55@-vC;BV*p56aj|L(ru`lF{#@BpCY&SGclU@ad;APA zQvvW4FTA)6lxi=}dFr8C0y|^^Ne*Uuhs8jZ5MqO+hCqr)vuYr1^^kxUkd~rKq*Z*r z&P0?l5b>3sG7V%1CkR?ed4`Ahh1CQw4Wh4!gF7=5x&lPc2a>`v_xPJN2vLH4)mhZ? z+Q=reo2h_8E20S^_n{D{aKfbN%|;d3SiY&=B2q|vqC{O2xvEf(l5AEZZ|ZFuy{d$@ zYYU_7ZUI;XQH_6+ORc|*NrhsRN|r>WRvmGPhzpb&CCx5MgGyqX*zmo9iTPA**N7y0 zh=>mU!Gj@+358$VXk^FUjdzVUC|0P-Yj5;XQVScjVUoItqnC-H36#6hhT+nReL}Q9 zQfR1m)J1!gr$5ZPRgGms0ZryAsDLUUQ?sODYP&dBI_phiywmkJ-4KwE`j%IS-8GO- z6ILQ4nW~}9gr+c^ji_NNjgq!t)PzQj<3N;@{E}R9T=5iNp@3MSNjrl9xF)V*L(L;} zHW0UfX|-fDG!+pdRoH_m6~iYMjf>gZCA`K^UOJ7eNYUhX|sdhaAD#v^^1{Ix7QL3(;(!!yp27@By zAWvt58Z>fUqB(6k&zBmMoa=O{fGXP9Bd-x?B59lXC)kvS;qj#FLWrHQBg)4Ma$`Pi z5P1|64C{m%wAi9Po#6se6wpZmttA+(v*o%(Qn`BA%cuE}_}MP(Xy-;!g(_7N18dm^ z0}(s?>I_X;h@s2_CYA;n=2n^@f+EM^=m0<_VDD{RtoAr2uAF*&Yrena`*M4^`=1PO z73JfAR{74{i>_I_;>2(dV2-jZ`Vr-a>|Et#EI7GwY*t(JWN$53>GB;W&;licRP^=I zH8I&P?R1zpQbH+oCFwgY&#r5uV%@6l^!iq4bh{g3O)dfJTU}P*s<-qZGd@i+M4~M! zQn(&vnM#;I5=}JI>*ks&sv+g;hDJk;!Af^zV6s89%pcKQaD`f-u3v0ck#8xhQaI=| znAA{44}S(dFEJ$(h)K_xjha43>7tCw!OnQ0u?;ol~#b+7CX2)K}k{%t)4)-!8lEy6gVSZ-3(7fAE8| z*~ZRtzPH?ErM6h%Qf<6#jL>ON*kGhwn{xG8e|x_D);r$(wzt0Nz9%34;h+6SKmLpV z;E^+r3`b*l2AWJt!x8r?qc$C2Nk5lEv?JBpRmJYwl4FD`VEl$RxO5wsjA#ODU#QKK zbR~JzKxQ(D$VH_R#Xg62QAWLG*Zp$GNC@Xnj$YKy_GIQIO(nX zm73rff6F{CRWYopP4#w0rb;&rh!PV!_natI(V;-8`H}(dsUI_jK}kzBR)_=#g9az1 zlwrW2WOZ^bRf`tbX#L3xD&M@BiY1J6FsGE4vpKJ6!nZjB+<)m)U+Vmqa2PTM?d5AJM30 zoa9Ux1ESQdiFJgJPAo7R>y@y_<7o(54|8D3C}T%PB6~J!h%4c#+oJd|$fAU(^f0amJWxWx zf5DK_w3E>?yF|gFvIWr0N38R!v%Zqk)p}`ZM*-DEQo(ORL@h&JVJze-=r$UPY5{XSRh@lnfGGEduB-(2)Wdu}1 z38qCG(hf@_rcXxXHr7;16}qJAyrOf8%1|=-l^>uW3%vNDD14Z~S@PkJsvPLjtwkA* zr9bk-C$?x4h8v79$YUX$5>s#z(_F&MKBG(hRTdiBlFwM-JRfNAB@UXOVJgx#938)K z3mMVn-uN>uT$n_$1|};^5;x`XMuif6xzeQSG&=63VzW;oZA1*koRlPVEg;z9=3t^E z5Z*2+vZ8Xsa0>|Jz_`E1ucq<&S>MI_ zp0=D45H3D(ro&^D3^t^?Z zTBbp1P_)NbWKMIwu6P>HZ}aA3HE{Cl|Otzu16L_#Kea1&X%Qbu5a znyASl>er0rNULt5Y7j;}Qk|ri(cge<2Dj^dKZxfNRYN%j#rx_Vm}E``7P%&;BNw zm%9sot(PmbizF>yaNx7GKn_}58|}kX~jC8Djumu#3I!+;jCbg=wybW#lfl_g|o+JI_EF!z3__JZO6yX zulqetNyi7hjn&@K;p%_<{L_E;SMNXb%<|ZnWa3t`or7I|Ny3E69g^H6spUArnLCDK zkdDzsM98xNHg*{VL8wEZi1~(#DBzyB3DYcEc80-0u8eJ=U3jh6Ny%Px;a2HG&B=%D z1{6}1K%d?^JSmoLRmf&%`qL1{L0w^my}F>ZfJ|u>uPp{$8)CNbv?vY6#*W%qXzeLE zZ>l|;D=6auANA~`ieijTxf{x5frs_+26qHP#uZRh)#PtuQ9lDq|qRN{aEfmWo^+r9mD5gk?&U#S?io6z> zXT;4b`Xh~+@1d}-1k#qchNKZl#3r_gOO3(QsY-g=ZGuE(hb=XBj57sfvp}xK6(vch z2u!)g`FH?i; z#fdaIGd-!?C8yRKB&loX@rL{Dg zpgBOfj-aS|CVgZSd&LO}4TZv4G)!b`e*yM*{lX;+9m|H7`)|NE4uvKp7)p zkJ1PNH<0B-tdd)=hCBC`A>>=8bO%ph*Q`!{EjiR3%;$rBKK1Us?{^;ikzcv@v1j*= z@de2oe^kp|KnK0i%E}!tzU*MgkLhrSESHV*tH2!5xuWYYY;soM)!TOEH%DU8qs$Y7 z6#NA-(bjBfw2cNn^yq%cK5IxXPt%TzWCk~hi(fdDP|(i?H&Js9*(9J&`c*D+n=M2d z^Ub1S(doEO?tH^Do<&Y<%uJQ!gusZ!-Jv%?Y*NZ%o38dWtA?z-;mXcFwrV6Gm4z4k zlO#A^&zAP4kA})u<4J6SzzC8{yd7=;kPbP3P6=ugSM%MFT7|NsOhFo$S?DGv?7TS2 zEkKL?{$zUe&%XZHJKp`SFaFtAj=tbHUk!it8(x3g3vS)r+4gq<8WOUJvf|k)r0&(4 z0J!?kWmtnuBA{R}%6TDa@#uq(9(~@4SKRW_o!#xBzbOr|%YF71NE4!Ex}?kyC8uEb z=UZ97kzVH3kjR=tMEqbh9B~feg2{*WIR3JDGTKH%pg|eHA{Qt7AsKRXZSAdhz4hOJ z_yd3QCx85br@uCx9zl{%d)HHZm6&q?62uUecg<3)_wehG*TMQ&9hz>gq$Q(I1o~Zf zbIuN4UgCcW9EsVmTJD1xQPA!zv^9K2hw zl}T3+px%52oYk}z548HsHL9))uRVsk#In~RI@J?2g*fPRzs-?fy@{%`1fljz;Dq3C z>2L0|@e7+rGMtb@7SpXqQ2J$b`QP&+2$BW0rY>g%toxZ>@RJ{y@^Avo8IW{NM4S~Q zCZN(l9{Y3qjTXm{7 z&p<1b%LVC&IC)cz^4Z(A&K`i|v_b?er4UI;#h#$rp~4_hTcbpdrYX++u@?Dq@G3iC zrOXLjO}kLkaktq)Cj^qZ)my9RG!r%(Zg4B|YKoSaodpt72%pe|+O*51+rnxa66oVC zj4G!qHn>-Taw0@l(mB{DBd0KjNI9r0&Y+Sq+B+dsVM!`BAE_Qe#Z{UNu!APYRfkg4 zD==qZFh+&0XmaVYQ5$qvwFbBTujZ~_Rq9r0$+Xy}l89+CkRu&7pee~iMd6Ap;p&Qs zQ5<5k*n&FP@urY!RA6+c5B+NG`mm*H;k31Zks>l0tIRaH1w~c5CIj8H6a}>cL^WkV zgpV5Fs}bGW6|hK4hU;6$rQ15 z7()xdl@n#W2btrZVCLYYU0$tc;o+!mp|uU(Vx{C{o~H7E6RNUH7bTBGRfozuw{M{P-br_JO-uDb+ajuiKYapKJzKhRbR{pz4?i? z!5@C%nIHSrdrzI&Jpt$bk~!P&yLk9Hc+Iu5jkOWK2evw3?d7imdufgj1F}4GdFT<^ zYq&Wib7hX>w8v~$cleg|j3);A2(Kj&NCpa!VCEpoQXqQ^E*E@xTqimzx3UAt|P&L(bdQgRsTB;3_JvM|L32e?Kw2JI- zqaxkm28Gvo2?=|PoGA^K2SXl#xHdiV>HEI?gFo@3|NLM4^4aIkpSbzTZ+rFY_m^AV zFqHFu-Vv@)o-*BL7mR@z_Q)nhQ;s_gCG*jRCGbwT&km^!Z65}D#9ypbR}XshcdCJUHXjG zkO@e}WN!&8p%O}~v_Svpf3x+XbZEWKp&C(n5OU5`shB$eo6RMCD6J}uO z2;|5I0U1N&$tFKEs_f7a4tMh|-&(h9mqSwh@a^vJ&!)rMubz!oe6BI)3mb!#XO=5} z@52xO$|s(hjMgSJ?e1{f&x$_qL^7}3P^K461>9sPag|1s3*u5&GM!HuRyZT|q;!~1 zzc|n|8AuYRRc{KBEzFsQ>qoVeAR0gbED?+^2$Pl zT9c@1zIP?lAx!$U<%U?uH8BgPlZ}xb0)>@QNTW=N%F&*@%N3PnVbMzr5fy2=^vV&y z{0~QBMVT$xsg!~nD;j^vPG7Z@SVM1uEuZ*NiQpiO#$pG-c?Ok(u0m7NFe^4 zKDC>Gq077F!zAny%8hEX9U7Zqw;bSf@O3-P|QlS0de%` zI-e*l16ML^6NmqX3DQj99KN(wSJ9<@dK6=ho^JVi{syX^Ro5cm%n!5@4Wy1&p$dpIqLK4U)&kP6Sldp;^wv08=p7j zXv$MHc#29ccX7$BuD5Z}9eA8{iIvrWYdn1}Ixo%vD#z?<)bKj717v1Hav;k^mk69N z^y@OAOD-hjh1t9sXw1+&cfQy(C|46DR@6{`)S|>*w6OSqMjCIO}0so ze<_F?F_AA{qD!xyt4%he2#oybTZ9lE7)8jYB2@)pG^k0}z~qkfSDRvua1azcBT9t) z=n8)Yo+a7L4&_oVLmD2yri7F#RJ&!Ma=PYcHaq}@RX<+xT&<6MvS>J--1p>}zxQwc z+4uaMB0-9K8dip!f7R^k0UPj+ep=$MbPExCU=Se zzY1hXMpGiCVdg{m#qb(My+1B#}WUFboYZM`a>~MvK(F?yCnI8-#{hpj#Z7W0Vh{4JFn}+Avqkd4B=`~A2j9*#L)={RTtP>x<$iWyg)j=GM%9bm67An(? z3l3;s9SwV4ZqJ%LpCobh0STN_LJ2#=k3xy!2k)-Ga(eUSQ+^|Yjq`x}H3 z&ptOEAK|wj_vbq#r&s;)h-1wUpK*RN!q8x>*74FzQQ_SrEWemH_CwJTPY@lg^!apj zvVYL$`zTlkNsC)XwE;5Z1XZ6po=qO*R)>CiLJ;5pWTVLz$3des zZ*p?ZaoPb#fU83D3W2%fOr0Zo6dt9_a4x~BB1Ad1ZuHA3PhxAntUC2=*#&|V=%0!@wW5HWshZm*v5d?^qatum@K zYU6(u1h`@-R9YfCy#+2lQ=jIg$$bb6l+w&Z__K>KQ3jdh@LMf*QOO(F^2G4aNu*UY z#c6u5Hk!qqiNiu(trw+=kV}Y-LswK|HC1ox#8mp+N@_HA6(}_($U*@&s+w}ihJaEP zpFr1W6xs|@HA|ayFnB> z--DR?m*nv) zt<-&qhERu#T_#pFh+Of!n{^JUP|{lSj3UY;L^$wlph*6u$3WUI$VMrFa)ZQaIB>!q zVxW02NciE2y|Q+%U}(~jtGMJ!nzUNt@?ktcFJ0(ILid*f4Vn&;V3Vp|3aBkDDn_an z*cwT8q+V87_9zlj6p@G#0YMpq92_`DH-L|xjD6Gv0U+XrpvNOwn+CH|T%bv5%q+;` zn*DPD%!1Ww;qG}T%KdJmzgEg+g_!El2_d2$(-f`AZWDD z1p#!vSVQ`u0c$^Q*r-PbdhsMr#fAsY%D^}WtM#MN)K9StBNdMA(P0{MOMpJC%afkJ z=|wkPTU*|drYogo^Dya4pez>EqYAhbpjA1_+N3)nOMIZNqpwFt*%H>I3w0XHC3Ys7>9&@Fa%Ayb4BrMf@(!I(AGR&` zN2Arrbp4MX{_6L>{V#v|4?gp*x4&~TS}vA*hEgtV$>7mI3W+z+%S(iskB2<>y7a~J zohPuLKlki>bLWa{o_EU)-@r3f#@ur634>@@>wB@^QwtLa2>>-;@^O*`$tQmE*T=c% zB^mv!H+g|7*@r=JqFRS*xCR<3T9m2Jvn&&PZcTzD{qS)Fg2_P>KAi583M-tpME0Q< z(;_7s5wFcC#E&*wiGy>*MqIp4a9SoLnn6hm{AzF$s?p4_Ov*$D;9`f8NiEtm(U+sf ztfXM%eT!yHOfL;VXt+aAZpbQGqe_P!fDkviFZgn@*%s4MK_+8Ll%B8Y_hht--B4D7 zfQNQ@lM7S@Yr-qsP2{0S{sjh-0QzLXWsJ{5c?$hDqwb95s~? zi`54&#hejJEr1Y(Hbh0LUrRr4vbxni4LVujtt|wS72tYfYIMn}S0&HFN+}^OgIaG) zjE3}u2o{CMIyFU1bt56xiANIEYHu;<*DMdGQ5L}4j1f8J7f%;0j`XfCnNL9}91jC`ZTVcC{oP&asNbZ`}Yme(FmAXBoC zw3hXT7x%bCwzpi_-8nH_o;$bu?hiin(LdWf#!u+%_c)ChudKY`1+&@2yR}*RyM)6t zuJQ%LfsKoES;l8c=6arjuR^9eh9Fz*+dyY~ek^ok>u{WwUZ= z@fI6R-V5P%)g(`SSPygSHi0{?@aqDRQmGF1Dhmcr9upDZA`d!ga+r>g=CW{T480PF zvc=J=y0ev4Wf^5W>8rAl2N%(Mv5yo4j3$Y+vPWour-O>vu)G}5cjx=_=^FsfQ3BOH*TrQ9Y6 zTOe#{l{|6rP>CV6A(hZyKdpI4E}N)&D}f~9r(mo4+n+$8A&W%EpR4>xPV!OK3o_D5 zGCHq9Rzf_#b)_RwWPnXt73*Fv>KC7OLzS^lgpEeVY=$kia$Vo}EV+4*vQ=Hh34GSo zGA2Zx0MzjG+fa^E_x>c8iv08iE+T_9hSTc^<;YCPWQLDx^%?)H!aWTjBgWb1JTWJy zoFcAp&p%o9>gzYAD?MmGe(L-?Kk&d;9@$+R4)%Aqmy7NF`JB%`eR!M$;igOIlf3oiHbBf#_Z0_*88V8r19DnZ{U-0gCe9JZ0oS5$}IW2)(0U71`E)fcmMlk@kk~|0 z(yZY@q}X7uQlm%-ja-@tmip4Igwj&-oqT9Q6mROxW`p&5m!&*Vt#;XGn=NHmq}bDK zl&cCE*ZE{fmyPz(M5Bo7QXddfhR8Kyr?S1v3YW{BLmldGC?Z*pRcA-iL7-Xh1wir>jFKjT-Wo9CK%5r!OLu*$T)_v_l|Ku@+jAAg&B^1=IBQ2A+r% z1ulvT_WW5@NZ3^3yg-$?H}`UvT$@xVLAno{Q&)BXy1WTDZY_m2$fS3?{GcZO6{6z6 zve)!2*%BwS?XS&(ydmfssWnW38rlJ&v1myx8^K&e=R*!e^dv5%xLOV-1WyR?2$dp2wNXxEtB9{Yjttud{`-U}L z@oM!$V;+AWJoL!7U;U2Po>+Q>>%DlRfAiJT2Tt$KMkA`Q z@`X9TiwRko+gZ-$KVTaFB|>#H29_l-*-3 zgBltW2|dU$K>RheuCa6&&uqe!1*Ccj@&hh_`Dr-H5>gaFEK>>RF@3t!2%S-f3 z=MWQ<%2r+HMriB@3^Wl!3CSoGwwyUqmkA-16vO4?{*nNI$_UWdG%77k@|P5JR4_$i zD?3b4TJ(1Ny4ED-09R5{B33|+BKa(cL6=8d^P$GD48Sh0Ao5$mlfh)Z*!!jb^zW`c zdG-4E7|%xGb}*O}oG1!ZIU;7U7H{it7VBh?Yc$t)YGWnKeG>DX?ek~OjgL)V|Hiw1 z{ntLkhdZ3z=`yrJWwOL=krz7*nHWo{pTy?FsD+K{k}4&lD_5lD zyxp;2jyPG?G+0QQ`^|GLiDWWO7(r}SumW?`+FL_aK`McYSeS?Q&=?VRL2jtU=0Vd4 zNLwP@t1u$qa^}FtTB_>|uZ-$1MUfV0m3Q=dcm;t$;c|POrC_KDB`{F&&t-V_t7Zbq z!C_+Q8vWqXKEp&IND@xoKhI42BPpdN|EI(rmc8Jbq&R ziKjPJ;f0&t&U|_6HAh~3%@Ic3FMjg$|M|P8cjx_eR{uQdpAWZu2HJw3kK=(qPNlfC zjYy^HQJu^SS=s_zPQq4KIe{IK^_$y!^X2W6{lV%C^q zXcAN#<1;ZssvwUhsFB!>7AwB=X-d3Ds5MIAg3^ViFR*JMlb*cl%yHMxKP_Sgv!DdB z#3qm81j!y|Kb;c?sheYemY9G_GN_VJRqW7_+0ywoAH|BI%_Cy>G!nL?L-gU=zz_$} z5#+*_^%w~Zbg+w9QbIrwxC$oFMs5NtRD8O_=D6LKO7-}Tie%Y0Ps$gcfds8nEv>`i#LFrx@#6D#! z9RhFGhtRtpj22y(&UI6MPkQqlkL~Q;blXkOzx;~Lot^P$xbQ-_&KJ1our?{F)%+_N zUC{9B#YfkVy!K_U{JpzB#q+1Q9n5n81}3XaJV&Y|>d-qdzzQD1mG~)#gvf9!i#fh$ zc})-MAjf;4;BtV969Ibc@XY3L!<33?b9dE@nQlX$R%mcUP@n@J+iVMTmSS#7HBeMx zz8bEIn$v=9G%x`OE+8wQSS=OC;Tq*BS700MgNjKa=OSzeOy2=rPK?WAa%4R zqd3LDB+hwAx7~yO*6#jzIJo7OE8qC4EAPB<nmI^ zAqQh=q!o6MYCFdi{K`y2BrC`0@Kuh%A0BMtYe{qkJO6bRPUM}d^@(&UkS2DHIy#-8 z4)&Q0DI-!Q)gb|=F>m9!0_C(576~jk7zi53p^_~51ZUv67fx+)k#qC^i{%|Nnf$bFwOnfDa@&LE082_NJ3Ul`PQJ`T<106GM3E zh(J7}&JY0yt8|Jj3B9E%L|PdU@acgMDHjFm9$to_<-O)$g^SyE6Z zU9H@FB{u1!AQFq!mfFHql3R-mSqzj!M1~j(O|2-;n-aRnmAIrR%+1mAtFEIKFdJ`C z&Mxn^)pl4_96}D=tb=J|6r`X8s#4LAG6Jl+Z}QU_J1yB4dIwcm&lo1YPRaBInfy{A z0gu#(m)E)6Nr8r zKpFqM!&SxHQjq1BE;VCS!kA90hbE5)oeTy*9fP53K7kP9K~)|oN#Ss`@FB842@k(> zpeH!$~l0CiN)SzFxc-6 z-uLm-&s~`R%4<_INa#&fTW!cGRHBiv&9*w1hS#(GRjnR#b(VQN2NnZ+6BU_QH$Gs}T7G_~b zj*|o8p}`yn>>!`bX2iO_lS4SZqaI2`@d1s6IGsKB_wVlR5@%X%bQNJ5JoEK4JXh-4 z7hZeQjW>M$v-eCVlR4*&46?%N+8tni$b&h+3L9|XA8c-I-G1ktJOie;Sn`Ddz1v&O z^<|)PGC)I)d>KPpXxv>)8M9ry0N2O6t!xglpmnP6fy+V5G6l zNSS#=ffdk!kf-)vc5HCdb;m#c-yU5Z&AcU$0lHXTcXIOTmtOG?|MP>t^(RkHhkSQ< z-yiv6ufso`K$3Bl(mla!|{RsyujYI-N1v+>RGYp&Y3^Oe`V=0zuNyL@N-`cwia2!fZoo`9kK|ONT$fPr@W5D* zNRJ_dcyv^jLM1w_^aI__pY6b=I3+7uoiY>cnx-_89vdw<(76JWM9eiAwc+8MKsqW^ z*CGdTv?h);Rt}5Kzivw{RdyKT{ER`WDu>u&hH0C8@dEw838FN*6J@p%ujz_mla4@z z5}|C0Hj^#G+9)GWDJ8+aSPe_e2Z<@vJSd;DnMlI`at?%Y)z0*=Uar&YsRfKwb|9l* zs5UStWJ)0)=#1<#%m_A`(h#H=rj&RZ@}{x$>MQLfEX6A=dddgMCUlAC7^c7lp+7^Lb1wlnmF2r>W9bBqzy(&fHp{Nv+!Zrs`1Zr1dsJBjwpFFh}f-GI+ zQ9zr8P#Hdt7R0+~bd_@K0#YdAci6N(Ef+lzmBkqe6l=m4nwt%}wQQ3ipjD89$`loZ0LcNsFhc1rO_mBj1Ts_UEDj}Anw8ELD>{~HiI~jPy3POM zK&<01uSVHA<3pO_nS2ct{0{eKlG->_Wten|X){H0K#w$umCf-L2eX`UX!}m`XuT^G zkh6R+UiN4Dq>ZDM&w)1UmbAtsk;7h=88yR{b2D`SqIS$dG*ahefy+xdM0qDl43!J5 z339Q>=7j6|1r70ZIRsiWyqcxT(i6FEBEbMRBWpOZj;m7~f_VnT!S;o{*^%jJ#N@@= zTq}{lMKtF*AmAgB3o~Afj|fmiLz#mt9_Vv%pYOR%2mKG<{nW{6|DAVUaqEfkb(c>b zeqx`SxOtGwV!515R{P`Lvzyy47s1?xMrhfv1e3wXML}=U7If^!C~y4r)^SbnVo_}J z5;?xWWgPNZ6TrxIE9}QS%*} zMb9|&0FktXUd6i)BiYnqKPqNmdr?Y&aZZ02hZJ-;ine zs*){kZYPC&80mG7hitZK`lBmpjSm8iR%ak{c~Hkb{P6*+n3 zJ*lA2q!Bz0!FKZ1X~jmvlQ$9&$Ls!54n28OVU{zRm7PL+b3B?>4FnPCI*e5HU=l?T zo7>8#8Yr|UYB<6s_bpyT6*vk2Bn*0lLZLvHWNh%5LGB^McV>HP1E#gL+1hmN=+kGO z9#1D)%`g5_g9CY|BJhi|%c=VU}}3I-t3>Z`yh_&p-TbH2$}jrf9y&kNu@kI$ZX zI+ZL0KEQRZLo+9JMyF05R`)LKe$&e?pNr#64-17F$M>yL+gdOC++Aq0y2P8u`9Oege+0A=l}feDDs zQ3MizErLvU0k)Tn>MhG3%PV~Y+Jz`6_A}paA z{x(_zua35n-6=47A{R^v#YZIvC9%hj0!Mp?sG;pT!t4oEG$&e=Fj_F?Mkc(}!l{cb zxLGI+T}zr9LM_9Fh@{xSNFOR1S|L^4Ty>;sS0^&%;Hj2Nm@X;$2>?W)E5R3GGB02T z*AY<2kZ6t*m#TDHvzMBwrs~4RDX#}(s}3l9Z5F0aPMLX+fPia&AW#J2wU#Xc(2_A& z=2n;f3kF5Iq6%>U3Cga{EfJY~qP244qK^b4Lq@(u9Y{6Do0MWgM5b4oFxW|$6H8Tr zD>$WBs4X5l6y9yLi;WgFN|uV zhXovbCJ8%A?8&-H0BXo!8PD{A0FWigBA+gip}`rgn=hDc4W`FeWEZLkGZ0jfW@D01 z0YXMA_o>oO$rcWtAdy_?9$T4xDWpO_3KStb@kODLS>=~{zwbM4~60Hz2&uZTIkc>%^P(rEPPTYU=j@{gh+V1>Z7GOlq=2gCRN-s4X{v;7m_^@6wD zbmU)s{h6sx+~XW!Wp}an;u}tW;m^MA1-$(|K%2JQ0H8$%U8tW&H$VtBy<^2 zxY4sQWy68PHJFM*6_HV{2qo!y%Hl{(7D!<>eM?i>C}{;(=V~#AgRxX3&uz?f)a9Et z(?$e=p-7}ta#CSrVrdYsm;`}kj%sw4WJ~Y|tDLSr2@S6@S10{vg zQGrB#VKO1#fJJ4UbxA;M-MgUR-G9u2(&Y07ZQ5p|$`#oM@S>CtwM-9^a?UJw>Dq$l3R~%965dZ^oZw53CUp_ z_Y0wra~6nh04OcsS!6ia9U;`|iMJdshOzwa!iamprT4?SxYglgvT2;w0ji|%W-&*h zxKqDrdb|%k{0WuO{&k$Z32_b6E`KADiA4*4w`82nn2#84D@l77vG^uRCRzdlw960)JYV>%O{P` zp4+|j*>yJM2_~ghsqss?1B{{hS(jW0K9M94ef+QU+Q2yOJ{JrXL+38@=8%`PN zw5LGtJ<*3sg4PO~`dgoQxj;)}^&?M(wT zS_i_!rIXu9S8XMs!ef7?NLxFlo$9nmrVkYm2(c^m=nZkBEM+(|TddSmjV?o06S_?- z20*AvaD1g8O=<0P8krb%$R2ZPg;e0~)nqI*Jm#1+&{?&ic}!B}+f=haF(M!`%2r8a z3w%g{CWlS#O0N_E;}D4@c8MaYLlr`4b2u*?osbY8+G{J+xS}A$q1j0nGN3A-T_8cN z_!4I$U-B1Y*Po^$LVtO-=4@!00|XkmbZJ8;ss#flM3#gMLasgWgn%`D^yi)3@c2~Nwnp5_;1sf%2$ z$gtDq&K|02ZUCXOi!AoLVu&fbC@WApCP$^4=#C{Ox_B6{RxvDVZLi-(jL4kG0+c2! zGv9?k5A_r>g6vhhBu1+Ar{AzaN~3l}TxKQK#~!5VU?l;n7zIpNrNnolw2nX;6s}h3 z>iRzIPV_dxoo6w?=Os=E)l^-G=GvHi;0TONB(f=9;$(}|`oOnv26hlpVw1sZcp1Lx zW-UFK&-cE3@5BG_2X24sE3bOr-Df`exhKz_-JH!*0R1=%M zpY|iT%TGvip1{4bcJ_E`-beo1)3b5!6)(DK<1=Tt^3`8q4cFV6^C7_S+N)2TdhFa} zJmS-Dj+?F$*~2K%3b#+8=jcwWO*#6r9?Y-jjPR2H^lqEi;vP-n8v=-u5jN;uF6M^W#vs6Q ze5Hp5oo~sP_8=dDHrgi+M6jrl2C;-GkgO!}(+ZJhwV{~`VaYo@VqleJUP6TEXmp$; zY_@_f8BceCQWNd^MhRBkr;|4X3d~HF0V*As-t4mQ#Y0Gr%A5eOzJP@-9|53Kfzm`e zKEZC|L)3Nda_D2x9jf`+0lqRhSdON<7v>q?%Uj;?vYT(d`72-e%J|5L?_ln&EJ;N# z%mNDAkeSRu9)5L++S`BKU9WrOk%umv+nUVQw2sD5Vkdb>%{I;?oVj?YDi(U0N25)G z%*3S^z-P##MQ@Peu5vU;a#VY=>5__dFc8W_$a%q)f+Fc)?^YBvSjTH707{f)a;Q!1 zB?%hsa!#<5RZ|PQpDRN(jPPxT7jI6+#n)$aq;F2@gH^;CKFy-@X5t&wu@qr`FciwS=5N z66;IX_nhH*y3hF2Tc(eDhBP?^P2Kt9HGk;+aRwfz-{S*D7XIvc)FhWH1&I*5gQS`J{yNnFGswJ(KeR62sW2 z0CqoU0>EUlx!=3~nxjARmg{HB<)dUE!O4jp{7G6q;LL>`CGWg@=D4;udOqG1%hF3c8hpSK(Bb*|nUm@wH;>&Yz zr&Gfr65KA1_#mE$RJ-+ia068s@Keok&@TgksI0=ZuDcI7Idl= zqmY0$xf(@?p@LmshE+=-rctVti$NzjmC_$MC9k2D90zoq%7_G+)RMo7cS4j#Rg@H{ zOO*;WFGEZkX_`7ihAp+B9h`!ZB5w+jrRFDdubg8iB{*ZP zR4B5Fs1ATqt*gXxptyo?lgwkYSCY6Xgjg}f5iWDnDZ^{s4yB0msFrBDrb8wYSx?l_ zfifweL_H`xT8`zEDT2U|jb)PdgRz@>A!?kX-?AW)hbQM6tnzRqD>q$j@pr zDM64B;(`fQNYsD@(Ckkp)6d`c-2eW*KYiajU;m4L^~SG$*R_9e|JEly`}nET=bqWx z<*xnFa6IJRTrP<1@8v0@J_XL{p2I6^77o8YKA)nd1=T)BLa6Z^!awx+vyVR4yW-gN zYiG8(KBsA;{-aNx`=(c1{m|pj^7#Xo&A?))w99MvScaqU2wxc-jJ%OsZ)ESqzX880 zFe3T##ZE_;#|)}?7&1t+@bpnYD=WL~{24Ny_lIy;IDvr$uX>{s%5pt};PCy0#dtb} ze0O^*iq(@@!qxJ+V=kLF6RK`VxrC&0yr>M*G2ha zXYZ=ZFTefHSO3D#{|gXIhC{xXu2pa%VR#q@~@vjx|c7H+Ffq z6O!u-;B~1-XK1`cnu8ehkV=@POYGCN&9R23TGDJ$N5mDV&=v6??B-Z9%v?r4LK1#N znOig}9qp=aHChxPq`)HhPBK&_=FL2$(QbuEWPBpPo52G}w0N&u|0Or6i!7?zy<`yy zOwki+rl?8E z3|u7OX*m!=DIm_sNfRYh%V5A=K72CB&*=IMU|yq%H;m;IfQfs4Wy>c7;z_w<9)6^( zA$lgAyNmC6)s?3n-+FrUU^+pOr{d%yqn1x2h=GpBNjgtUnY7GT1BM2I25LyJr zq$0c?kNVl^j}|NY@A!)^y6x(<^YaC1lsQk$nMZoF{>uCQzsG<7k00Nda_i5YA@)F$ zi%ssLuTdOUp~WJI!c7ehMM$n5hpJtw zO>5~8Tv*ggzg)!{(lArEjigd)nTidH4H!KcUZMw4sVy)vsIp;*wLw$(Bo><#B`swk z>Lm1;N3p?)&4K`%2~DhRZUJsDsu2UZAk#}@(Xf}jZB@jwf=H_}0Azn9ps0AY>`-Vn z-=d=xY@~Qme9=LI5eT)*a<}bsdO(=#cCi^oje}junyUjN zPL8h7nne+0W@HH|do#mmhTyd;auFaynhXM^D5aDupeQW|G^HR8SsnObb3qZ?0wu;k zlSC|(s;ZLhDSBcch6opJhL(F;;|2?m2B-E^$}qpEpE_?eGqdK?7>z)M=yh@7DR$~G ztBo1Y#kzFSftVBlqxrn(ixh-J7;VWa7m#AHRT}a?Q%$Nc)9%EWYZ7rU#C{ly>RcBh z=t*`#qs$eu6S3(>NxT{RHnP-U?GZagicqK>nMwnK$Z5BbM){LC8OaHE$kfn{upAax z-ok+txy9@poggBg^-bn<(iUN+U%IghWQ$`P>tA?y=O6$2egEVKUVO{((T%SefB$Qa zKe*ZZqx&|$bpN>rp4fWonG5sn#c(;;=f}UW97lUC2@J|%&*}%?&UiktPzrMMh!=s@LoREFgC(# zj)loL{lWUs%tT@G(W@RggxanDc(=zCQlk2-MjHF{t0h+(D--B5&hSFW>YPh@#seUJ zClBzF*{g*bgCkr*i@v&d|kSCABJ`b4X9ALNC<1`>SClMo!Xp$_?T{!oqZ+~NdZS|4+ADWEEUIRu` zvXz|l_MwE9KrQ&BN?-%-&cK;`!+__RqRWKNc~otN;?^WoV=Ux3jbWun zg}igU7XyW@pMszFWVG_Zqs>s3NA#z47&5*QQ=b7A^ze7#;XS#Lvl3-dgAoZexb%Xp zN(Kd;o|-!SR6N1k<|Of8C-l0t0$-|2x(X{{Nr^&+ES$InNQT~?<*J`eK%}_@sw8v9L6J#g zG&;Mv`}Vip_?4;1p;NP?Ka-RnJCBaQOz+%EJmvLQ87KyCTjeT+3E1@$beSN#YS^ z(n!p9^H0~IokUbxJ8ZMaWUB;14OGZgE!}J%T>6Nk>K-F^9B1g%RLZ*c1;XP#0 zl(b18ij9FUUULaBa^_uzGC$mhebo&O1m(IYj~#kmM0C}E1hO;rbc;-=&N;1wA~lGN z>!1!=u?0pUh`2H^LYqZua8qQJy1m~4XkIr~X^xLVSazZSs?Ou;gG@h`V#gCj8Kg;H zM1?LAZR>hE%5+qWH^>-aZj#agr=}1sYfcNcQL%}F zMH%uhMFv+Hq{r!qkZ>#1Y4@;6SO{c1R_-DtU`mI=n`qyhaqJGHI4Wd86Y@9?dgF@(BO(`s2y`OLWjv|kDBnGX#j&!mqIB`Wa*IZNAp$j|t|(izCkH8> zuIE+Ic04bm{BDCPv+W-{y7rr&ed79SkNwDZJpZ}*KF8LZkF31xRqOk=Z#=oZJaziQ z?|td%-}uav!{M0K7t%03;Y52AQP=I7`7SU8fW010R{J~i1%Ak!#!-S(qrn4TKmX>J zpSb7Yt<^El@IZ(M^k#*A#cTZmkKpF^U{{Np$8|pLAzm4Bhsc=wq`9E028^c9qeVHr zY|4E=_=v>-^gLO_6$sDq;G+zF!jtfExqQ#35-pe8&u!jz`ztvdKXA{NJu>R+oG#TA ztdp?Sp_F(r^W@n=sKcWfm%5%8tQw+AYI!I<(^pS4}=`wrZ;Rqd? z5J78*b7@~{d1V$U@7du}gAWzns;sOIs=WAT%z7o&)oKpi^-~K9bBoj}iccsa;f)c8 z4kT-ci#d_ykDa33bJ*uaV0}Swbh38CtuOw(J33u%*PQ3J*TV8a_gI_wuS4b~#ZgH@j6F+ZB`5@7PGb&Y7_nDgHIRFKKNimEJ`u`S!?_0FX99dD9Qmt!Gn`NJ7|t~2YXE$~ zW6aYvdF}wW0P)L%J$`I(G%*C6c!Jbh7h@|m2Zwjy18x6*c==`X-TeoiSq#QL|ID=t zivj#1H8UBLIn;!JIu^OMiT&J<0&T90n;p7I_u&*=JRY6hp5OJ2SHAtulO)Fln&W3Z zL=>0Hqy5!WPtD)+8xL%rpP!iWh2uRRMYPCO3AEPLBF`3_EI2zGqF+cfIXlT=B}qyx zSs-B|<%JM85?ajBC%oDUpi0r=LLy)#6LZqD8Epe%Y-^UNIWREHAQ3B_5jqPV8xbN? zHcze^U|Y%n)rhMj3KXR@b)mB=hq61qv`A$`ETg+=Y6Y7aB09N~7P*oYP~IIwdRQU# zhu>njyWo<%(Z?9+X^IxT8^1|v3sqOMfzp*Fw?#Acm5@rED(p2{-3Fwpp|rr$LyM*s z6=h!WQuK73m%ayd=*C&;n0)j19MAt&O)}Os)oLri=0{jf!ds-t@+!x&$|*Q?o?w9t}2%Fx7HhW@wt? zQ>7`l%WkJ&p+uK$=--Ik~mB!hJG)iq8*fa*VC@o=B31{I(_*`2qS)Y|{wxAkxd&j#-2(o;CYKc}Hi=7xhIM6r5Nyad1V^jm39tTS!e{hI z;N{b8#xsa6Y;JzfU;3`+UwOr;FMi1ZfzKJL2`v+$eq-Akutl`9rQjvSUW$_yFY75U0K zvNvRT42te11YU}3EG)213HGY4jX*UNMyZBg8w6GygBem5maK)n56;!838Ms(sa%6l zl=1R}%NO_Vupr*r+Pvu-Zur@s{;6XpW_xpfMRm$~oELKWNXEOkxM@2fT+Q|VKDTXK zJw85ibTmCOoXiHJBV6U>OmpsXY_T~%n62FP-EW-q2hg0Dte+SiIX;*j?N5#lCP#;} zBZJ9oINlgd=NGnL^P1OO^NrWt{a-(IWV|+JX~+=uQ-%Iy;9Vm;fNHHjo_YrfYd_YN z{07zP+F-JE;o0l2y7uKay@c~3zW3~zreQWA3;>4fLCj3m%o=jH5-);+7#?I~x_KU8 zhSadl*sIa*C@@NGEAJr-X@|Z{xJ{8HN&7@0x54Y`d=Q$jXGkR!POyq1CT-@X1*FPE zlPP44ibp?nWqFxJIW#sxXC#G@>ALtyC$8qY{#7#%>BlJ2k~lLvDJm~dsU6k$DG7!v zye$Vx@8c)!agKRWLos*kNDLV*cx}9X;^+VEC(fO@@UPze))U7E7dVp}ud#$DTHFHW zE&eR$ZJGLDpW&37dsfG@)eL{8A*w9)IT>ImbMp_&e(wL73|1!7(Rj>@*<&!`4zTfv zhx`mC!@+(~T$I^}W|$c4Y7zws_Gc`%j z2K#$lec-&9xG};wD{%sI%yA;Z-f-eTCzS*=dBdpBL_}DfWK1U^6AaQo2Fqq_!U~JQ z&0&RxNHYs7)b`rydf9iv5=|(Ot;|QTlZi^r1hf!oN_J9lnfz0$bTU{_pIscqDLXA9(Zi-TXoWhmL1ft`DJ8_6=wX4!*345yx*;tb5uhqI zMW+`{)l&OZwH$a5rLo`v$UG}Jr6VwCC_+J}B&48Mfh`J9ujmM9+(!j^l28jJQ&s%Z zQC5@9_SOU!nzU4}v|3r7Qmq*pIe|hKxgn7l+`Qn@xw_QFiu8sX!p?)zd-scmX*onJ z{ESR2!T{N1u&&7XRXJh`aB+vA140vIY*5r^h$dZHW1UzvN5D#j%opiMJ&v-Y&Kas3 zGNH)Fu!W40fAxac4&2sp>FjLHUE!51oBV7YjgD1wn%E=APLacEy-^Fr7r1 zu8Wzbs6zlo)5CJth|rc8$~BV}9$O4R%Is=OrgatH>8}@Rl+;CdQx zdk6F0!B($#W^cJR?ESCbeAQ*g#|u8lXTjqU+`~MFz?=yLl&b{}lG$`g9k5Ww#e8ZU zT)E!nL18=|pWR!1;>?Bbd+~;!*bg~8bHE-B`2AQO0mB!NT_N@-yoP?m&!anBDDKO>loQ|h71EdyTb{npfy^E zldCkuacHh}D)I5?WX`b}>lD~gV0x^1)q&=18T>XEhDoOJTrYR|gU4MBaoo>2K)#y^ z1zJX<*+)L|k^Aob>c9SlU%32=jqMBP*M>8^$?Bg~_?Vk~Sd07p&C%4evEh`jdMx|= zkle;-w$`7ppkL>lfqTfdxYH-w!O9EX^snoGBgW+5M;&*R9_Uz;LK75RyTH;K@gz~IovN}YaCx1Qk^6VI%zUcMe^_B7F`7>|2 z;|)Lf{Xg*3Q%|t^&k7LV;a>g9Xlbsb%3u7z-kiutbZKdlaq7bzPXtsi;l@i2)oHl6 z)73d=%Zf^R-R3a2N4<*3)dVh{+sT8t{vP_Afv&@xR(WRBB}cpxE+=AKAQ3ez=d$!8cqVi56*A`Apv9+{jK zB)#n-UjRua!sR3|+*_Ic-23mo?fDx&^UiO*;>y``i`6A(f575&71_)!lNoD$7X6&B z^&I$|!%sPxXU)B8J1v~kuS|PAKLqeDpq`V4Bu*oo28_%AE?5jW1ze5?N2i0oaO?W4 z-}}_*9oF%)DKn0zu1q7W@^NDjdJd3jjT1T=GIacoulE!t>xX<)vBJ#7@B4CzVlw5E zN}&Ddcf9aLmyI`B>Em`j!|3&<{oc{x%0_?Xxr3GWe&Y1!?srWd+)90N)YdvMS9`?R z63abQqCzj)ZFAO9x&X;J=tT_Do48P3+#yE*YSClrE*LI;g1Xj?2uK5BN*N6?Le2su zl5UY%GHT&euwn>=Q6-Otm#9+Hi4h1Lc*GJ!N_Ej&64IV(m>Fu?CTgjR(TeLf~Gv}rD96|t1^$#BS&LrPaa?65&a z%82F4toW8E>}X5nV2Pcf*Rgg=ap_ify9Q`F5$FcVp_XDzD=}?I84&nALko>A;6U>52^PW?zHL@ghg3 zXd#f*9Hu}DADn?eZv&zC;fIZY zB1R?ZQMPWcl8UNTyIN5>auyE)JPvvws`=+fL9w@n7;S%;vP#4;11mYJMZj}7W% zuF{zb2L*4rZ@9)yc{bW|0@HzMNk}$w3g8c9G1K-b0E;OB`m7ph*C!lBZD%5j8-%1I zlgX;)QmrdXDCHFb=oCdQ>v4K$(ow>z_~JBw=6=D2`G=pF|Lp(!wg2OXZsbt9!&yt8 zU$j}_p&0YM<()5_{`GHv-aq|~(;H*1csi9b@1qe}92<37R+#zRSSvZC>Ix1S-oop; z5U&CcXK_Es-ePpm>8&4o?R`^;U}?ES^Xh7~#|3Ag|-EI*?5b$4*+ zo^KZZ8AsXk#Pf55@pi*kdl>5dWCEcsW6~U2)T1WSuv>ryUkMq{tQobrlfZT%;yT_= zwB{8uJvM#xpLk-ANyra;@3%0O`Qaaan;VCDB`LS#UF^)(E~=wcG`V8q5GsTQqc$#) zu%1g@3(c$a7~A<9Tav7z$B~Ocz41aRx`JIjLr+N!HY1=6H4obk1p>e~r`EL8u_@|{ zp6wY>y56F>;*8Xack6x%513YW?qMDNC8T3`#%;(oV)ai=2`lo_qh|#jd!Q)6iPE#69m~{m?OCDjO)TJTBnn;=g^~sUeP3NKw<4+og7C`V7s>~KL zP*@d^{0mK+n>0nGD@e-%)xfWn67W$V9ZK|t4V2!fnSlZe2GtewygYC~;(hYNVQ{O` z<%W*eu938!rTh858!@LbrxP&rcg~0(lJ8u=9;DgYntu9Adq4j>pZeZ6KI=#R$CrQG zPk!L?;SyKq^7O2#Wdg9%jP?!v@hp&VcYsHF1|z&9GqwJj+l4%4)UZa-<(->6CNvsJ zx0vNA4_^8NCU-z~I{S9`RwrUyIyna*WR|Xzgmy!_kdWfE?i!I{KDen_wU{P z`3s-A@BD@HmnZB}n$ywhSD;k6V1+&$#2TJqmuq|Y*g_lI;B_{tP@ZVp%B@T0M+3!` zZ27v2-GS~>hQOuha7atZnOo`yfN%$A(SDNUgxGj^>c;!27iS2va14m671+nFz;AmmSwR9hN{Te*kbEwZD^5YZxI%#)%POewg=NZ9 ztB?n!Rjh?HK~Pq+O(C3_0ccU%J%}QQs#eNswq^kgnU(_A(NpU-@FjkwD02RiBY3FW zJ%C)gVJO%raj;Q5GgBoLtX+o;@!v9mB9yee2*K29Vbwy3kSY|}Nswb(tE03cR*%rF zRFWbi)7OB8+bF;ctMNxYg~Sc?buS}ikW$0^2quFe841;Z+7Y*nfa_p0<4K7PQ{(|H z>69y`S=c)6SBylnWJ1m^rXU1`RLGGiq6#Jo`K>e;w82uA;(!7x*!Cn0B$WASlT|we ztda!902E+W!L5NI&8m6@5PU&LyYdK81D0S?Wu`q8&#NMi_K-{X;v*G<8OXqc1R<`- zSPdi4#e?#KPCHSh1d9e!e*+WxQB+cJWSc5htWAyd@rk6+lZ#oA%~q-{JwP7<8(E5S z*bLUB!Z>6hFW<#Hm!_6<`~+c)Tnj7MvAIK8K=LJPMlq*cwi(}K&*ob@UIr4>k--B7 zO-Tp#aV{iZC-M{nFGrSRx68;kr&speRF8}6pkG=m>7vA^h7f)>s z*RumB06yj$UO<=V61<@R?hGuB`OKINWy~gk2!�ZL?$VX4boWje#@ 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

3&`FP0wAM^ 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ð%nGcvAh_OOyLTDa>*jv+g( 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 D0bDd^ literal 0 HcmV?d00001 diff --git a/Assets/ProjectQuint/Art/AppIcon/ProjectQuintAppIcon.png.meta b/Assets/ProjectQuint/Art/AppIcon/ProjectQuintAppIcon.png.meta new file mode 100644 index 0000000..f881269 --- /dev/null +++ b/Assets/ProjectQuint/Art/AppIcon/ProjectQuintAppIcon.png.meta @@ -0,0 +1,143 @@ +fileFormatVersion: 2 +guid: aa998a7af36b743fdb4ab07c8a19741a +TextureImporter: + internalIDToNameTable: + - first: + 213: -4050758962521307463 + second: ProjectQuintAppIcon_0 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 2 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: ProjectQuintAppIcon_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1024 + height: 1024 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 9b600387f20d8c7c0800000000000000 + internalID: -4050758962521307463 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: + ProjectQuintAppIcon_0: -4050758962521307463 + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Editor.meta b/Assets/ProjectQuint/Editor.meta new file mode 100644 index 0000000..1642376 --- /dev/null +++ b/Assets/ProjectQuint/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 39aa599a9465344fd9bbf8714fe9e66d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Editor/ChartFeatureCalibrationReport.cs b/Assets/ProjectQuint/Editor/ChartFeatureCalibrationReport.cs new file mode 100644 index 0000000..c1d0ac0 --- /dev/null +++ b/Assets/ProjectQuint/Editor/ChartFeatureCalibrationReport.cs @@ -0,0 +1,90 @@ +#if UNITY_EDITOR +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using ProjectQuint.Library; +using UnityEngine; + +namespace ProjectQuint.Editor +{ + public static class ChartFeatureCalibrationReport + { + private const string PackPathVariable = "PROJECTQUINT_FEATURE_PACK_PATH"; + private const string ReportPathVariable = "PROJECTQUINT_FEATURE_REPORT_PATH"; + private const string CachePathVariable = "PROJECTQUINT_FEATURE_CACHE_PATH"; + + public static void Generate() + { + var packPath = Environment.GetEnvironmentVariable(PackPathVariable); + var reportPath = Environment.GetEnvironmentVariable(ReportPathVariable); + var cachePath = Environment.GetEnvironmentVariable(CachePathVariable); + if (string.IsNullOrWhiteSpace(packPath) || !Directory.Exists(packPath)) + throw new DirectoryNotFoundException($"Set {PackPathVariable} to a BMS pack folder."); + if (string.IsNullOrWhiteSpace(reportPath)) + throw new InvalidOperationException($"Set {ReportPathVariable} to the output TSV path."); + if (string.IsNullOrWhiteSpace(cachePath)) + throw new InvalidOperationException($"Set {CachePathVariable} to a disposable index cache path."); + + var stopwatch = Stopwatch.StartNew(); + var scanner = new SongLibraryScanner(cachePath, packId: "feature-calibration"); + var songs = scanner.Scan(Path.GetFullPath(packPath), CancellationToken.None); + stopwatch.Stop(); + + var builder = new StringBuilder(); + builder.AppendLine( + "chart_path\tmd5\tkey_count\thas_scratch\tdifficulty\tnotes_raw\tpeak_raw\tchord_raw\tlong_raw\tsoflan_raw\ttech_raw\tnotes\tpeak\tchord\tlong\tsoflan\ttech\tanomalies\terror"); + foreach (var song in songs) + { + var feature = song.Features; + builder.Append(Escape(song.ChartPath)).Append('\t') + .Append(Escape(song.Metadata?.Md5)).Append('\t') + .Append(song.Metadata?.KeyCount ?? 0).Append('\t') + .Append(song.Metadata?.HasScratch == true ? 1 : 0).Append('\t') + .Append(Escape(song.DifficultyLabel)).Append('\t'); + if (feature != null) + { + Append(builder, feature.NotesRaw).Append('\t'); + Append(builder, feature.PeakRaw).Append('\t'); + Append(builder, feature.ChordRaw).Append('\t'); + Append(builder, feature.LongRaw).Append('\t'); + Append(builder, feature.SofLanRaw).Append('\t'); + Append(builder, feature.TechRaw).Append('\t'); + Append(builder, feature.Notes).Append('\t'); + Append(builder, feature.Peak).Append('\t'); + Append(builder, feature.Chord).Append('\t'); + Append(builder, feature.Long).Append('\t'); + Append(builder, feature.SofLan).Append('\t'); + Append(builder, feature.Tech).Append('\t'); + builder.Append(feature.AnomalyCount).Append('\t'); + } + else + { + builder.Append("\t\t\t\t\t\t\t\t\t\t\t\t\t"); + } + builder.AppendLine(Escape(song.FeatureAnalysisError)); + } + + var directory = Path.GetDirectoryName(Path.GetFullPath(reportPath)); + if (!string.IsNullOrWhiteSpace(directory)) + Directory.CreateDirectory(directory); + File.WriteAllText(reportPath, builder.ToString(), new UTF8Encoding(false)); + + var available = songs.Count(song => song.Features != null); + UnityEngine.Debug.Log( + $"[ProjectQuint Features] {available:N0}/{songs.Count:N0} chart profiles written to {reportPath} " + + $"in {stopwatch.Elapsed.TotalSeconds:0.00}s; cache {scanner.CacheHits:N0} hits / " + + $"{scanner.CacheMisses:N0} refreshes; failures {scanner.Failed:N0}."); + } + + private static StringBuilder Append(StringBuilder builder, double value) => + builder.Append(value.ToString("0.######", CultureInfo.InvariantCulture)); + + private static string Escape(string value) => + (value ?? string.Empty).Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); + } +} +#endif diff --git a/Assets/ProjectQuint/Editor/ChartFeatureCalibrationReport.cs.meta b/Assets/ProjectQuint/Editor/ChartFeatureCalibrationReport.cs.meta new file mode 100644 index 0000000..c0e7d41 --- /dev/null +++ b/Assets/ProjectQuint/Editor/ChartFeatureCalibrationReport.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 608820f0ced354ca8984048c7444040b \ No newline at end of file diff --git a/Assets/ProjectQuint/Editor/ProjectQuintBuild.cs b/Assets/ProjectQuint/Editor/ProjectQuintBuild.cs new file mode 100644 index 0000000..815423d --- /dev/null +++ b/Assets/ProjectQuint/Editor/ProjectQuintBuild.cs @@ -0,0 +1,758 @@ +#if UNITY_EDITOR +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Xml; +using ProjectQuint.Core.Release; +using UnityEditor; +using UnityEditor.Callbacks; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace ProjectQuint.Editor +{ + public static class ProjectQuintBuild + { + private const string BuildPathEnvironmentVariable = "PROJECTQUINT_BUILD_PATH"; + private const string CompanyName = "Team Denia"; + private const string ProductName = "ProjectQuint"; + private const string BundleIdentifier = "com.teamdenia.projectquint"; + private const string Copyright = "© 2026 Team Denia"; + private const string CopyrightXml = "© 2026 Team Denia"; + + public static void BuildMacStable() + { + PlayerSettings.resizableWindow = true; + ApplyReleaseVersion(); + RequireMetalDisplayLink(); + RequireDisabledUnityTelemetry(); + RequireProductIdentity(); + RequireMacNativeInputPlugin(); + RequireMacSparkleUpdater(); + ProjectQuintIconConfigurator.ApplyMacAppIcon(); + + var outputPath = Environment.GetEnvironmentVariable(BuildPathEnvironmentVariable); + if (string.IsNullOrWhiteSpace(outputPath)) + { + throw new InvalidOperationException( + $"Set {BuildPathEnvironmentVariable} to the exact .app output path before building."); + } + + outputPath = Path.GetFullPath(outputPath); + if (!outputPath.EndsWith(".app", StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException($"macOS build output must end in .app: {outputPath}"); + + var directory = Path.GetDirectoryName(outputPath); + if (string.IsNullOrWhiteSpace(directory)) + throw new InvalidOperationException($"Build output has no parent directory: {outputPath}"); + Directory.CreateDirectory(directory); + + var scenes = EditorBuildSettings.scenes + .Where(scene => scene.enabled) + .Select(scene => scene.path) + .ToArray(); + if (scenes.Length == 0) + throw new InvalidOperationException("No enabled scenes are configured in Build Settings."); + + var report = BuildPipeline.BuildPlayer(new BuildPlayerOptions + { + scenes = scenes, + locationPathName = outputPath, + target = BuildTarget.StandaloneOSX, + options = BuildOptions.None, + }); + + var summary = report.summary; + Debug.Log( + $"[ProjectQuint Build] {summary.result}: {summary.totalErrors} errors, " + + $"{summary.totalWarnings} warnings, {summary.totalSize} bytes, {summary.totalTime}."); + if (summary.result != BuildResult.Succeeded || summary.totalErrors > 0) + throw new InvalidOperationException( + $"ProjectQuint macOS build failed: {summary.result}, {summary.totalErrors} errors."); + + ValidateBuiltMacNativeInputPlugin(outputPath); + ValidateBuiltMacSparkleUpdater(outputPath); + WriteAndValidateMacBundleIdentity(outputPath); + WriteAndValidateSparkleConfiguration(outputPath); + ApplyAndValidateMacPrivacyPolicy(outputPath); + ValidateReleaseDependencySurface(outputPath); + AdHocSignAndVerify(outputPath); + } + + private static void ApplyReleaseVersion() + { + PlayerSettings.bundleVersion = ProjectQuintReleaseVersion.ProductVersion; + PlayerSettings.macOS.buildNumber = ProjectQuintReleaseVersion.BuildNumberText; + Debug.Log( + $"[ProjectQuint Build] Release version: " + + $"{ProjectQuintReleaseVersion.DisplayVersionWithBuild}"); + } + + private static void RequireMacNativeInputPlugin() + { + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var sourceRoot = Path.Combine(projectRoot, "Native", "ProjectQuintInput"); + var bundleRoot = Path.Combine( + Application.dataPath, + "ProjectQuint", + "Plugins", + "macOS", + "ProjectQuintInput.bundle"); + var binary = Path.Combine(bundleRoot, "Contents", "MacOS", "ProjectQuintInput"); + RequireFile(Path.Combine(sourceRoot, "project_quint_input.cpp"), "macOS native input source"); + RequireFile(Path.Combine(sourceRoot, "build-macos.sh"), "macOS native input build script"); + RequireFile(Path.Combine(bundleRoot, "Contents", "Info.plist"), "macOS native input Info.plist"); + RequireFile(binary, "macOS native input Universal binary"); + RunTool( + "/usr/bin/lipo", + new[] { binary, "-verify_arch", "arm64", "x86_64" }, + "macOS native input architecture verification"); + } + + private static void ValidateBuiltMacNativeInputPlugin(string appPath) + { + var bundles = Directory.GetDirectories( + appPath, + "ProjectQuintInput.bundle", + SearchOption.AllDirectories); + if (bundles.Length != 1) + throw new InvalidDataException( + $"Expected exactly one ProjectQuintInput.bundle in the Player, found {bundles.Length}."); + var binary = Path.Combine(bundles[0], "Contents", "MacOS", "ProjectQuintInput"); + RequireFile(binary, "built macOS native input binary"); + RunTool( + "/usr/bin/lipo", + new[] { binary, "-verify_arch", "arm64", "x86_64" }, + "built macOS native input architecture verification"); + Debug.Log($"[ProjectQuint Build] Included Universal native input polling backend: {bundles[0]}"); + } + + private static void RequireMacSparkleUpdater() + { + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var sourceRoot = Path.Combine(projectRoot, "Native", "ProjectQuintUpdater"); + var bundleRoot = Path.Combine( + Application.dataPath, + "ProjectQuint", + "Plugins", + "macOS", + "ProjectQuintUpdater.bundle"); + var binary = Path.Combine(bundleRoot, "Contents", "MacOS", "ProjectQuintUpdater"); + var sparkleRoot = Path.Combine(projectRoot, "ThirdParty", "Sparkle", "2.9.4"); + var sparkleBinary = Path.Combine( + sparkleRoot, + "Sparkle.framework", + "Versions", + "B", + "Sparkle"); + + RequireFile(Path.Combine(sourceRoot, "project_quint_updater.mm"), "Sparkle updater source"); + RequireFile(Path.Combine(sourceRoot, "build-macos.sh"), "Sparkle updater build script"); + RequireFile(Path.Combine(bundleRoot, "Contents", "Info.plist"), "Sparkle updater Info.plist"); + RequireFile(binary, "Sparkle updater Universal binary"); + RequireFile(sparkleBinary, "Sparkle 2.9.4 framework binary"); + RequireFile(Path.Combine(sparkleRoot, "LICENSE"), "Sparkle license"); + RunTool( + "/usr/bin/lipo", + new[] { binary, "-verify_arch", "arm64", "x86_64" }, + "Sparkle updater architecture verification"); + RunTool( + "/usr/bin/lipo", + new[] { sparkleBinary, "-verify_arch", "arm64", "x86_64" }, + "Sparkle framework architecture verification"); + var linkedLibraries = RunToolCapture( + "/usr/bin/otool", + new[] { "-L", binary }, + "Sparkle updater linkage verification"); + if (!linkedLibraries.Contains("@rpath/Sparkle.framework/Versions/B/Sparkle")) + throw new InvalidDataException("ProjectQuintUpdater is not linked to Sparkle.framework."); + var loadCommands = RunToolCapture( + "/usr/bin/otool", + new[] { "-l", binary }, + "Sparkle updater rpath verification"); + if (!loadCommands.Contains("@loader_path/../../../../Frameworks")) + throw new InvalidDataException("ProjectQuintUpdater has no app Frameworks rpath."); + } + + private static void ValidateBuiltMacSparkleUpdater(string appPath) + { + var bundles = Directory.GetDirectories( + appPath, + "ProjectQuintUpdater.bundle", + SearchOption.AllDirectories); + if (bundles.Length != 1) + throw new InvalidDataException( + $"Expected exactly one ProjectQuintUpdater.bundle in the Player, found {bundles.Length}."); + var binary = Path.Combine(bundles[0], "Contents", "MacOS", "ProjectQuintUpdater"); + RequireFile(binary, "built Sparkle updater binary"); + RunTool( + "/usr/bin/lipo", + new[] { binary, "-verify_arch", "arm64", "x86_64" }, + "built Sparkle updater architecture verification"); + Debug.Log($"[ProjectQuint Build] Included Universal Sparkle bridge: {bundles[0]}"); + } + + private static void RequireProductIdentity() + { + var identifier = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Standalone); + if (!string.Equals(PlayerSettings.companyName, CompanyName, StringComparison.Ordinal) || + !string.Equals(PlayerSettings.productName, ProductName, StringComparison.Ordinal) || + !string.Equals( + PlayerSettings.bundleVersion, + ProjectQuintReleaseVersion.ProductVersion, + StringComparison.Ordinal) || + !string.Equals( + PlayerSettings.macOS.buildNumber, + ProjectQuintReleaseVersion.BuildNumberText, + StringComparison.Ordinal) || + !string.Equals(identifier, BundleIdentifier, StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "ProjectQuint product identity is incomplete. Expected " + + $"{CompanyName}/{ProductName}/{BundleIdentifier}/" + + $"{ProjectQuintReleaseVersion.DisplayVersionWithBuild}, got " + + $"{PlayerSettings.companyName}/{PlayerSettings.productName}/{identifier}/" + + $"{PlayerSettings.bundleVersion} ({PlayerSettings.macOS.buildNumber})."); + } + } + + private static void WriteAndValidateMacBundleIdentity(string appPath) + { + var infoPlist = Path.Combine(appPath, "Contents", "Info.plist"); + RequireFile(infoPlist, "macOS application Info.plist"); + RunTool( + "/usr/bin/plutil", + new[] { "-replace", "CFBundleDisplayName", "-string", ProductName, infoPlist }, + "macOS display-name metadata"); + RunTool( + "/usr/bin/plutil", + new[] { "-replace", "NSHumanReadableCopyright", "-xml", CopyrightXml, infoPlist }, + "macOS copyright metadata"); + + RequirePlistValue(infoPlist, "CFBundleIdentifier", BundleIdentifier); + RequirePlistValue(infoPlist, "CFBundleName", ProductName); + RequirePlistValue(infoPlist, "CFBundleDisplayName", ProductName); + RequirePlistValue( + infoPlist, + "CFBundleShortVersionString", + ProjectQuintReleaseVersion.ProductVersion); + RequirePlistValue( + infoPlist, + "CFBundleVersion", + ProjectQuintReleaseVersion.BuildNumberText); + RequireXmlPlistStringValue(infoPlist, "NSHumanReadableCopyright", Copyright); + Debug.Log( + $"[ProjectQuint Build] Product identity verified: {BundleIdentifier} " + + $"{ProjectQuintReleaseVersion.DisplayVersionWithBuild}, {Copyright}"); + } + + private static void WriteAndValidateSparkleConfiguration(string appPath) + { + var infoPlist = Path.Combine(appPath, "Contents", "Info.plist"); + RequireFile(infoPlist, "macOS application Info.plist"); + SetPlistValue( + infoPlist, + "SUFeedURL", + "string", + ProjectQuintReleaseVersion.SparkleFeedUrl); + SetPlistValue( + infoPlist, + "SUPublicEDKey", + "string", + ProjectQuintReleaseVersion.SparklePublicEdKey); + SetPlistValue(infoPlist, "SUEnableAutomaticChecks", "bool", "false"); + SetPlistValue(infoPlist, "SUAutomaticallyUpdate", "bool", "false"); + SetPlistValue(infoPlist, "SUAllowsAutomaticUpdates", "bool", "false"); + SetPlistValue(infoPlist, "SUVerifyUpdateBeforeExtraction", "bool", "true"); + SetPlistValue(infoPlist, "SUShowReleaseNotes", "bool", "true"); + + RequirePlistValue( + infoPlist, + "SUFeedURL", + ProjectQuintReleaseVersion.SparkleFeedUrl); + RequirePlistValue( + infoPlist, + "SUPublicEDKey", + ProjectQuintReleaseVersion.SparklePublicEdKey); + RequirePlistValue(infoPlist, "SUEnableAutomaticChecks", "false"); + RequirePlistValue(infoPlist, "SUAutomaticallyUpdate", "false"); + RequirePlistValue(infoPlist, "SUAllowsAutomaticUpdates", "false"); + RequirePlistValue(infoPlist, "SUVerifyUpdateBeforeExtraction", "true"); + RequirePlistValue(infoPlist, "SUShowReleaseNotes", "true"); + Debug.Log( + $"[ProjectQuint Build] Sparkle feed and Ed25519 verification configured: " + + ProjectQuintReleaseVersion.SparkleFeedUrl); + } + + private static void SetPlistValue( + string plistPath, + string key, + string valueType, + string value) + { + var document = new XmlDocument(); + document.Load(plistPath); + var operation = FindXmlPlistValueNode(document, key) == null ? "-insert" : "-replace"; + RunTool( + "/usr/bin/plutil", + new[] { operation, key, "-" + valueType, value, plistPath }, + $"macOS {key} configuration"); + } + + private static void RequirePlistValue(string plistPath, string key, string expected) + { + var actual = RunToolCapture( + "/usr/bin/plutil", + new[] { "-extract", key, "raw", "-o", "-", plistPath }, + $"macOS {key} verification").Trim(); + if (!string.Equals(actual, expected, StringComparison.Ordinal)) + throw new InvalidDataException( + $"macOS {key} mismatch. Expected '{expected}', got '{actual}'."); + } + + private static void RequireXmlPlistStringValue(string plistPath, string key, string expected) + { + var document = new XmlDocument(); + document.Load(plistPath); + var keys = document.SelectNodes("/plist/dict/key"); + if (keys != null) + { + foreach (XmlNode keyNode in keys) + { + if (!string.Equals(keyNode.InnerText, key, StringComparison.Ordinal)) + continue; + var valueNode = keyNode.NextSibling; + while (valueNode != null && valueNode.NodeType != XmlNodeType.Element) + valueNode = valueNode.NextSibling; + var actual = valueNode?.InnerText ?? string.Empty; + if (string.Equals(actual, expected, StringComparison.Ordinal)) + return; + throw new InvalidDataException( + $"macOS {key} mismatch. Expected '{expected}', got '{actual}'."); + } + } + throw new InvalidDataException($"macOS Info.plist is missing {key}."); + } + + private static void AdHocSignAndVerify(string appPath) + { + RunTool( + "/usr/bin/codesign", + new[] { "--force", "--deep", "--sign", "-", appPath }, + "ad-hoc code signing"); + RunTool( + "/usr/bin/codesign", + new[] { "--verify", "--deep", "--strict", appPath }, + "code signature verification"); + RunTool( + "/usr/bin/codesign", + new[] + { + "--verify", + "--deep", + "--strict", + Path.Combine(appPath, "Contents", "Frameworks", "Sparkle.framework"), + }, + "Sparkle framework signature verification"); + var updaterBundles = Directory.GetDirectories( + appPath, + "ProjectQuintUpdater.bundle", + SearchOption.AllDirectories); + if (updaterBundles.Length != 1) + throw new InvalidDataException( + $"Expected one signed ProjectQuintUpdater.bundle, found {updaterBundles.Length}."); + RunTool( + "/usr/bin/codesign", + new[] { "--verify", "--strict", updaterBundles[0] }, + "Sparkle bridge signature verification"); + Debug.Log($"[ProjectQuint Build] Ad-hoc signature verified: {appPath}"); + } + + private static void RunTool(string executable, string[] arguments, string operation) + { + var startInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = executable, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + Arguments = string.Join(" ", arguments.Select(QuoteProcessArgument)), + }; + + using var process = System.Diagnostics.Process.Start(startInfo) ?? + throw new InvalidOperationException($"Could not start {operation}."); + var standardOutput = process.StandardOutput.ReadToEnd(); + var standardError = process.StandardError.ReadToEnd(); + process.WaitForExit(); + if (process.ExitCode != 0) + { + throw new InvalidOperationException( + $"ProjectQuint {operation} failed ({process.ExitCode}): " + + $"{standardError}\n{standardOutput}"); + } + } + + private static string QuoteProcessArgument(string argument) => + "\"" + (argument ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; + + private static void RequireMetalDisplayLink() + { + var projectSettingsPath = Path.GetFullPath( + Path.Combine(Application.dataPath, "..", "ProjectSettings", "ProjectSettings.asset")); + var serializedSettings = File.ReadAllText(projectSettingsPath); + if (!serializedSettings.Contains("metalUseMetalDisplayLink: 1")) + { + throw new InvalidOperationException( + "Use MetalDisplayLink (Mac Player only) must be enabled for stable macOS frame pacing."); + } + } + + private static void RequireDisabledUnityTelemetry() + { + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var projectSettings = File.ReadAllText( + Path.Combine(projectRoot, "ProjectSettings", "ProjectSettings.asset")); + var connectSettings = File.ReadAllText( + Path.Combine(projectRoot, "ProjectSettings", "UnityConnectSettings.asset")); + if (!projectSettings.Contains("submitAnalytics: 0") || + !connectSettings.Contains("UnityConnectSettings:\n m_ObjectHideFlags: 0\n" + + " serializedVersion: 1\n m_Enabled: 0") || + !connectSettings.Contains("m_EngineDiagnosticsEnabled: 0") || + !connectSettings.Contains("m_EnableCloudDiagnosticsReporting: 0") || + !connectSettings.Contains("UnityAnalyticsSettings:\n m_Enabled: 0") || + !connectSettings.Contains("PerformanceReportingSettings:\n m_Enabled: 0")) + { + throw new InvalidOperationException( + "Unity telemetry and diagnostics must remain disabled for a ProjectQuint release."); + } + } + + private static void ApplyAndValidateMacPrivacyPolicy(string appPath) + { + var infoPlist = Path.Combine(appPath, "Contents", "Info.plist"); + RunTool( + "/usr/bin/plutil", + new[] { "-remove", "NSAppTransportSecurity", infoPlist }, + "macOS App Transport Security hardening"); + RequireXmlPlistMissingKey(infoPlist, "NSAppTransportSecurity"); + + var privacyManifest = Path.Combine( + appPath, + "Contents", + "Resources", + "PrivacyInfo.xcprivacy"); + RequireFile(privacyManifest, "macOS privacy manifest"); + var document = new XmlDocument(); + document.Load(privacyManifest); + var collectedData = FindXmlPlistValueNode(document, "NSPrivacyCollectedDataTypes"); + if (collectedData != null && + (collectedData.Name != "array" || + collectedData.ChildNodes.Cast().Any( + node => node.NodeType == XmlNodeType.Element))) + { + throw new InvalidDataException( + "ProjectQuint declares no collected data, but the built privacy manifest is not empty."); + } + + Debug.Log( + "[ProjectQuint Build] Privacy policy verified: no collected data and macOS ATS enforced."); + } + + private static void ValidateReleaseDependencySurface(string appPath) + { + var managedDirectory = Path.Combine( + appPath, + "Contents", + "Resources", + "Data", + "Managed"); + if (!Directory.Exists(managedDirectory)) + throw new DirectoryNotFoundException( + $"Built macOS Managed directory not found: {managedDirectory}"); + + var forbiddenAssemblyNames = new[] + { + "Unity.AI.MCP.Runtime.dll", + "Unity.AI.Tracing.dll", + "Unity.Multiplayer.Center.Common.dll", + "Unity.VisualScripting.Antlr3.Runtime.dll", + "Unity.VisualScripting.Core.dll", + "Unity.VisualScripting.DocCodeExamples.dll", + "Unity.VisualScripting.Flow.dll", + "Unity.VisualScripting.State.dll", + "Unity.2D.Animation.Runtime.dll", + "Unity.2D.Common.Runtime.dll", + "Unity.2D.IK.Runtime.dll", + "Unity.2D.SpriteShape.Runtime.dll", + "Unity.2D.Tilemap.Extras.dll", + "FMODUnityHaptics.dll", + "FMODUnityResonance.dll", + }; + var includedForbiddenAssemblies = forbiddenAssemblyNames + .Where(name => File.Exists(Path.Combine(managedDirectory, name))) + .ToArray(); + + var pluginDirectory = Path.Combine(appPath, "Contents", "PlugIns"); + var forbiddenPluginNames = new[] + { + "fmod_haptics.bundle", + "resonanceaudio.bundle", + }; + var includedForbiddenPlugins = Directory.Exists(pluginDirectory) + ? forbiddenPluginNames + .Where(name => Directory.Exists(Path.Combine(pluginDirectory, name))) + .ToArray() + : Array.Empty(); + + var debugArtifacts = Directory + .EnumerateFiles(appPath, "*", SearchOption.AllDirectories) + .Where(path => + path.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(".mdb", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(".debug", StringComparison.OrdinalIgnoreCase)) + .Select(Path.GetFileName) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(name => name, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + var unexpected = includedForbiddenAssemblies + .Concat(includedForbiddenPlugins) + .Concat(debugArtifacts) + .ToArray(); + if (unexpected.Length > 0) + { + throw new InvalidDataException( + "Release build contains development-only or unused runtime files: " + + string.Join(", ", unexpected) + + ". Build through Tools/build-mac-stable-release.sh."); + } + + RequireFile( + Path.Combine(managedDirectory, "FMODUnity.dll"), + "FMOD runtime assembly"); + RequireFile( + Path.Combine(pluginDirectory, "fmodstudio.bundle", "Contents", "MacOS", "fmodstudio"), + "FMOD Studio macOS runtime"); + RequireFile( + Path.Combine( + appPath, + "Contents", + "Frameworks", + "Sparkle.framework", + "Versions", + "B", + "Sparkle"), + "Sparkle updater framework"); + Debug.Log( + "[ProjectQuint Build] Release dependency surface verified: " + + "no AI/MCP, Visual Scripting, unused 2D, multiplayer, " + + "FMOD haptics/resonance or debug artifacts."); + } + + private static void RequireXmlPlistMissingKey(string plistPath, string key) + { + var document = new XmlDocument(); + document.Load(plistPath); + if (FindXmlPlistValueNode(document, key) != null) + throw new InvalidDataException($"macOS Info.plist must not contain {key}."); + } + + private static XmlNode FindXmlPlistValueNode(XmlDocument document, string key) + { + var keys = document.SelectNodes("/plist/dict/key"); + if (keys == null) + return null; + foreach (XmlNode keyNode in keys) + { + if (!string.Equals(keyNode.InnerText, key, StringComparison.Ordinal)) + continue; + var valueNode = keyNode.NextSibling; + while (valueNode != null && valueNode.NodeType != XmlNodeType.Element) + valueNode = valueNode.NextSibling; + return valueNode; + } + return null; + } + + [PostProcessBuild(100)] + public static void IncludeLegacyEncodingProvider(BuildTarget target, string outputPath) + { + if (target != BuildTarget.StandaloneOSX) + return; + + var sourcePath = Path.Combine( + EditorApplication.applicationContentsPath, + "Resources", + "Scripting", + "NetStandard", + "EditorExtensions", + "System.Text.Encoding.CodePages.dll"); + if (!File.Exists(sourcePath)) + throw new FileNotFoundException( + "Unity's Shift-JIS encoding provider was not found; refusing to create a broken macOS build.", + sourcePath); + + var managedDirectory = Path.Combine(outputPath, "Contents", "Resources", "Data", "Managed"); + if (!Directory.Exists(managedDirectory)) + throw new DirectoryNotFoundException($"macOS Managed directory not found: {managedDirectory}"); + + var destinationPath = Path.Combine(managedDirectory, "System.Text.Encoding.CodePages.dll"); + File.Copy(sourcePath, destinationPath, true); + Debug.Log($"[ProjectQuint Build] Included Shift-JIS encoding provider: {destinationPath}"); + } + + [PostProcessBuild(110)] + public static void IncludeBundledBgaConverter(BuildTarget target, string outputPath) + { + if (target != BuildTarget.StandaloneOSX) + return; + + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var thirdPartyRoot = Path.Combine(projectRoot, "ThirdParty", "FFmpeg"); + var sourceBinary = Path.Combine(thirdPartyRoot, "macOS", "ffmpeg"); + var sourceArchive = Path.Combine(thirdPartyRoot, "Source", "ffmpeg-8.1.2.tar.xz"); + var expectedSourceHash = + "464beb5e7bf0c311e68b45ae2f04e9cc2af88851abb4082231742a74d97b524c"; + RequireFile(sourceBinary, "Bundled macOS FFmpeg converter"); + RequireFile(sourceArchive, "Corresponding FFmpeg source archive"); + RequireFile(Path.Combine(thirdPartyRoot, "COPYING.LGPLv2.1"), "FFmpeg LGPL license"); + RequireFile(Path.Combine(thirdPartyRoot, "NOTICE.txt"), "FFmpeg attribution notice"); + RequireFile(Path.Combine(projectRoot, "Tools", "build-ffmpeg-macos.sh"), "FFmpeg build script"); + + var actualSourceHash = ComputeSha256(sourceArchive); + if (!string.Equals(actualSourceHash, expectedSourceHash, StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException( + $"FFmpeg source archive hash mismatch. Expected {expectedSourceHash}, got {actualSourceHash}."); + + RunTool( + "/usr/bin/lipo", + new[] { sourceBinary, "-verify_arch", "arm64", "x86_64" }, + "bundled FFmpeg architecture verification"); + var version = RunToolCapture(sourceBinary, new[] { "-version" }, "bundled FFmpeg version check"); + if (!version.Contains("ffmpeg version 8.1.2") || + !version.Contains("--enable-encoder=h264_videotoolbox") || + version.Contains("--enable-gpl") || version.Contains("--enable-nonfree") || + version.Contains("libx264")) + throw new InvalidDataException( + "Bundled FFmpeg must be the pinned LGPL 8.1.2 VideoToolbox build without GPL/nonfree components."); + + var toolDirectory = Path.Combine(outputPath, "Contents", "Resources", "ProjectQuint", "Tools"); + Directory.CreateDirectory(toolDirectory); + var destinationBinary = Path.Combine(toolDirectory, "ffmpeg"); + File.Copy(sourceBinary, destinationBinary, true); + RunTool("/bin/chmod", new[] { "755", destinationBinary }, "bundled FFmpeg permission setup"); + + var noticeDirectory = Path.Combine(outputPath, "Contents", "Resources", "ThirdParty", "FFmpeg"); + Directory.CreateDirectory(noticeDirectory); + CopyRequired(thirdPartyRoot, noticeDirectory, "README.md"); + CopyRequired(thirdPartyRoot, noticeDirectory, "NOTICE.txt"); + CopyRequired(thirdPartyRoot, noticeDirectory, "COPYING.LGPLv2.1"); + File.Copy(sourceArchive, Path.Combine(noticeDirectory, Path.GetFileName(sourceArchive)), true); + File.Copy( + Path.Combine(projectRoot, "Tools", "build-ffmpeg-macos.sh"), + Path.Combine(noticeDirectory, "build-ffmpeg-macos.sh"), + true); + Debug.Log( + $"[ProjectQuint Build] Included Universal LGPL FFmpeg BGA converter and corresponding source: " + + destinationBinary); + } + + [PostProcessBuild(120)] + public static void IncludeThirdPartyNotices(BuildTarget target, string outputPath) + { + if (target != BuildTarget.StandaloneOSX) + return; + + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var sourceDirectory = Path.Combine(projectRoot, "ThirdParty", "Licenses"); + var destinationDirectory = Path.Combine( + outputPath, + "Contents", + "Resources", + "ThirdParty", + "Licenses"); + Directory.CreateDirectory(destinationDirectory); + + CopyRequired(sourceDirectory, destinationDirectory, "THIRD-PARTY-NOTICES.txt"); + CopyRequired(sourceDirectory, destinationDirectory, "FMOD-NOTICE.txt"); + CopyRequired(sourceDirectory, destinationDirectory, "Pretendard-OFL-1.1.txt"); + CopyRequired(sourceDirectory, destinationDirectory, "Rajdhani-OFL-1.1.txt"); + CopyRequired(sourceDirectory, destinationDirectory, "Sparkle-MIT.txt"); + Debug.Log( + $"[ProjectQuint Build] Included third-party notices and font licenses: " + + destinationDirectory); + } + + [PostProcessBuild(130)] + public static void IncludeSparkleFramework(BuildTarget target, string outputPath) + { + if (target != BuildTarget.StandaloneOSX) + return; + + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var sparkleRoot = Path.Combine(projectRoot, "ThirdParty", "Sparkle", "2.9.4"); + var sourceFramework = Path.Combine(sparkleRoot, "Sparkle.framework"); + var destinationDirectory = Path.Combine(outputPath, "Contents", "Frameworks"); + var destinationFramework = Path.Combine(destinationDirectory, "Sparkle.framework"); + if (!Directory.Exists(sourceFramework)) + throw new DirectoryNotFoundException( + $"Sparkle framework was not found: {sourceFramework}"); + Directory.CreateDirectory(destinationDirectory); + if (Directory.Exists(destinationFramework)) + Directory.Delete(destinationFramework, true); + RunTool( + "/usr/bin/ditto", + new[] { sourceFramework, destinationFramework }, + "Sparkle framework copy"); + RequireFile( + Path.Combine(destinationFramework, "Versions", "B", "Sparkle"), + "copied Sparkle framework binary"); + Debug.Log($"[ProjectQuint Build] Included Sparkle 2.9.4: {destinationFramework}"); + } + + private static void RequireFile(string path, string label) + { + if (!File.Exists(path)) + throw new FileNotFoundException($"{label} was not found; refusing to create an incomplete build.", path); + } + + private static void CopyRequired(string sourceDirectory, string destinationDirectory, string fileName) + { + var source = Path.Combine(sourceDirectory, fileName); + RequireFile(source, fileName); + File.Copy(source, Path.Combine(destinationDirectory, fileName), true); + } + + private static string ComputeSha256(string path) + { + using var stream = File.OpenRead(path); + using var sha256 = SHA256.Create(); + return string.Concat(sha256.ComputeHash(stream).Select(value => value.ToString("x2"))); + } + + private static string RunToolCapture(string executable, string[] arguments, string operation) + { + var startInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = executable, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + Arguments = string.Join(" ", arguments.Select(QuoteProcessArgument)), + }; + using var process = System.Diagnostics.Process.Start(startInfo) ?? + throw new InvalidOperationException($"Could not start {operation}."); + var standardOutput = process.StandardOutput.ReadToEnd(); + var standardError = process.StandardError.ReadToEnd(); + process.WaitForExit(); + if (process.ExitCode != 0) + throw new InvalidOperationException( + $"ProjectQuint {operation} failed ({process.ExitCode}): {standardError}\n{standardOutput}"); + return standardOutput + "\n" + standardError; + } + } +} +#endif diff --git a/Assets/ProjectQuint/Editor/ProjectQuintBuild.cs.meta b/Assets/ProjectQuint/Editor/ProjectQuintBuild.cs.meta new file mode 100644 index 0000000..46f7c20 --- /dev/null +++ b/Assets/ProjectQuint/Editor/ProjectQuintBuild.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b0d7464bb03b24dd4b47b33d5bcddcbf \ No newline at end of file diff --git a/Assets/ProjectQuint/Editor/ProjectQuintIconConfigurator.cs b/Assets/ProjectQuint/Editor/ProjectQuintIconConfigurator.cs new file mode 100644 index 0000000..8bf4ba4 --- /dev/null +++ b/Assets/ProjectQuint/Editor/ProjectQuintIconConfigurator.cs @@ -0,0 +1,37 @@ +#if UNITY_EDITOR +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.Build; +using UnityEngine; + +namespace ProjectQuint.Editor +{ + internal static class ProjectQuintIconConfigurator + { + internal const string IconAssetPath = + "Assets/ProjectQuint/Art/AppIcon/ProjectQuintAppIcon.png"; + + [MenuItem("ProjectQuint/Apply macOS App Icon")] + public static void ApplyMacAppIcon() + { + AssetDatabase.ImportAsset(IconAssetPath, ImportAssetOptions.ForceUpdate); + var icon = AssetDatabase.LoadAssetAtPath(IconAssetPath); + if (icon == null) + throw new InvalidOperationException($"ProjectQuint app icon is missing: {IconAssetPath}"); + + var target = NamedBuildTarget.Standalone; + var iconCount = PlayerSettings.GetIconSizes(target, IconKind.Application).Length; + if (iconCount <= 0) + throw new InvalidOperationException("Unity reported no Standalone application icon slots."); + + PlayerSettings.SetIcons( + target, + Enumerable.Repeat(icon, iconCount).ToArray(), + IconKind.Application); + AssetDatabase.SaveAssets(); + Debug.Log($"[ProjectQuint Icon] Applied {IconAssetPath} to {iconCount} Standalone icon slots."); + } + } +} +#endif diff --git a/Assets/ProjectQuint/Editor/ProjectQuintIconConfigurator.cs.meta b/Assets/ProjectQuint/Editor/ProjectQuintIconConfigurator.cs.meta new file mode 100644 index 0000000..1c71de1 --- /dev/null +++ b/Assets/ProjectQuint/Editor/ProjectQuintIconConfigurator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 262f464501a9e4e74b58ab78ccd699d7 \ No newline at end of file diff --git a/Assets/ProjectQuint/Editor/ProjectQuintVisualQa.cs b/Assets/ProjectQuint/Editor/ProjectQuintVisualQa.cs new file mode 100644 index 0000000..8d42b89 --- /dev/null +++ b/Assets/ProjectQuint/Editor/ProjectQuintVisualQa.cs @@ -0,0 +1,1149 @@ +using System.IO; +using System; +using System.Reflection; +using ProjectQuint.Bootstrap; +using ProjectQuint.Features.Flow; +using UnityEditor; +using UnityEngine; + +namespace ProjectQuint.Editor +{ + [InitializeOnLoad] + internal static class ProjectQuintVisualQa + { + private const string ResultsQaOutputArgument = "-resultsQaOutput"; + private const string MusicSelectQaOutputArgument = "-musicSelectQaOutput"; + private const string MusicSelectQaChartArgument = "-musicSelectQaChart"; + private const string MusicSelectQaHoldStepsArgument = "-musicSelectQaHoldSteps"; + private const string MusicSelectQaModalArgument = "-musicSelectQaModal"; + private const string MusicSelectQaPackRootArgument = "-musicSelectQaPackRoot"; + private const string MusicSelectQaCachePathArgument = "-musicSelectQaCachePath"; + private const double MusicSelectQaHoldIntervalSeconds = 0.055; + private const string QuickMenuQaOutputArgument = "-quickMenuQaOutput"; + private const string OptionsQaOutputArgument = "-optionsQaOutput"; + private const string OptionsQaCategoryArgument = "-optionsQaCategory"; + private const string OptionsQaFromCategoryArgument = "-optionsQaFromCategory"; + private const string OptionsQaKeyCountArgument = "-optionsQaKeyCount"; + private const string OptionsQaAudioSyncArgument = "-optionsQaAudioSync"; + private const string DaylightGalleryOutputArgument = "-daylightGalleryOutput"; + private const string ResultsQaSessionActiveKey = + "ProjectQuint.VisualQa.ResultsCaptureActive"; + private const string ResultsQaSessionOutputKey = + "ProjectQuint.VisualQa.ResultsCaptureOutput"; + private const string MusicSelectQaSessionActiveKey = + "ProjectQuint.VisualQa.MusicSelectCaptureActive"; + private const string MusicSelectQaSessionOutputKey = + "ProjectQuint.VisualQa.MusicSelectCaptureOutput"; + private const string QuickMenuQaSessionActiveKey = + "ProjectQuint.VisualQa.QuickMenuCaptureActive"; + private const string QuickMenuQaSessionOutputKey = + "ProjectQuint.VisualQa.QuickMenuCaptureOutput"; + private const string OptionsQaSessionActiveKey = + "ProjectQuint.VisualQa.OptionsCaptureActive"; + private const string OptionsQaSessionOutputKey = + "ProjectQuint.VisualQa.OptionsCaptureOutput"; + private const string OptionsQaSessionCategoryKey = + "ProjectQuint.VisualQa.OptionsCaptureCategory"; + private const string OptionsQaSessionFromCategoryKey = + "ProjectQuint.VisualQa.OptionsCaptureFromCategory"; + private const string DaylightGallerySessionActiveKey = + "ProjectQuint.VisualQa.DaylightGalleryActive"; + private const string DaylightGallerySessionOutputKey = + "ProjectQuint.VisualQa.DaylightGalleryOutput"; + private const string ChartFeaturePreviewMenu = + "Project Quint/QA/Open Chart Feature Radar Preview"; + private const string CapturePlayViewMenu = + "Project Quint/QA/Capture Play View"; + private const string CapturePlayView4KMenu = + "Project Quint/QA/Capture Play View 4K"; + private const string AudioOptionsPreviewMenu = + "Project Quint/QA/Open Audio Options Preview"; + private const string CaptureAudioOptionsMenu = + "Project Quint/QA/Capture Audio Options"; + private const string ResultsPreviewMenu = + "Project Quint/QA/Open Results Preview"; + private const string CaptureResultsPreviewMenu = + "Project Quint/QA/Capture Results Preview"; + private static string s_CommandLineResultsOutputPath; + private static double s_CommandLineResultsDeadline; + private static double s_CommandLineResultsCaptureAt; + private static bool s_CommandLineResultsPreviewReady; + private static bool s_CommandLineResultsCaptureRequested; + private static string s_CommandLineMusicSelectOutputPath; + private static double s_CommandLineMusicSelectDeadline; + private static double s_CommandLineMusicSelectCaptureAt; + private static double s_CommandLineMusicSelectNextHoldStepAt; + private static int s_CommandLineMusicSelectHoldSteps; + private static bool s_CommandLineMusicSelectPreviewReady; + private static bool s_CommandLineMusicSelectCaptureRequested; + private static string s_CommandLineQuickMenuOutputPath; + private static double s_CommandLineQuickMenuDeadline; + private static double s_CommandLineQuickMenuCaptureAt; + private static bool s_CommandLineQuickMenuPreviewReady; + private static bool s_CommandLineQuickMenuCaptureRequested; + private static string s_CommandLineOptionsOutputPath; + private static string s_CommandLineOptionsCategory; + private static string s_CommandLineOptionsFromCategory; + private static double s_CommandLineOptionsDeadline; + private static double s_CommandLineOptionsCaptureAt; + private static double s_CommandLineOptionsTransitionAt; + private static int s_CommandLineOptionsBurstFrame = -1; + private static bool s_CommandLineOptionsPreviewReady; + private static bool s_CommandLineOptionsCaptureRequested; + private static bool s_CommandLineOptionsPreviewPressed; + private static readonly string[] DaylightGallerySurfaces = + { + "startup-attribution", + "team-denia-branding", + "update", + "music-select", + "sort", + "filter", + "quick-menu", + "options", + "audio-sync", + "pack", + "pack-remove", + "tables", + "course", + "profile-overview", + "profile-rating", + "pack-cache", + "pack-cache-confirm", + "loading", + "pause", + "game-over", + "results", + "course-results", + "error", + }; + private static string s_DaylightGalleryOutputDirectory; + private static double s_DaylightGalleryDeadline; + private static double s_DaylightGalleryCaptureAt; + private static int s_DaylightGalleryIndex; + private static bool s_DaylightGallerySurfaceReady; + private static bool s_DaylightGalleryCaptureRequested; + + static ProjectQuintVisualQa() + { + if (SessionState.GetBool(ResultsQaSessionActiveKey, false)) + { + s_CommandLineResultsOutputPath = + SessionState.GetString(ResultsQaSessionOutputKey, string.Empty); + s_CommandLineResultsDeadline = EditorApplication.timeSinceStartup + 30.0; + EditorApplication.update -= UpdateCommandLineResultsCapture; + EditorApplication.update += UpdateCommandLineResultsCapture; + } + + if (SessionState.GetBool(MusicSelectQaSessionActiveKey, false)) + { + s_CommandLineMusicSelectOutputPath = + SessionState.GetString(MusicSelectQaSessionOutputKey, string.Empty); + s_CommandLineMusicSelectDeadline = EditorApplication.timeSinceStartup + 45.0; + EditorApplication.update -= UpdateCommandLineMusicSelectCapture; + EditorApplication.update += UpdateCommandLineMusicSelectCapture; + } + + if (SessionState.GetBool(QuickMenuQaSessionActiveKey, false)) + { + s_CommandLineQuickMenuOutputPath = + SessionState.GetString(QuickMenuQaSessionOutputKey, string.Empty); + s_CommandLineQuickMenuDeadline = EditorApplication.timeSinceStartup + 45.0; + EditorApplication.update -= UpdateCommandLineQuickMenuCapture; + EditorApplication.update += UpdateCommandLineQuickMenuCapture; + } + + if (SessionState.GetBool(OptionsQaSessionActiveKey, false)) + { + s_CommandLineOptionsOutputPath = + SessionState.GetString(OptionsQaSessionOutputKey, string.Empty); + s_CommandLineOptionsCategory = + SessionState.GetString(OptionsQaSessionCategoryKey, "gameplay"); + s_CommandLineOptionsFromCategory = + SessionState.GetString(OptionsQaSessionFromCategoryKey, string.Empty); + s_CommandLineOptionsDeadline = EditorApplication.timeSinceStartup + 30.0; + EditorApplication.update -= UpdateCommandLineOptionsCapture; + EditorApplication.update += UpdateCommandLineOptionsCapture; + } + + if (SessionState.GetBool(DaylightGallerySessionActiveKey, false)) + { + s_DaylightGalleryOutputDirectory = + SessionState.GetString(DaylightGallerySessionOutputKey, string.Empty); + s_DaylightGalleryDeadline = EditorApplication.timeSinceStartup + 180.0; + EditorApplication.update -= UpdateCommandLineDaylightGalleryCapture; + EditorApplication.update += UpdateCommandLineDaylightGalleryCapture; + } + } + + [MenuItem(ChartFeaturePreviewMenu)] + private static void OpenChartFeatureRadarPreview() + { + if (!EditorApplication.isPlaying) + { + Debug.LogError("[ProjectQuint Visual QA] Enter Play Mode before opening the preview."); + return; + } + + var app = UnityEngine.Object.FindFirstObjectByType(); + if (app == null) + { + Debug.LogError("[ProjectQuint Visual QA] ProjectQuintApp is not running."); + return; + } + + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var developmentRoot = Path.GetFullPath(Path.Combine(projectRoot, "..")); + var packRoot = Path.Combine(developmentRoot, "AERY-FULL-PACK"); + var cachePath = Path.Combine(projectRoot, "_workspace", "aery-feature-index-v4.json"); + if (!Directory.Exists(packRoot) || !File.Exists(cachePath)) + { + Debug.LogError( + $"[ProjectQuint Visual QA] Preview fixture unavailable. Pack: {packRoot}; cache: {cachePath}"); + return; + } + + app.EditorOpenChartFeaturePreview(packRoot, cachePath, "_opslimone_5a.bms"); + MaximizeGameView(1920, 1080); + Debug.Log("[ProjectQuint Visual QA] Chart feature radar preview opened."); + } + + [MenuItem(AudioOptionsPreviewMenu)] + private static void OpenAudioOptionsPreview() + { + if (!TryFindRunningApp(out var app)) + return; + app.EditorOpenAudioOptionsPreview(); + MaximizeGameView(1920, 1080); + Debug.Log("[ProjectQuint Visual QA] Audio options preview opened."); + } + + [MenuItem(CaptureAudioOptionsMenu)] + private static void CaptureAudioOptions() => + CapturePlayView(1920, 1080, "audio-dsp-options.png"); + + [MenuItem(ResultsPreviewMenu)] + private static void OpenResultsPreview() + { + if (!TryFindRunningApp(out var app)) + return; + app.EditorOpenResultsPreview(); + MaximizeGameView(1920, 1080); + Debug.Log("[ProjectQuint Visual QA] Results preview opened."); + } + + [MenuItem(CaptureResultsPreviewMenu)] + private static void CaptureResultsPreview() + { + if (!TryFindRunningApp(out var app)) + return; + app.EditorOpenResultsPreview(); + MaximizeGameView(1920, 1080); + var captureAt = EditorApplication.timeSinceStartup + 0.5; + void CaptureWhenReady() + { + if (EditorApplication.timeSinceStartup < captureAt) + return; + EditorApplication.update -= CaptureWhenReady; + CapturePlayView(1920, 1080, "results-v4-runtime-qa.png"); + } + EditorApplication.update += CaptureWhenReady; + } + + ///

+ /// Opens Play Mode, switches directly to the deterministic Results preview, + /// captures it, and exits. Intended for CLI-driven visual QA without playing + /// through an entire chart. + /// + public static void CaptureResultsPreviewFromCommandLine() + { + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + s_CommandLineResultsOutputPath = ReadCommandLineValue(ResultsQaOutputArgument); + if (string.IsNullOrWhiteSpace(s_CommandLineResultsOutputPath)) + { + s_CommandLineResultsOutputPath = Path.Combine( + projectRoot, + "_workspace", + "results-v4-runtime-qa.png"); + } + + s_CommandLineResultsOutputPath = Path.GetFullPath(s_CommandLineResultsOutputPath); + Directory.CreateDirectory(Path.GetDirectoryName(s_CommandLineResultsOutputPath)); + if (File.Exists(s_CommandLineResultsOutputPath)) + File.Delete(s_CommandLineResultsOutputPath); + + s_CommandLineResultsDeadline = EditorApplication.timeSinceStartup + 30.0; + s_CommandLineResultsCaptureAt = 0.0; + s_CommandLineResultsPreviewReady = false; + s_CommandLineResultsCaptureRequested = false; + SessionState.SetBool(ResultsQaSessionActiveKey, true); + SessionState.SetString(ResultsQaSessionOutputKey, s_CommandLineResultsOutputPath); + EditorApplication.update -= UpdateCommandLineResultsCapture; + EditorApplication.update += UpdateCommandLineResultsCapture; + EditorApplication.EnterPlaymode(); + Debug.Log( + $"[ProjectQuint Visual QA] Starting immediate Results capture: {s_CommandLineResultsOutputPath}"); + } + + /// + /// Opens Play Mode, switches directly to a real indexed chart in Music + /// Select, captures it, and exits. This avoids waiting for gameplay to end. + /// + public static void CaptureMusicSelectPreviewFromCommandLine() + { + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + s_CommandLineMusicSelectOutputPath = + ReadCommandLineValue(MusicSelectQaOutputArgument); + if (string.IsNullOrWhiteSpace(s_CommandLineMusicSelectOutputPath)) + { + s_CommandLineMusicSelectOutputPath = Path.Combine( + projectRoot, + "_workspace", + "music-select-a-v2-runtime-qa.png"); + } + + s_CommandLineMusicSelectOutputPath = + Path.GetFullPath(s_CommandLineMusicSelectOutputPath); + Directory.CreateDirectory(Path.GetDirectoryName(s_CommandLineMusicSelectOutputPath)); + if (File.Exists(s_CommandLineMusicSelectOutputPath)) + File.Delete(s_CommandLineMusicSelectOutputPath); + + s_CommandLineMusicSelectDeadline = EditorApplication.timeSinceStartup + 45.0; + s_CommandLineMusicSelectCaptureAt = 0.0; + s_CommandLineMusicSelectNextHoldStepAt = 0.0; + s_CommandLineMusicSelectHoldSteps = 0; + s_CommandLineMusicSelectPreviewReady = false; + s_CommandLineMusicSelectCaptureRequested = false; + SessionState.SetBool(MusicSelectQaSessionActiveKey, true); + SessionState.SetString( + MusicSelectQaSessionOutputKey, + s_CommandLineMusicSelectOutputPath); + EditorApplication.update -= UpdateCommandLineMusicSelectCapture; + EditorApplication.update += UpdateCommandLineMusicSelectCapture; + EditorApplication.EnterPlaymode(); + Debug.Log( + $"[ProjectQuint Visual QA] Starting immediate Music Select capture: {s_CommandLineMusicSelectOutputPath}"); + } + + /// + /// Opens the same real Music Select fixture, overlays Quick Menu directly, + /// captures it, and exits without requiring input or gameplay. + /// + public static void CaptureQuickMenuPreviewFromCommandLine() + { + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + s_CommandLineQuickMenuOutputPath = + ReadCommandLineValue(QuickMenuQaOutputArgument); + if (string.IsNullOrWhiteSpace(s_CommandLineQuickMenuOutputPath)) + { + s_CommandLineQuickMenuOutputPath = Path.Combine( + projectRoot, + "_workspace", + "quick-menu-v2-runtime-qa.png"); + } + + s_CommandLineQuickMenuOutputPath = + Path.GetFullPath(s_CommandLineQuickMenuOutputPath); + Directory.CreateDirectory(Path.GetDirectoryName(s_CommandLineQuickMenuOutputPath)); + if (File.Exists(s_CommandLineQuickMenuOutputPath)) + File.Delete(s_CommandLineQuickMenuOutputPath); + + s_CommandLineQuickMenuDeadline = EditorApplication.timeSinceStartup + 45.0; + s_CommandLineQuickMenuCaptureAt = 0.0; + s_CommandLineQuickMenuPreviewReady = false; + s_CommandLineQuickMenuCaptureRequested = false; + SessionState.SetBool(QuickMenuQaSessionActiveKey, true); + SessionState.SetString( + QuickMenuQaSessionOutputKey, + s_CommandLineQuickMenuOutputPath); + EditorApplication.update -= UpdateCommandLineQuickMenuCapture; + EditorApplication.update += UpdateCommandLineQuickMenuCapture; + EditorApplication.EnterPlaymode(); + Debug.Log( + $"[ProjectQuint Visual QA] Starting immediate Quick Menu capture: {s_CommandLineQuickMenuOutputPath}"); + } + + /// + /// Opens Play Mode, switches directly to the requested Options category, + /// captures it, and exits without requiring menu navigation. + /// + public static void CaptureOptionsPreviewFromCommandLine() + { + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + s_CommandLineOptionsOutputPath = ReadCommandLineValue(OptionsQaOutputArgument); + if (string.IsNullOrWhiteSpace(s_CommandLineOptionsOutputPath)) + { + s_CommandLineOptionsOutputPath = Path.Combine( + projectRoot, + "_workspace", + "options-module-gameplay-runtime-qa.png"); + } + + s_CommandLineOptionsCategory = + ReadCommandLineValue(OptionsQaCategoryArgument).Trim().ToLowerInvariant(); + if (!Enum.TryParse( + s_CommandLineOptionsCategory, + true, + out _)) + s_CommandLineOptionsCategory = "gameplay"; + s_CommandLineOptionsFromCategory = + ReadCommandLineValue(OptionsQaFromCategoryArgument).Trim().ToLowerInvariant(); + if (!Enum.TryParse( + s_CommandLineOptionsFromCategory, + true, + out _)) + s_CommandLineOptionsFromCategory = string.Empty; + + s_CommandLineOptionsOutputPath = + Path.GetFullPath(s_CommandLineOptionsOutputPath); + Directory.CreateDirectory(Path.GetDirectoryName(s_CommandLineOptionsOutputPath)); + if (File.Exists(s_CommandLineOptionsOutputPath)) + File.Delete(s_CommandLineOptionsOutputPath); + + s_CommandLineOptionsDeadline = EditorApplication.timeSinceStartup + 30.0; + s_CommandLineOptionsCaptureAt = 0.0; + s_CommandLineOptionsTransitionAt = 0.0; + s_CommandLineOptionsBurstFrame = -1; + s_CommandLineOptionsPreviewReady = false; + s_CommandLineOptionsCaptureRequested = false; + s_CommandLineOptionsPreviewPressed = false; + SessionState.SetBool(OptionsQaSessionActiveKey, true); + SessionState.SetString( + OptionsQaSessionOutputKey, + s_CommandLineOptionsOutputPath); + SessionState.SetString( + OptionsQaSessionCategoryKey, + s_CommandLineOptionsCategory); + SessionState.SetString( + OptionsQaSessionFromCategoryKey, + s_CommandLineOptionsFromCategory); + EditorApplication.update -= UpdateCommandLineOptionsCapture; + EditorApplication.update += UpdateCommandLineOptionsCapture; + EditorApplication.EnterPlaymode(); + Debug.Log( + $"[ProjectQuint Visual QA] Starting immediate Options ({s_CommandLineOptionsCategory}) capture: {s_CommandLineOptionsOutputPath}"); + } + + /// + /// Captures every retained Daylight Prism screen and overlay in one Play Mode + /// session. Each surface is opened through an editor-only app preview path, so + /// visual QA never waits for chart completion or triggers persistence. + /// + public static void CaptureDaylightGalleryFromCommandLine() + { + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + s_DaylightGalleryOutputDirectory = + ReadCommandLineValue(DaylightGalleryOutputArgument); + if (string.IsNullOrWhiteSpace(s_DaylightGalleryOutputDirectory)) + { + s_DaylightGalleryOutputDirectory = Path.Combine( + projectRoot, + "_workspace", + "qa-captures", + "daylight-header-runtime"); + } + s_DaylightGalleryOutputDirectory = + Path.GetFullPath(s_DaylightGalleryOutputDirectory); + Directory.CreateDirectory(s_DaylightGalleryOutputDirectory); + foreach (var surface in DaylightGallerySurfaces) + { + var path = DaylightGalleryPath(surface); + if (File.Exists(path)) + File.Delete(path); + } + + s_DaylightGalleryDeadline = EditorApplication.timeSinceStartup + 180.0; + s_DaylightGalleryCaptureAt = 0.0; + s_DaylightGalleryIndex = 0; + s_DaylightGallerySurfaceReady = false; + s_DaylightGalleryCaptureRequested = false; + SessionState.SetBool(DaylightGallerySessionActiveKey, true); + SessionState.SetString( + DaylightGallerySessionOutputKey, + s_DaylightGalleryOutputDirectory); + EditorApplication.update -= UpdateCommandLineDaylightGalleryCapture; + EditorApplication.update += UpdateCommandLineDaylightGalleryCapture; + EditorApplication.EnterPlaymode(); + Debug.Log( + $"[ProjectQuint Visual QA] Starting Daylight gallery capture: {s_DaylightGalleryOutputDirectory}"); + } + + private static void UpdateCommandLineResultsCapture() + { + if (EditorApplication.timeSinceStartup >= s_CommandLineResultsDeadline) + { + FinishCommandLineResultsCapture( + 1, + "[ProjectQuint Visual QA] Timed out while capturing the Results preview."); + return; + } + + if (!EditorApplication.isPlaying) + return; + + if (!s_CommandLineResultsPreviewReady) + { + var app = UnityEngine.Object.FindFirstObjectByType(); + if (app == null) + return; + app.EditorOpenResultsPreview(); + MaximizeGameView(1920, 1080); + s_CommandLineResultsPreviewReady = true; + s_CommandLineResultsCaptureAt = EditorApplication.timeSinceStartup + 0.75; + return; + } + + if (!s_CommandLineResultsCaptureRequested && + EditorApplication.timeSinceStartup >= s_CommandLineResultsCaptureAt) + { + ScreenCapture.CaptureScreenshot(s_CommandLineResultsOutputPath, 1); + s_CommandLineResultsCaptureRequested = true; + return; + } + + if (s_CommandLineResultsCaptureRequested && + File.Exists(s_CommandLineResultsOutputPath) && + new FileInfo(s_CommandLineResultsOutputPath).Length > 0) + { + FinishCommandLineResultsCapture( + 0, + $"[ProjectQuint Visual QA] Results capture completed: {s_CommandLineResultsOutputPath}"); + } + } + + private static void FinishCommandLineResultsCapture(int exitCode, string message) + { + EditorApplication.update -= UpdateCommandLineResultsCapture; + SessionState.EraseBool(ResultsQaSessionActiveKey); + SessionState.EraseString(ResultsQaSessionOutputKey); + if (exitCode == 0) + Debug.Log(message); + else + Debug.LogError(message); + EditorApplication.Exit(exitCode); + } + + private static void UpdateCommandLineMusicSelectCapture() + { + if (EditorApplication.timeSinceStartup >= s_CommandLineMusicSelectDeadline) + { + FinishCommandLineMusicSelectCapture( + 1, + "[ProjectQuint Visual QA] Timed out while capturing Music Select."); + return; + } + + if (!EditorApplication.isPlaying) + return; + + if (!s_CommandLineMusicSelectPreviewReady) + { + var app = UnityEngine.Object.FindFirstObjectByType(); + if (app == null) + return; + + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var developmentRoot = Path.GetFullPath(Path.Combine(projectRoot, "..")); + var packRoot = ReadCommandLineValue(MusicSelectQaPackRootArgument); + if (string.IsNullOrWhiteSpace(packRoot)) + packRoot = Path.Combine(developmentRoot, "AERY-FULL-PACK"); + else + packRoot = Path.GetFullPath(packRoot); + var cachePath = ReadCommandLineValue(MusicSelectQaCachePathArgument); + if (string.IsNullOrWhiteSpace(cachePath)) + { + cachePath = Path.Combine( + projectRoot, + "_workspace", + "aery-feature-index-v4.json"); + } + else + { + cachePath = Path.GetFullPath(cachePath); + } + if (!Directory.Exists(packRoot)) + { + FinishCommandLineMusicSelectCapture( + 1, + $"[ProjectQuint Visual QA] Music Select fixture unavailable. Pack: {packRoot}"); + return; + } + + var preferredChart = ReadCommandLineValue(MusicSelectQaChartArgument); + if (string.IsNullOrWhiteSpace(preferredChart)) + preferredChart = "_opslimone_5a.bms"; + else if (string.Equals( + preferredChart, + "RANDOM", + StringComparison.OrdinalIgnoreCase)) + preferredChart = string.Empty; + app.EditorOpenChartFeaturePreview(packRoot, cachePath, preferredChart); + var modal = ReadCommandLineValue(MusicSelectQaModalArgument); + if (string.Equals(modal, "sort", StringComparison.OrdinalIgnoreCase)) + app.EditorOpenLibraryControlPreview(false); + else if (string.Equals(modal, "filter", StringComparison.OrdinalIgnoreCase)) + app.EditorOpenLibraryControlPreview(true); + MaximizeGameView(1920, 1080); + var holdStepsText = ReadCommandLineValue(MusicSelectQaHoldStepsArgument); + if (!int.TryParse(holdStepsText, out s_CommandLineMusicSelectHoldSteps)) + s_CommandLineMusicSelectHoldSteps = 0; + s_CommandLineMusicSelectHoldSteps = + Math.Max(0, Math.Min(256, s_CommandLineMusicSelectHoldSteps)); + s_CommandLineMusicSelectPreviewReady = true; + if (s_CommandLineMusicSelectHoldSteps > 0) + { + s_CommandLineMusicSelectNextHoldStepAt = + EditorApplication.timeSinceStartup + 1.0; + s_CommandLineMusicSelectCaptureAt = double.PositiveInfinity; + } + else + { + s_CommandLineMusicSelectCaptureAt = + EditorApplication.timeSinceStartup + 1.25; + } + return; + } + + if (s_CommandLineMusicSelectHoldSteps > 0 && + EditorApplication.timeSinceStartup >= + s_CommandLineMusicSelectNextHoldStepAt) + { + var app = UnityEngine.Object.FindFirstObjectByType(); + if (app == null) + return; + + app.EditorMoveLibrarySelection(1); + s_CommandLineMusicSelectHoldSteps--; + s_CommandLineMusicSelectNextHoldStepAt = + EditorApplication.timeSinceStartup + + MusicSelectQaHoldIntervalSeconds; + if (s_CommandLineMusicSelectHoldSteps == 0) + { + // Capture before the removed 80 ms correction could have run. + s_CommandLineMusicSelectCaptureAt = + EditorApplication.timeSinceStartup + 0.02; + } + return; + } + + if (!s_CommandLineMusicSelectCaptureRequested && + EditorApplication.timeSinceStartup >= s_CommandLineMusicSelectCaptureAt) + { + ScreenCapture.CaptureScreenshot(s_CommandLineMusicSelectOutputPath, 1); + s_CommandLineMusicSelectCaptureRequested = true; + return; + } + + if (s_CommandLineMusicSelectCaptureRequested && + File.Exists(s_CommandLineMusicSelectOutputPath) && + new FileInfo(s_CommandLineMusicSelectOutputPath).Length > 0) + { + FinishCommandLineMusicSelectCapture( + 0, + $"[ProjectQuint Visual QA] Music Select capture completed: {s_CommandLineMusicSelectOutputPath}"); + } + } + + private static void FinishCommandLineMusicSelectCapture(int exitCode, string message) + { + EditorApplication.update -= UpdateCommandLineMusicSelectCapture; + SessionState.EraseBool(MusicSelectQaSessionActiveKey); + SessionState.EraseString(MusicSelectQaSessionOutputKey); + if (exitCode == 0) + Debug.Log(message); + else + Debug.LogError(message); + EditorApplication.Exit(exitCode); + } + + private static void UpdateCommandLineQuickMenuCapture() + { + if (EditorApplication.timeSinceStartup >= s_CommandLineQuickMenuDeadline) + { + FinishCommandLineQuickMenuCapture( + 1, + "[ProjectQuint Visual QA] Timed out while capturing Quick Menu."); + return; + } + + if (!EditorApplication.isPlaying) + return; + + if (!s_CommandLineQuickMenuPreviewReady) + { + var app = UnityEngine.Object.FindFirstObjectByType(); + if (app == null) + return; + + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var developmentRoot = Path.GetFullPath(Path.Combine(projectRoot, "..")); + var packRoot = Path.Combine(developmentRoot, "AERY-FULL-PACK"); + var cachePath = Path.Combine( + projectRoot, + "_workspace", + "aery-feature-index-v4.json"); + if (!Directory.Exists(packRoot) || !File.Exists(cachePath)) + { + FinishCommandLineQuickMenuCapture( + 1, + $"[ProjectQuint Visual QA] Quick Menu fixture unavailable. Pack: {packRoot}; cache: {cachePath}"); + return; + } + + app.EditorOpenChartFeaturePreview(packRoot, cachePath, "_opslimone_5a.bms"); + app.EditorOpenQuickOptionsPreview(); + MaximizeGameView(1920, 1080); + s_CommandLineQuickMenuPreviewReady = true; + s_CommandLineQuickMenuCaptureAt = + EditorApplication.timeSinceStartup + 1.25; + return; + } + + if (!s_CommandLineQuickMenuCaptureRequested && + EditorApplication.timeSinceStartup >= s_CommandLineQuickMenuCaptureAt) + { + ScreenCapture.CaptureScreenshot(s_CommandLineQuickMenuOutputPath, 1); + s_CommandLineQuickMenuCaptureRequested = true; + return; + } + + if (s_CommandLineQuickMenuCaptureRequested && + File.Exists(s_CommandLineQuickMenuOutputPath) && + new FileInfo(s_CommandLineQuickMenuOutputPath).Length > 0) + { + FinishCommandLineQuickMenuCapture( + 0, + $"[ProjectQuint Visual QA] Quick Menu capture completed: {s_CommandLineQuickMenuOutputPath}"); + } + } + + private static void FinishCommandLineQuickMenuCapture(int exitCode, string message) + { + EditorApplication.update -= UpdateCommandLineQuickMenuCapture; + SessionState.EraseBool(QuickMenuQaSessionActiveKey); + SessionState.EraseString(QuickMenuQaSessionOutputKey); + if (exitCode == 0) + Debug.Log(message); + else + Debug.LogError(message); + EditorApplication.Exit(exitCode); + } + + private static void UpdateCommandLineOptionsCapture() + { + if (EditorApplication.timeSinceStartup >= s_CommandLineOptionsDeadline) + { + FinishCommandLineOptionsCapture( + 1, + "[ProjectQuint Visual QA] Timed out while capturing Options."); + return; + } + + if (!EditorApplication.isPlaying) + return; + + if (!s_CommandLineOptionsPreviewReady) + { + var app = UnityEngine.Object.FindFirstObjectByType(); + if (app == null) + return; + + var initialCategory = string.IsNullOrWhiteSpace(s_CommandLineOptionsFromCategory) + ? s_CommandLineOptionsCategory + : s_CommandLineOptionsFromCategory; + OpenOptionsCategory(app, initialCategory); + var audioSyncMode = ReadCommandLineValue(OptionsQaAudioSyncArgument); + if (string.Equals(audioSyncMode, "run", StringComparison.OrdinalIgnoreCase)) + app.EditorStartAudioSyncCalibrationPreview(); + else if (string.Equals(audioSyncMode, "1", StringComparison.Ordinal)) + app.EditorOpenAudioSyncCalibrationPreview(); + + MaximizeGameView(1920, 1080); + s_CommandLineOptionsPreviewReady = true; + if (!string.Equals( + initialCategory, + s_CommandLineOptionsCategory, + StringComparison.Ordinal)) + { + s_CommandLineOptionsTransitionAt = + EditorApplication.timeSinceStartup + 0.5; + s_CommandLineOptionsCaptureAt = double.PositiveInfinity; + } + else + { + s_CommandLineOptionsCaptureAt = + EditorApplication.timeSinceStartup + 1.0; + } + return; + } + + if (s_CommandLineOptionsTransitionAt > 0.0 && + EditorApplication.timeSinceStartup >= s_CommandLineOptionsTransitionAt) + { + var app = UnityEngine.Object.FindFirstObjectByType(); + if (app == null) + return; + OpenOptionsCategory(app, s_CommandLineOptionsCategory); + s_CommandLineOptionsTransitionAt = 0.0; + s_CommandLineOptionsBurstFrame = 0; + s_CommandLineOptionsCaptureAt = + EditorApplication.timeSinceStartup + 0.5; + return; + } + + if (s_CommandLineOptionsBurstFrame >= 0 && + s_CommandLineOptionsBurstFrame < 6) + { + ScreenCapture.CaptureScreenshot( + OptionsBurstPath( + s_CommandLineOptionsOutputPath, + s_CommandLineOptionsBurstFrame), + 1); + s_CommandLineOptionsBurstFrame++; + return; + } + + if (!s_CommandLineOptionsCaptureRequested && + EditorApplication.timeSinceStartup >= s_CommandLineOptionsCaptureAt) + { + ScreenCapture.CaptureScreenshot(s_CommandLineOptionsOutputPath, 1); + s_CommandLineOptionsCaptureRequested = true; + return; + } + + if (!s_CommandLineOptionsPreviewPressed && + string.Equals( + s_CommandLineOptionsCategory, + "gameplay", + StringComparison.Ordinal) && + EditorApplication.timeSinceStartup >= s_CommandLineOptionsCaptureAt - 0.12) + { + var app = UnityEngine.Object.FindFirstObjectByType(); + if (app != null) + { + app.EditorPressOptionsPreviewLane(3); + s_CommandLineOptionsPreviewPressed = true; + } + return; + } + + if (s_CommandLineOptionsCaptureRequested && + File.Exists(s_CommandLineOptionsOutputPath) && + new FileInfo(s_CommandLineOptionsOutputPath).Length > 0) + { + FinishCommandLineOptionsCapture( + 0, + $"[ProjectQuint Visual QA] Options capture completed: {s_CommandLineOptionsOutputPath}"); + } + } + + private static void OpenOptionsCategory(ProjectQuintApp app, string categoryName) + { + var keyCountText = ReadCommandLineValue(OptionsQaKeyCountArgument); + var hasRequestedKeyCount = + int.TryParse(keyCountText, out var requestedKeyCount) && + requestedKeyCount >= 4 && + requestedKeyCount <= 7; + if (string.Equals(categoryName, "audio", StringComparison.Ordinal)) + { + app.EditorOpenAudioOptionsPreview(); + return; + } + if (string.Equals(categoryName, "gameplay", StringComparison.Ordinal)) + { + app.EditorOpenGameplayOptionsPreview( + hasRequestedKeyCount ? requestedKeyCount : 0); + return; + } + if (Enum.TryParse(categoryName, true, out var category)) + { + app.EditorOpenOptionsPreview( + category, + hasRequestedKeyCount ? requestedKeyCount : 0); + } + } + + private static string OptionsBurstPath(string outputPath, int frame) + { + var directory = Path.GetDirectoryName(outputPath); + var fileName = Path.GetFileNameWithoutExtension(outputPath); + return Path.Combine(directory ?? string.Empty, $"{fileName}-frame-{frame:00}.png"); + } + + private static void FinishCommandLineOptionsCapture(int exitCode, string message) + { + EditorApplication.update -= UpdateCommandLineOptionsCapture; + SessionState.EraseBool(OptionsQaSessionActiveKey); + SessionState.EraseString(OptionsQaSessionOutputKey); + SessionState.EraseString(OptionsQaSessionCategoryKey); + SessionState.EraseString(OptionsQaSessionFromCategoryKey); + if (exitCode == 0) + Debug.Log(message); + else + Debug.LogError(message); + EditorApplication.Exit(exitCode); + } + + private static void UpdateCommandLineDaylightGalleryCapture() + { + if (EditorApplication.timeSinceStartup >= s_DaylightGalleryDeadline) + { + FinishCommandLineDaylightGalleryCapture( + 1, + "[ProjectQuint Visual QA] Timed out while capturing the Daylight gallery."); + return; + } + + if (!EditorApplication.isPlaying) + return; + if (s_DaylightGalleryIndex >= DaylightGallerySurfaces.Length) + { + FinishCommandLineDaylightGalleryCapture( + 0, + $"[ProjectQuint Visual QA] Daylight gallery completed: {s_DaylightGalleryOutputDirectory}"); + return; + } + + var app = UnityEngine.Object.FindFirstObjectByType(); + if (app == null) + return; + var surface = DaylightGallerySurfaces[s_DaylightGalleryIndex]; + var outputPath = DaylightGalleryPath(surface); + if (!s_DaylightGallerySurfaceReady) + { + OpenDaylightGallerySurface(app, surface); + MaximizeGameView(1920, 1080); + s_DaylightGallerySurfaceReady = true; + s_DaylightGalleryCaptureRequested = false; + s_DaylightGalleryCaptureAt = EditorApplication.timeSinceStartup + 0.8; + return; + } + + if (!s_DaylightGalleryCaptureRequested && + EditorApplication.timeSinceStartup >= s_DaylightGalleryCaptureAt) + { + ScreenCapture.CaptureScreenshot(outputPath, 1); + s_DaylightGalleryCaptureRequested = true; + return; + } + + if (!s_DaylightGalleryCaptureRequested || + !File.Exists(outputPath) || + new FileInfo(outputPath).Length <= 0) + return; + + Debug.Log($"[ProjectQuint Visual QA] Captured {surface}: {outputPath}"); + s_DaylightGalleryIndex++; + s_DaylightGallerySurfaceReady = false; + s_DaylightGalleryCaptureRequested = false; + } + + private static void OpenDaylightGallerySurface(ProjectQuintApp app, string surface) + { + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var developmentRoot = Path.GetFullPath(Path.Combine(projectRoot, "..")); + var packRoot = Path.Combine(developmentRoot, "AERY-FULL-PACK"); + var cachePath = Path.Combine( + projectRoot, + "_workspace", + "aery-feature-index-v4.json"); + var needsLibrary = + surface == "music-select" || + surface == "sort" || + surface == "filter" || + surface == "quick-menu" || + surface.StartsWith("profile", StringComparison.Ordinal) || + surface == "loading" || + surface == "pause"; + if (needsLibrary) + { + if (!Directory.Exists(packRoot)) + { + throw new DirectoryNotFoundException( + $"Daylight gallery fixture is unavailable: {packRoot}"); + } + app.EditorOpenChartFeaturePreview(packRoot, cachePath, "_opslimone_5a.bms"); + } + + switch (surface) + { + case "music-select": + return; + case "sort": + app.EditorOpenLibraryControlPreview(false); + return; + case "filter": + app.EditorOpenLibraryControlPreview(true); + return; + case "quick-menu": + app.EditorOpenQuickOptionsPreview(); + return; + case "options": + app.EditorOpenOptionsPreview(OptionsCategory.Gameplay); + return; + case "results": + app.EditorOpenResultsPreview(); + return; + default: + app.EditorOpenVisualQaSurface(surface); + return; + } + } + + private static string DaylightGalleryPath(string surface) => + Path.Combine(s_DaylightGalleryOutputDirectory, $"{surface}.png"); + + private static void FinishCommandLineDaylightGalleryCapture( + int exitCode, + string message) + { + EditorApplication.update -= UpdateCommandLineDaylightGalleryCapture; + SessionState.EraseBool(DaylightGallerySessionActiveKey); + SessionState.EraseString(DaylightGallerySessionOutputKey); + if (exitCode == 0) + Debug.Log(message); + else + Debug.LogError(message); + EditorApplication.Exit(exitCode); + } + + private static string ReadCommandLineValue(string argumentName) + { + var arguments = Environment.GetCommandLineArgs(); + for (var index = 0; index + 1 < arguments.Length; index++) + { + if (string.Equals(arguments[index], argumentName, StringComparison.Ordinal)) + return arguments[index + 1]; + } + return string.Empty; + } + + [MenuItem(CapturePlayViewMenu)] + private static void CapturePlayView() + { + CapturePlayView(1920, 1080, "chart-feature-radar-song-select.png"); + } + + [MenuItem(CapturePlayView4KMenu)] + private static void CapturePlayView4K() + { + CapturePlayView(3840, 2160, "chart-feature-radar-song-select-4k.png"); + } + + private static void CapturePlayView(int width, int height, string fileName) + { + if (!EditorApplication.isPlaying) + { + Debug.LogError("[ProjectQuint Visual QA] Enter Play Mode before capturing the play view."); + return; + } + + var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + var outputPath = Path.Combine( + projectRoot, + "_workspace", + fileName); + Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + MaximizeGameView(width, height); + ScreenCapture.CaptureScreenshot(outputPath, 1); + Debug.Log($"[ProjectQuint Visual QA] Play view capture requested: {outputPath}"); + } + + private static bool TryFindRunningApp(out ProjectQuintApp app) + { + app = null; + if (!EditorApplication.isPlaying) + { + Debug.LogError("[ProjectQuint Visual QA] Enter Play Mode before opening the preview."); + return false; + } + app = UnityEngine.Object.FindFirstObjectByType(); + if (app != null) + return true; + Debug.LogError("[ProjectQuint Visual QA] ProjectQuintApp is not running."); + return false; + } + + private static void MaximizeGameView(int width, int height) + { + var editorAssembly = typeof(EditorWindow).Assembly; + var gameViewType = editorAssembly.GetType("UnityEditor.GameView"); + if (gameViewType == null) + throw new InvalidOperationException("Unity GameView type is unavailable."); + var gameView = EditorWindow.GetWindow(gameViewType); + SelectFixedGameViewSize(editorAssembly, gameView, width, height); + gameView.maximized = true; + gameView.Repaint(); + } + + private static void SelectFixedGameViewSize( + Assembly editorAssembly, + EditorWindow gameView, + int width, + int height) + { + var sizesType = editorAssembly.GetType("UnityEditor.GameViewSizes"); + var groupType = editorAssembly.GetType("UnityEditor.GameViewSizeGroupType"); + var sizeType = editorAssembly.GetType("UnityEditor.GameViewSize"); + var kindType = editorAssembly.GetType("UnityEditor.GameViewSizeType"); + if (sizesType == null || groupType == null || sizeType == null || kindType == null) + throw new InvalidOperationException("Unity GameView size API is unavailable."); + + var singletonType = typeof(ScriptableSingleton<>).MakeGenericType(sizesType); + var instance = singletonType + .GetProperty("instance", BindingFlags.Public | BindingFlags.Static) + ?.GetValue(null); + var standalone = Enum.Parse(groupType, "Standalone"); + var group = sizesType + .GetMethod("GetGroup", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + ?.Invoke(instance, new[] { standalone }); + if (group == null) + throw new InvalidOperationException("Unity standalone GameView size group is unavailable."); + + var groupRuntimeType = group.GetType(); + var getTotalCount = groupRuntimeType.GetMethod( + "GetTotalCount", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var getSize = groupRuntimeType.GetMethod( + "GetGameViewSize", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var count = (int)getTotalCount.Invoke(group, null); + var selectedIndex = -1; + for (var index = 0; index < count; index++) + { + var size = getSize.Invoke(group, new object[] { index }); + var runtimeType = size.GetType(); + var candidateWidth = (int)runtimeType + .GetProperty("width", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(size); + var candidateHeight = (int)runtimeType + .GetProperty("height", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(size); + if (candidateWidth == width && candidateHeight == height) + { + selectedIndex = index; + break; + } + } + + if (selectedIndex < 0) + { + var fixedResolution = Enum.Parse(kindType, "FixedResolution"); + var size = Activator.CreateInstance( + sizeType, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + null, + new object[] { fixedResolution, width, height, "Project Quint Visual QA" }, + null); + groupRuntimeType + .GetMethod("AddCustomSize", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Invoke(group, new[] { size }); + selectedIndex = (int)getTotalCount.Invoke(group, null) - 1; + } + + gameView.GetType() + .GetProperty("selectedSizeIndex", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .SetValue(gameView, selectedIndex); + } + } +} diff --git a/Assets/ProjectQuint/Editor/ProjectQuintVisualQa.cs.meta b/Assets/ProjectQuint/Editor/ProjectQuintVisualQa.cs.meta new file mode 100644 index 0000000..8753f8b --- /dev/null +++ b/Assets/ProjectQuint/Editor/ProjectQuintVisualQa.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9d3df3121b0094dfc8d2b7b52f6be9b9 \ No newline at end of file diff --git a/Assets/ProjectQuint/Plugins.meta b/Assets/ProjectQuint/Plugins.meta new file mode 100644 index 0000000..6e77f00 --- /dev/null +++ b/Assets/ProjectQuint/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6248c2c2485984703b8814051c952fe3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Plugins/macOS.meta b/Assets/ProjectQuint/Plugins/macOS.meta new file mode 100644 index 0000000..503d755 --- /dev/null +++ b/Assets/ProjectQuint/Plugins/macOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 407742959808e452d95dc237db8e178b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle.meta b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle.meta new file mode 100644 index 0000000..46ed7d5 --- /dev/null +++ b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 529df0bdea17b4c449d335536cf75642 \ No newline at end of file diff --git a/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/Info.plist b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/Info.plist new file mode 100644 index 0000000..47620a9 --- /dev/null +++ b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ProjectQuintInput + CFBundleIdentifier + com.teamdenia.projectquint.input + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ProjectQuintInput + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0.0 + CFBundleVersion + 1 + + diff --git a/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/MacOS/ProjectQuintInput b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/MacOS/ProjectQuintInput new file mode 100755 index 0000000000000000000000000000000000000000..3eccb67c7baf576085177c1b9400a1db5f192238 GIT binary patch literal 135968 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 R6JP>NfC(@GCh#{B_+P+oS)%{| literal 0 HcmV?d00001 diff --git a/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/_CodeSignature/CodeResources b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000..d5d0fd7 --- /dev/null +++ b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintInput.bundle/Contents/_CodeSignature/CodeResources @@ -0,0 +1,115 @@ + + + + + files + + files2 + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle.meta b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle.meta new file mode 100644 index 0000000..7cd9848 --- /dev/null +++ b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c361e5c9916a44c1b72e671552512b9c diff --git a/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/Info.plist b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/Info.plist new file mode 100644 index 0000000..6069fca --- /dev/null +++ b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ProjectQuintUpdater + CFBundleIdentifier + com.teamdenia.projectquint.updater + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ProjectQuintUpdater + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0.0 + CFBundleVersion + 1 + + diff --git a/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/MacOS/ProjectQuintUpdater b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/MacOS/ProjectQuintUpdater new file mode 100755 index 0000000000000000000000000000000000000000..7dfb7a709f02cf2aa9dbfd430d6a58e0140385e9 GIT binary patch literal 136608 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 literal 0 HcmV?d00001 diff --git a/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/_CodeSignature/CodeResources b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000..d5d0fd7 --- /dev/null +++ b/Assets/ProjectQuint/Plugins/macOS/ProjectQuintUpdater.bundle/Contents/_CodeSignature/CodeResources @@ -0,0 +1,115 @@ + + + + + files + + files2 + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Assets/ProjectQuint/Runtime.meta b/Assets/ProjectQuint/Runtime.meta new file mode 100644 index 0000000..0e0ca24 --- /dev/null +++ b/Assets/ProjectQuint/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 77996964903b549a3ba6124b7f2e91ac +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core.meta b/Assets/ProjectQuint/Runtime/Core.meta new file mode 100644 index 0000000..69db8a8 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 17adf60357a0142419c85a863df17c9b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Analysis.meta b/Assets/ProjectQuint/Runtime/Core/Analysis.meta new file mode 100644 index 0000000..54a0f19 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Analysis.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fa19f129aed7947eba7998908f182719 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Analysis/ChartFeatureAnalysis.cs b/Assets/ProjectQuint/Runtime/Core/Analysis/ChartFeatureAnalysis.cs new file mode 100644 index 0000000..4e879f0 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Analysis/ChartFeatureAnalysis.cs @@ -0,0 +1,741 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Charts; + +namespace ProjectQuint.Core.Analysis +{ + public enum ChartFeatureAxis + { + Notes, + Peak, + Chord, + Long, + SofLan, + Tech, + } + + [Serializable] + public sealed class ChartFeatureVector + { + public const int CurrentAnalyzerVersion = 3; + public const int CurrentCalibrationVersion = 4; + public const float ReferenceValue = 100f; + public const float MaximumValue = 200f; + + public int AnalyzerVersion = CurrentAnalyzerVersion; + public int CalibrationVersion = CurrentCalibrationVersion; + public int KeyCount; + public int TotalLaneCount; + public int InputCount; + public int AnomalyCount; + public double ActiveDurationSeconds; + public double NotesRaw; + public double PeakRaw; + public double ChordRaw; + public double LongRaw; + public double SofLanRaw; + public double TechRaw; + public float Notes; + public float Peak; + public float Chord; + public float Long; + public float SofLan; + public float Tech; + + public bool IsCurrent => + AnalyzerVersion == CurrentAnalyzerVersion && + CalibrationVersion == CurrentCalibrationVersion; + + public float Value(ChartFeatureAxis axis) => axis switch + { + ChartFeatureAxis.Notes => Notes, + ChartFeatureAxis.Peak => Peak, + ChartFeatureAxis.Chord => Chord, + ChartFeatureAxis.Long => Long, + ChartFeatureAxis.SofLan => SofLan, + ChartFeatureAxis.Tech => Tech, + _ => 0f, + }; + } + + public static class ChartFeatureCalibration + { + public static ChartFeatureVector Apply(ChartFeatureVector vector) + { + if (vector == null) + throw new ArgumentNullException(nameof(vector)); + if (vector.AnalyzerVersion != ChartFeatureVector.CurrentAnalyzerVersion) + throw new InvalidOperationException("Raw chart features were produced by an incompatible analyzer."); + + vector.Notes = Normalize(ChartFeatureAxis.Notes, vector.KeyCount, vector.NotesRaw); + vector.Peak = Normalize(ChartFeatureAxis.Peak, vector.KeyCount, vector.PeakRaw); + vector.Chord = Normalize(ChartFeatureAxis.Chord, vector.KeyCount, vector.ChordRaw); + vector.Long = Normalize(ChartFeatureAxis.Long, vector.KeyCount, vector.LongRaw); + vector.SofLan = Normalize(ChartFeatureAxis.SofLan, vector.KeyCount, vector.SofLanRaw); + vector.Tech = Normalize(ChartFeatureAxis.Tech, vector.KeyCount, vector.TechRaw); + vector.CalibrationVersion = ChartFeatureVector.CurrentCalibrationVersion; + return vector; + } + + public static float Normalize(ChartFeatureAxis axis, int keyCount, double rawValue) + { + if (!(rawValue > 0) || double.IsNaN(rawValue) || double.IsInfinity(rawValue)) + return 0f; + + keyCount = Math.Max(4, Math.Min(7, keyCount)); + var laneScale = keyCount / 5.0; + var notesScale = keyCount switch + { + 4 => 6.0 / 7.0, + 6 => 8.0 / 7.0, + 7 => 9.0 / 7.0, + _ => 1.0, + }; + var peakScale = 1.0 + 0.125 * (keyCount - 5); + double first; + double second; + double third; + double fourth; + double fifth; + double sixth; + switch (axis) + { + case ChartFeatureAxis.Notes: + (first, second, third, fourth, fifth, sixth) = + Scale((300.0, 600.0, 865.0, 1065.0, 1260.0, 1900.0), notesScale); + break; + case ChartFeatureAxis.Peak: + (first, second, third, fourth, fifth, sixth) = + Scale((9.5, 17.1, 23.9, 29.3, 34.8, 48.0), peakScale); + break; + case ChartFeatureAxis.Chord: + (first, second, third, fourth, fifth, sixth) = + Scale((36.0, 153.0, 317.0, 461.0, 579.0, 1500.0), laneScale); + break; + case ChartFeatureAxis.Long: + (first, second, third, fourth, fifth, sixth) = + (0.005, 0.03, 0.10, 0.20, 0.30, 0.60); + break; + case ChartFeatureAxis.SofLan: + (first, second, third, fourth, fifth, sixth) = + (0.15, 1.0, 6.0, 15.0, 30.0, 150.0); + break; + default: + (first, second, third, fourth, fifth, sixth) = + Scale((8.0, 21.0, 36.0, 67.0, 111.0, 200.0), laneScale); + break; + } + + return (float)Piecewise(rawValue, first, second, third, fourth, fifth, sixth); + } + + private static (double, double, double, double, double, double) Scale( + (double, double, double, double, double, double) values, + double scale) => + (values.Item1 * scale, + values.Item2 * scale, + values.Item3 * scale, + values.Item4 * scale, + values.Item5 * scale, + values.Item6 * scale); + + private static double Piecewise( + double value, + double first, + double second, + double third, + double fourth, + double fifth, + double sixth) + { + const double step = ChartFeatureVector.MaximumValue / 6.0; + if (value <= first) + return step * value / first; + if (value <= second) + return step + step * (value - first) / (second - first); + if (value <= third) + return step * 2.0 + step * (value - second) / (third - second); + if (value <= fourth) + return step * 3.0 + step * (value - third) / (fourth - third); + if (value <= fifth) + return step * 4.0 + step * (value - fourth) / (fifth - fourth); + if (value <= sixth) + return step * 5.0 + step * (value - fifth) / (sixth - fifth); + return ChartFeatureVector.MaximumValue; + } + } + + public static class ChartFeatureAnalyzer + { + private const double SimultaneousEpsilonSeconds = 0.0005; + private const double MinimumActiveDurationSeconds = 15.0; + private const double PeakSampleStepSeconds = 0.050; + private const double SofLanEventBeatEpsilon = 0.000000001; + private const double SofLanEffectiveSpeedTolerance = 0.01; + + public static ChartFeatureVector Analyze(PlayableChart chart) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + + var keyCount = Math.Max(4, Math.Min(7, chart.Metadata?.KeyCount ?? 5)); + var totalLaneCount = Math.Max(1, chart.Metadata?.TotalLaneCount ?? keyCount); + var groups = BuildOnsetGroups(chart.Notes, totalLaneCount, out var uniqueNotes, out var anomalies); + if (groups.Count == 0) + { + return new ChartFeatureVector + { + KeyCount = keyCount, + TotalLaneCount = totalLaneCount, + AnomalyCount = anomalies, + ActiveDurationSeconds = MinimumActiveDurationSeconds, + }; + } + + var activeStart = groups[0].Time; + var activeEnd = activeStart; + foreach (var note in uniqueNotes) + activeEnd = Math.Max(activeEnd, note.IsLong ? note.EndTime : note.StartTime); + var activeDuration = Math.Max(MinimumActiveDurationSeconds, activeEnd - activeStart); + activeEnd = activeStart + activeDuration; + var inputCount = 0; + foreach (var group in groups) + inputCount += group.Count; + + var notesRaw = 60.0 * inputCount / activeDuration; + var peakRaw = CalculatePeak(groups, activeStart, activeEnd); + var chordRaw = CalculateChord(groups, activeDuration, totalLaneCount); + var longRaw = CalculateLong(uniqueNotes, groups, activeStart, activeEnd, totalLaneCount); + var sofLanRaw = CalculateSofLan(chart, groups, activeDuration, inputCount); + var techRaw = CalculateTech( + uniqueNotes, + groups, + activeDuration, + keyCount, + chart.Metadata?.HasScratch == true); + + return ChartFeatureCalibration.Apply(new ChartFeatureVector + { + KeyCount = keyCount, + TotalLaneCount = totalLaneCount, + InputCount = inputCount, + AnomalyCount = anomalies, + ActiveDurationSeconds = activeDuration, + NotesRaw = notesRaw, + PeakRaw = peakRaw, + ChordRaw = chordRaw, + LongRaw = longRaw, + SofLanRaw = sofLanRaw, + TechRaw = techRaw, + }); + } + + private static List BuildOnsetGroups( + IReadOnlyList source, + int totalLaneCount, + out List uniqueNotes, + out int anomalyCount) + { + var sorted = new List(); + if (source != null) + { + foreach (var note in source) + { + if (note == null || !IsFinite(note.StartTime)) + continue; + sorted.Add(note); + } + } + sorted.Sort((left, right) => + { + var time = left.StartTime.CompareTo(right.StartTime); + return time != 0 ? time : left.Lane.CompareTo(right.Lane); + }); + + uniqueNotes = new List(sorted.Count); + var groups = new List(); + anomalyCount = 0; + OnsetGroup current = null; + foreach (var note in sorted) + { + if (note.Lane < 0 || note.Lane >= totalLaneCount) + { + anomalyCount++; + continue; + } + if (current == null || note.StartTime - current.Time > SimultaneousEpsilonSeconds) + { + current = new OnsetGroup(note.StartTime); + groups.Add(current); + } + + var laneBit = 1 << note.Lane; + if ((current.LaneMask & laneBit) != 0) + { + anomalyCount++; + continue; + } + + current.LaneMask |= laneBit; + current.Count++; + if (note.IsScratch) + { + current.HasScratch = true; + } + else + { + current.KeyLaneMask |= laneBit; + current.KeyCount++; + } + uniqueNotes.Add(note); + } + return groups; + } + + private static double CalculatePeak( + IReadOnlyList groups, + double activeStart, + double activeEnd) + { + var sustained = RollingNpsQuantile(groups, activeStart, activeEnd, 2.0, 0.98); + var burst = RollingNpsQuantile(groups, activeStart, activeEnd, 0.5, 0.99); + return sustained * 0.60 + burst * 0.40; + } + + private static double RollingNpsQuantile( + IReadOnlyList groups, + double activeStart, + double activeEnd, + double windowSeconds, + double quantile) + { + var sampleCount = Math.Max(1, + (int)Math.Ceiling((activeEnd - activeStart) / PeakSampleStepSeconds)); + var values = new double[sampleCount]; + var left = 0; + var right = 0; + var count = 0; + for (var sample = 0; sample < sampleCount; sample++) + { + var windowStart = activeStart + sample * PeakSampleStepSeconds; + var windowEnd = windowStart + windowSeconds; + while (left < groups.Count && groups[left].Time < windowStart - SimultaneousEpsilonSeconds) + count -= groups[left++].Count; + while (right < groups.Count && groups[right].Time < windowEnd - SimultaneousEpsilonSeconds) + count += groups[right++].Count; + values[sample] = count / windowSeconds; + } + + Array.Sort(values); + var index = Math.Max(0, Math.Min(values.Length - 1, + (int)Math.Ceiling(values.Length * quantile) - 1)); + return values[index]; + } + + private static double CalculateChord( + IReadOnlyList groups, + double activeDuration, + int laneCount) + { + var chordLoad = 0.0; + var transitionLoad = 0.0; + OnsetGroup previousChord = null; + foreach (var group in groups) + { + if (group.Count < 2) + continue; + chordLoad += Math.Pow(group.Count - 1, 1.35); + if (previousChord != null) + { + var interval = group.Time - previousChord.Time; + if (interval >= 0 && interval <= 0.6) + { + var changedRatio = PopCount(group.LaneMask ^ previousChord.LaneMask) / + (double)Math.Max(1, laneCount); + transitionLoad += changedRatio * Math.Exp(-interval / 0.180); + } + } + previousChord = group; + } + + var perMinute = 60.0 / activeDuration; + return 0.75 * chordLoad * perMinute + 0.25 * transitionLoad * perMinute; + } + + private static double CalculateLong( + IReadOnlyList notes, + IReadOnlyList groups, + double activeStart, + double activeEnd, + int laneCount) + { + var byLane = new List[laneCount]; + for (var lane = 0; lane < laneCount; lane++) + byLane[lane] = new List(); + foreach (var note in notes) + { + if (!note.IsLong || note.Lane < 0 || note.Lane >= laneCount || !IsFinite(note.EndTime)) + continue; + var start = Math.Max(activeStart, note.StartTime); + var end = Math.Min(activeEnd, note.EndTime); + if (end > start + SimultaneousEpsilonSeconds) + byLane[note.Lane].Add(new HoldInterval(start, end)); + } + + var merged = new List(); + var heldLaneSeconds = 0.0; + foreach (var intervals in byLane) + { + intervals.Sort((left, right) => left.Start.CompareTo(right.Start)); + for (var index = 0; index < intervals.Count; index++) + { + var start = intervals[index].Start; + var end = intervals[index].End; + while (index + 1 < intervals.Count && + intervals[index + 1].Start <= end + SimultaneousEpsilonSeconds) + { + index++; + end = Math.Max(end, intervals[index].End); + } + var interval = new HoldInterval(start, end); + merged.Add(interval); + heldLaneSeconds += end - start; + } + } + if (merged.Count == 0) + return 0; + + var events = new List(merged.Count * 2); + foreach (var interval in merged) + { + events.Add(new HoldSweepEvent(interval.Start, 1)); + events.Add(new HoldSweepEvent(interval.End, -1)); + } + events.Sort((left, right) => + { + var time = left.Time.CompareTo(right.Time); + return time != 0 ? time : left.Delta.CompareTo(right.Delta); + }); + + var held = 0; + var previousTime = activeStart; + var extraHeldSeconds = 0.0; + foreach (var item in events) + { + var time = Math.Max(activeStart, Math.Min(activeEnd, item.Time)); + if (time > previousTime) + extraHeldSeconds += Math.Max(0, held - 1) * (time - previousTime); + held += item.Delta; + previousTime = time; + } + + var overlayGroups = 0; + held = 0; + var eventIndex = 0; + foreach (var group in groups) + { + while (eventIndex < events.Count && + events[eventIndex].Time < group.Time - SimultaneousEpsilonSeconds) + held += events[eventIndex++].Delta; + if (held > 0) + overlayGroups++; + while (eventIndex < events.Count && + events[eventIndex].Time <= group.Time + SimultaneousEpsilonSeconds) + held += events[eventIndex++].Delta; + } + + var duration = activeEnd - activeStart; + var occupancy = heldLaneSeconds / (duration * Math.Max(1, laneCount)); + var concurrency = extraHeldSeconds / + (duration * Math.Max(1, laneCount - 1)); + var overlay = overlayGroups / (double)Math.Max(1, groups.Count); + return 0.45 * Clamp01(occupancy) + + 0.25 * Clamp01(concurrency) + + 0.30 * Clamp01(overlay); + } + + private static double CalculateSofLan( + PlayableChart chart, + IReadOnlyList groups, + double activeDuration, + int inputCount) + { + var nodes = chart.TempoMap?.Nodes; + if (nodes == null || nodes.Count == 0 || inputCount <= 0) + return 0; + + var meanNps = inputCount / activeDuration; + var burden = 0.0; + for (var index = 0; index < nodes.Count; index++) + { + var node = nodes[index]; + if (node.StopDuration > 0) + { + var localNps = CountInputs( + groups, + node.StopEndTime, + node.StopEndTime + 2.0) / 2.0; + burden += Math.Min(2.0, node.StopDuration) * Exposure(localNps, meanNps); + } + } + + var scrollNodes = chart.ScrollMap?.Nodes; + var currentBpm = nodes[0].Bpm; + var currentScroll = scrollNodes != null && scrollNodes.Count > 0 + ? scrollNodes[0].Multiplier + : 1.0; + var tempoIndex = 1; + var scrollIndex = scrollNodes != null && scrollNodes.Count > 0 ? 1 : 0; + // BPM and BMS #SCROLL can change at the same beat to deliberately + // preserve visual speed. Merge those events before measuring the + // transition so an inverse pair is not counted twice. + while (tempoIndex < nodes.Count || + (scrollNodes != null && scrollIndex < scrollNodes.Count)) + { + var tempoBeat = tempoIndex < nodes.Count + ? nodes[tempoIndex].Beat + : double.PositiveInfinity; + var scrollBeat = scrollNodes != null && scrollIndex < scrollNodes.Count + ? scrollNodes[scrollIndex].Beat + : double.PositiveInfinity; + var eventBeat = Math.Min(tempoBeat, scrollBeat); + var previousBpm = currentBpm; + var previousScroll = currentScroll; + + while (tempoIndex < nodes.Count && + Math.Abs(nodes[tempoIndex].Beat - eventBeat) <= SofLanEventBeatEpsilon) + { + currentBpm = nodes[tempoIndex].Bpm; + tempoIndex++; + } + while (scrollNodes != null && + scrollIndex < scrollNodes.Count && + Math.Abs(scrollNodes[scrollIndex].Beat - eventBeat) <= + SofLanEventBeatEpsilon) + { + currentScroll = scrollNodes[scrollIndex].Multiplier; + scrollIndex++; + } + + var change = EffectiveScrollSpeedChange( + previousBpm, + previousScroll, + currentBpm, + currentScroll); + if (change > 0) + { + var eventTime = chart.TempoMap.BeatToTime(eventBeat); + var localNps = CountInputs(groups, eventTime - 1.0, eventTime + 2.0) / 3.0; + burden += Math.Min(4.0, change) * Exposure(localNps, meanNps); + } + } + return burden * 60.0 / activeDuration; + } + + private static double EffectiveScrollSpeedChange( + double previousBpm, + double previousScroll, + double currentBpm, + double currentScroll) + { + const double zeroEpsilon = 0.000001; + var previousSpeed = previousBpm * previousScroll; + var currentSpeed = currentBpm * currentScroll; + var previousIsZero = Math.Abs(previousSpeed) <= zeroEpsilon; + var currentIsZero = Math.Abs(currentSpeed) <= zeroEpsilon; + + if (previousIsZero && currentIsZero) + return 0; + if (previousIsZero || currentIsZero) + return 2.5; + if (Math.Sign(previousSpeed) != Math.Sign(currentSpeed)) + return 4.0; + + var ratio = Math.Abs(currentSpeed / previousSpeed); + if (!(ratio > 0) || double.IsNaN(ratio) || double.IsInfinity(ratio)) + return 4.0; + if (ratio >= 1.0 / (1.0 + SofLanEffectiveSpeedTolerance) && + ratio <= 1.0 + SofLanEffectiveSpeedTolerance) + return 0; + return Math.Abs(Log2(ratio)); + } + + private static double CalculateTech( + IReadOnlyList notes, + IReadOnlyList groups, + double activeDuration, + int keyCount, + bool hasScratch) + { + var laneTimes = new Dictionary(); + var jack = 0.0; + foreach (var note in notes) + { + if (laneTimes.TryGetValue(note.Lane, out var previous)) + { + var interval = note.StartTime - previous; + if (interval >= 0 && interval <= 0.200) + jack += 1.0 - interval / 0.200; + } + laneTimes[note.Lane] = note.StartTime; + } + + var trill = 0.0; + for (var index = 3; index < groups.Count; index++) + { + var a = groups[index - 3]; + var b = groups[index - 2]; + var c = groups[index - 1]; + var d = groups[index]; + if (a.Count != 1 || b.Count != 1 || c.Count != 1 || d.Count != 1 || + a.HasScratch || b.HasScratch || c.HasScratch || d.HasScratch || + a.LaneMask != c.LaneMask || b.LaneMask != d.LaneMask || a.LaneMask == b.LaneMask) + continue; + var first = b.Time - a.Time; + var second = c.Time - b.Time; + var third = d.Time - c.Time; + if (first <= 0 || second <= 0 || third <= 0 || + first > 0.250 || second > 0.250 || third > 0.250) + continue; + trill += Math.Exp(-((first + second + third) / 3.0) / 0.150); + } + + var movement = 0.0; + OnsetGroup previousKeys = null; + foreach (var group in groups) + { + if (group.KeyCount <= 0) + continue; + if (previousKeys != null) + { + var interval = group.Time - previousKeys.Time; + if (interval >= 0 && interval <= 0.300) + { + var distance = Math.Abs( + KeyCenter(group.KeyLaneMask, keyCount, hasScratch) - + KeyCenter(previousKeys.KeyLaneMask, keyCount, hasScratch)); + movement += distance * Math.Exp(-interval / 0.180); + } + } + previousKeys = group; + } + + var scratchCross = 0.0; + for (var index = 0; index < groups.Count; index++) + { + if (!groups[index].HasScratch) + continue; + var maximumNearbyKeys = 0; + for (var other = index - 1; other >= 0; other--) + { + if (groups[index].Time - groups[other].Time > 0.150) + break; + maximumNearbyKeys = Math.Max(maximumNearbyKeys, groups[other].KeyCount); + } + for (var other = index + 1; other < groups.Count; other++) + { + if (groups[other].Time - groups[index].Time > 0.150) + break; + maximumNearbyKeys = Math.Max(maximumNearbyKeys, groups[other].KeyCount); + } + maximumNearbyKeys = Math.Max(maximumNearbyKeys, groups[index].KeyCount); + scratchCross += maximumNearbyKeys; + } + + var perMinute = 60.0 / activeDuration; + return 0.35 * jack * perMinute + + 0.30 * trill * perMinute + + 0.20 * movement * perMinute + + 0.15 * scratchCross * perMinute; + } + + private static double KeyCenter(int laneMask, int keyCount, bool hasScratch) + { + var offset = hasScratch ? 1 : 0; + var total = 0.0; + var count = 0; + for (var key = 0; key < keyCount; key++) + { + if ((laneMask & (1 << (key + offset))) == 0) + continue; + total += key / (double)Math.Max(1, keyCount - 1); + count++; + } + return count > 0 ? total / count : 0.5; + } + + private static int CountInputs(IReadOnlyList groups, double start, double end) + { + var count = 0; + foreach (var group in groups) + { + if (group.Time < start) + continue; + if (group.Time >= end) + break; + count += group.Count; + } + return count; + } + + private static double Exposure(double localNps, double meanNps) + { + if (!(meanNps > 0)) + return 0.25; + var ratio = Math.Max(0, Math.Min(3.0, localNps / meanNps)); + return 0.25 + 0.75 * ratio; + } + + private static int PopCount(int value) + { + var count = 0; + var bits = (uint)value; + while (bits != 0) + { + bits &= bits - 1; + count++; + } + return count; + } + + private static double Clamp01(double value) => Math.Max(0, Math.Min(1, value)); + private static double Log2(double value) => Math.Log(value) / Math.Log(2.0); + private static bool IsFinite(double value) => !double.IsNaN(value) && !double.IsInfinity(value); + + private sealed class OnsetGroup + { + public OnsetGroup(double time) => Time = time; + + public double Time; + public int LaneMask; + public int KeyLaneMask; + public int Count; + public int KeyCount; + public bool HasScratch; + } + + private readonly struct HoldInterval + { + public HoldInterval(double start, double end) + { + Start = start; + End = end; + } + + public double Start { get; } + public double End { get; } + } + + private readonly struct HoldSweepEvent + { + public HoldSweepEvent(double time, int delta) + { + Time = time; + Delta = delta; + } + + public double Time { get; } + public int Delta { get; } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Analysis/ChartFeatureAnalysis.cs.meta b/Assets/ProjectQuint/Runtime/Core/Analysis/ChartFeatureAnalysis.cs.meta new file mode 100644 index 0000000..5f74070 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Analysis/ChartFeatureAnalysis.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b0f6f0d468cb04ff397643fcc59325e7 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Bms.meta b/Assets/ProjectQuint/Runtime/Core/Bms.meta new file mode 100644 index 0000000..6354d55 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Bms.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6196c448b624e48dbbc87bf93d7c2273 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Bms/BmsModels.cs b/Assets/ProjectQuint/Runtime/Core/Bms/BmsModels.cs new file mode 100644 index 0000000..ac8d278 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Bms/BmsModels.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Timing; + +namespace ProjectQuint.Core.Bms +{ + public readonly struct BmsFraction : IEquatable, IComparable + { + public BmsFraction(long numerator, long denominator) + { + if (denominator <= 0) + throw new ArgumentOutOfRangeException(nameof(denominator)); + + var divisor = GreatestCommonDivisor(Math.Abs(numerator), denominator); + Numerator = numerator / divisor; + Denominator = denominator / divisor; + } + + public long Numerator { get; } + public long Denominator { get; } + public double Value => (double)Numerator / Denominator; + + public int CompareTo(BmsFraction other) + { + var left = (decimal)Numerator * other.Denominator; + var right = (decimal)other.Numerator * Denominator; + return left.CompareTo(right); + } + + public bool Equals(BmsFraction other) => + Numerator == other.Numerator && Denominator == other.Denominator; + + public override bool Equals(object obj) => obj is BmsFraction other && Equals(other); + public override int GetHashCode() => HashCode.Combine(Numerator, Denominator); + + private static long GreatestCommonDivisor(long left, long right) + { + while (right != 0) + { + var remainder = left % right; + left = right; + right = remainder; + } + + return left == 0 ? 1 : left; + } + } + + public readonly struct BmsPosition : IEquatable, IComparable + { + public BmsPosition(int measure, BmsFraction fraction) + { + Measure = measure; + Fraction = fraction; + } + + public int Measure { get; } + public BmsFraction Fraction { get; } + + public int CompareTo(BmsPosition other) + { + var measureComparison = Measure.CompareTo(other.Measure); + return measureComparison != 0 ? measureComparison : Fraction.CompareTo(other.Fraction); + } + + public bool Equals(BmsPosition other) => Measure == other.Measure && Fraction.Equals(other.Fraction); + public override bool Equals(object obj) => obj is BmsPosition other && Equals(other); + public override int GetHashCode() => HashCode.Combine(Measure, Fraction); + } + + public sealed class BmsMetadata + { + public string ChartPath { get; set; } = string.Empty; + public string Title { get; set; } = "Untitled"; + public string Subtitle { get; set; } = string.Empty; + public string Artist { get; set; } = "Unknown Artist"; + public string SubArtist { get; set; } = string.Empty; + public string Genre { get; set; } = string.Empty; + public string StageFile { get; set; } = string.Empty; + public string BackBmp { get; set; } = string.Empty; + public string Md5 { get; set; } = string.Empty; + public string Sha256 { get; set; } = string.Empty; + public double InitialBpm { get; set; } = 120.0; + public double MinimumBpm { get; set; } + public double MaximumBpm { get; set; } + public double Total { get; set; } + public int PlayLevel { get; set; } + public int Difficulty { get; set; } + public int Rank { get; set; } = 3; + public int LnType { get; set; } = 1; + public string LnObj { get; set; } = string.Empty; + public int ObjectBase { get; set; } = 36; + public int KeyCount { get; set; } = 5; + public bool HasScratch { get; set; } + public int UsedKeyChannelMask { get; set; } + } + + public sealed class BmsRawEvent + { + public BmsPosition Position { get; set; } + public string Channel { get; set; } = string.Empty; + public string ObjectId { get; set; } = string.Empty; + public int Sequence { get; set; } + } + + public sealed class BmsDocument + { + public BmsMetadata Metadata { get; } = new BmsMetadata(); + public Dictionary WavDefinitions { get; } = new Dictionary(StringComparer.Ordinal); + public Dictionary BmpDefinitions { get; } = new Dictionary(StringComparer.Ordinal); + public Dictionary ExtendedBpms { get; } = new Dictionary(StringComparer.Ordinal); + public Dictionary Stops { get; } = new Dictionary(StringComparer.Ordinal); + public Dictionary Scrolls { get; } = new Dictionary(StringComparer.Ordinal); + public Dictionary MeasureLengths { get; } = new Dictionary(); + public List Events { get; } = new List(); + public List Warnings { get; } = new List(); + } + + public sealed class BmsNote + { + public int Id { get; set; } + public int Lane { get; set; } + public double StartTime { get; set; } + public double EndTime { get; set; } + public double StartBeat { get; set; } = double.NaN; + public double EndBeat { get; set; } = double.NaN; + public string SoundId { get; set; } = string.Empty; + public string TailSoundId { get; set; } = string.Empty; + public bool IsScratch { get; set; } + public bool IsLong => EndTime > StartTime; + } + + public sealed class BmsAudioEvent + { + public double Time { get; set; } + public string SoundId { get; set; } = string.Empty; + } + + public sealed class BmsLaneSoundEvent + { + public int Lane { get; set; } + public double Time { get; set; } + public string SoundId { get; set; } = string.Empty; + } + + public enum BmsBgaLayer + { + Base, + Poor, + Layer, + } + + public sealed class BmsBgaEvent + { + public double Time { get; set; } + public string ResourceId { get; set; } = string.Empty; + public BmsBgaLayer Layer { get; set; } + } + + public sealed class BmsChart + { + public BmsMetadata Metadata { get; set; } = new BmsMetadata(); + public TempoMap TempoMap { get; set; } + public ScrollMap ScrollMap { get; set; } + public Dictionary WavDefinitions { get; set; } = new Dictionary(StringComparer.Ordinal); + public Dictionary BmpDefinitions { get; set; } = new Dictionary(StringComparer.Ordinal); + public List Notes { get; } = new List(); + public List BackgroundSounds { get; } = new List(); + public List LaneSoundEvents { get; } = new List(); + public List BgaEvents { get; } = new List(); + public List Warnings { get; } = new List(); + public int ScratchObjectCount { get; set; } + public double Duration { get; set; } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Bms/BmsModels.cs.meta b/Assets/ProjectQuint/Runtime/Core/Bms/BmsModels.cs.meta new file mode 100644 index 0000000..3952827 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Bms/BmsModels.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8b835aeba53ee44eca669ba79b1f4fad \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Bms/BmsParser.cs b/Assets/ProjectQuint/Runtime/Core/Bms/BmsParser.cs new file mode 100644 index 0000000..a9599b3 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Bms/BmsParser.cs @@ -0,0 +1,588 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +namespace ProjectQuint.Core.Bms +{ + public static class BmsParser + { + private static readonly Encoding ShiftJisEncoding = CreateShiftJisEncoding(); + + public static BmsDocument ParseFile(string chartPath, int randomSeed = 1, int forcedKeyCount = 0) + { + if (string.IsNullOrWhiteSpace(chartPath)) + throw new ArgumentException("Chart path is required.", nameof(chartPath)); + + var bytes = File.ReadAllBytes(chartPath); + var document = ParseText(chartPath, Decode(bytes), randomSeed, true, forcedKeyCount); + return document; + } + + public static BmsMetadata ReadMetadataFile(string chartPath, int randomSeed = 1, int forcedKeyCount = 0) + { + var bytes = File.ReadAllBytes(chartPath); + return ParseText(chartPath, Decode(bytes), randomSeed, false, forcedKeyCount).Metadata; + } + + public static BmsDocument ParseText(string chartPath, string text, int randomSeed = 1) + { + return ParseText(chartPath, text, randomSeed, true, 0); + } + + private static BmsDocument ParseText( + string chartPath, + string text, + int randomSeed, + bool includeEvents, + int forcedKeyCount) + { + var document = new BmsDocument(); + document.Metadata.ChartPath = chartPath ?? string.Empty; + + var processor = new ConditionalProcessor(randomSeed); + var sequence = 0; + var lines = (text ?? string.Empty) + .Replace("\r\n", "\n") + .Replace('\r', '\n') + .Split('\n'); + document.Metadata.ObjectBase = ResolveObjectBase(lines, randomSeed); + + foreach (var originalLine in lines) + { + var line = originalLine.Trim(); + if (line.Length == 0 || line[0] != '#') + continue; + + if (processor.TryHandle(line)) + continue; + + if (!processor.IsActive) + continue; + + if (TryParseMeasureLine(line, document, includeEvents, ref sequence)) + continue; + + ParseHeaderLine(line, document); + } + + if (processor.HasOpenCondition) + AddWarning(document, "Conditional block was not closed before EOF."); + + FinalizeKeyLayout(document.Metadata, forcedKeyCount); + + return document; + } + + private static bool TryParseMeasureLine(string line, BmsDocument document, bool includeEvents, ref int sequence) + { + if (line.Length < 7 || line[6] != ':' || + !char.IsDigit(line[1]) || !char.IsDigit(line[2]) || !char.IsDigit(line[3])) + return false; + + if (!int.TryParse(line.Substring(1, 3), NumberStyles.None, CultureInfo.InvariantCulture, out var measure)) + return false; + + var channel = line.Substring(4, 2).ToUpperInvariant(); + var payload = line.Substring(7).Replace(" ", string.Empty).Replace("\t", string.Empty); + + ObservePlayableChannel(document.Metadata, channel, payload); + + if (channel == "02") + { + if (double.TryParse(payload, NumberStyles.Float, CultureInfo.InvariantCulture, out var length) && length > 0) + document.MeasureLengths[measure] = length; + else + AddWarning(document, $"Invalid measure length at #{measure:000}02."); + return true; + } + + if (!includeEvents) + return true; + + if ((payload.Length & 1) != 0) + { + AddWarning(document, $"Odd object data length at #{measure:000}{channel}."); + return true; + } + + var resolution = payload.Length / 2; + if (resolution == 0) + return true; + + for (var index = 0; index < resolution; index++) + { + var rawObjectId = payload.Substring(index * 2, 2); + if (!TryNormalizeObjectId(rawObjectId, document.Metadata.ObjectBase, out var objectId)) + { + AddWarning( + document, + $"Invalid base-{document.Metadata.ObjectBase} object id '{rawObjectId}' at " + + $"#{measure:000}{channel}."); + continue; + } + if (objectId == "00") + continue; + + document.Events.Add(new BmsRawEvent + { + Position = new BmsPosition(measure, new BmsFraction(index, resolution)), + Channel = channel, + ObjectId = objectId, + Sequence = sequence++, + }); + } + + return true; + } + + private static void ParseHeaderLine(string line, BmsDocument document) + { + var separator = FindWhitespace(line); + var rawCommand = separator < 0 ? line.Substring(1) : line.Substring(1, separator - 1); + var command = rawCommand.ToUpperInvariant(); + var value = separator < 0 ? string.Empty : line.Substring(separator).Trim(); + + if (command.StartsWith("WAV", StringComparison.Ordinal) && command.Length == 5) + { + AddObjectDefinition( + document, + document.WavDefinitions, + rawCommand.Substring(3, 2), + value, + "WAV"); + return; + } + + if (command.StartsWith("BMP", StringComparison.Ordinal) && command.Length == 5) + { + AddObjectDefinition( + document, + document.BmpDefinitions, + rawCommand.Substring(3, 2), + value, + "BMP"); + return; + } + + if (command.StartsWith("BPM", StringComparison.Ordinal) && command.Length == 5) + { + if (!TryNormalizeObjectId( + rawCommand.Substring(3, 2), + document.Metadata.ObjectBase, + out var objectId)) + AddWarning(document, $"Invalid base-{document.Metadata.ObjectBase} BPM object id: {rawCommand}"); + else if (TryParsePositiveDouble(value, out var bpm)) + document.ExtendedBpms[objectId] = bpm; + else + AddWarning(document, $"Invalid extended BPM: {rawCommand} {value}"); + return; + } + + if (command.StartsWith("STOP", StringComparison.Ordinal) && command.Length == 6) + { + if (!TryNormalizeObjectId( + rawCommand.Substring(4, 2), + document.Metadata.ObjectBase, + out var objectId)) + AddWarning(document, $"Invalid base-{document.Metadata.ObjectBase} STOP object id: {rawCommand}"); + else if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var stop) && stop >= 0) + document.Stops[objectId] = stop; + else + AddWarning(document, $"Invalid STOP definition: {rawCommand} {value}"); + return; + } + + if (command.StartsWith("SCROLL", StringComparison.Ordinal) && command.Length == 8) + { + if (!TryNormalizeObjectId( + rawCommand.Substring(6, 2), + document.Metadata.ObjectBase, + out var objectId)) + AddWarning(document, $"Invalid base-{document.Metadata.ObjectBase} SCROLL object id: {rawCommand}"); + else if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var scroll) && + !double.IsNaN(scroll) && !double.IsInfinity(scroll)) + document.Scrolls[objectId] = scroll; + else + AddWarning(document, $"Invalid SCROLL definition: {rawCommand} {value}"); + return; + } + + switch (command) + { + case "TITLE": document.Metadata.Title = EmptyFallback(value, "Untitled"); break; + case "SUBTITLE": document.Metadata.Subtitle = value; break; + case "ARTIST": document.Metadata.Artist = EmptyFallback(value, "Unknown Artist"); break; + case "SUBARTIST": document.Metadata.SubArtist = value; break; + case "GENRE": document.Metadata.Genre = value; break; + case "STAGEFILE": document.Metadata.StageFile = value; break; + case "BACKBMP": document.Metadata.BackBmp = value; break; + case "LNOBJ": + if (TryNormalizeObjectId(value, document.Metadata.ObjectBase, out var lnObject)) + document.Metadata.LnObj = lnObject; + else + AddWarning(document, $"Invalid base-{document.Metadata.ObjectBase} LNOBJ id: {value}"); + break; + case "BPM": + if (TryParsePositiveDouble(value, out var bpm)) document.Metadata.InitialBpm = bpm; + else AddWarning(document, $"Invalid initial BPM: {value}"); + break; + case "TOTAL": + if (TryParsePositiveDouble(value, out var total)) document.Metadata.Total = total; + else AddWarning(document, $"Invalid TOTAL: {value}"); + break; + case "PLAYLEVEL": document.Metadata.PlayLevel = ParseLeadingInteger(value); break; + case "DIFFICULTY": document.Metadata.Difficulty = ParseLeadingInteger(value); break; + case "RANK": document.Metadata.Rank = ParseLeadingInteger(value); break; + case "LNTYPE": + document.Metadata.LnType = ParseLeadingInteger(value); + EnsureSupportedLongNoteNotation(document.Metadata.LnType); + break; + case "SCROLL": AddWarning(document, "SCROLL requires a two-character object id."); break; + case "BASE": break; + } + } + + public static void EnsureSupportedLongNoteNotation(int lnType) + { + if (lnType == 2) + throw new FormatException( + "Unsupported BMS #LNTYPE 2 (obsolete MGQ long-note notation). " + + "ProjectQuint supports #LNTYPE 1 and #LNOBJ charts."); + } + + private static int ResolveObjectBase(IReadOnlyList lines, int randomSeed) + { + var objectBase = 36; + var processor = new ConditionalProcessor(randomSeed); + foreach (var originalLine in lines) + { + var line = (originalLine ?? string.Empty).Trim(); + if (line.Length == 0 || line[0] != '#') + continue; + if (processor.TryHandle(line) || !processor.IsActive) + continue; + + var separator = FindWhitespace(line); + var command = (separator < 0 + ? line.Substring(1) + : line.Substring(1, separator - 1)) + .ToUpperInvariant(); + if (command != "BASE") + continue; + + var value = separator < 0 ? string.Empty : line.Substring(separator).Trim(); + if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out objectBase) || + (objectBase != 36 && objectBase != 62)) + throw new FormatException( + $"Unsupported BMS #BASE '{value}'. ProjectQuint supports only #BASE 36 and #BASE 62."); + } + return objectBase; + } + + private static void AddObjectDefinition( + BmsDocument document, + IDictionary definitions, + string rawObjectId, + string value, + string definitionType) + { + if (TryNormalizeObjectId( + rawObjectId, + document.Metadata.ObjectBase, + out var objectId)) + definitions[objectId] = value; + else + AddWarning( + document, + $"Invalid base-{document.Metadata.ObjectBase} {definitionType} object id: {rawObjectId}"); + } + + private static bool TryNormalizeObjectId(string rawObjectId, int objectBase, out string objectId) + { + objectId = string.Empty; + if (string.IsNullOrEmpty(rawObjectId) || rawObjectId.Length != 2) + return false; + + for (var index = 0; index < rawObjectId.Length; index++) + { + var character = rawObjectId[index]; + if (!(character >= '0' && character <= '9') && + !(character >= 'A' && character <= 'Z') && + !(character >= 'a' && character <= 'z')) + return false; + } + + objectId = objectBase == 36 ? rawObjectId.ToUpperInvariant() : rawObjectId; + return objectBase == 36 || objectBase == 62; + } + + private static string Decode(byte[] bytes) + { + if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) + return Encoding.UTF8.GetString(bytes, 3, bytes.Length - 3); + if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) + return Encoding.Unicode.GetString(bytes, 2, bytes.Length - 2); + if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) + return Encoding.BigEndianUnicode.GetString(bytes, 2, bytes.Length - 2); + + try + { + return new UTF8Encoding(false, true).GetString(bytes); + } + catch (DecoderFallbackException) + { + try + { + return ShiftJisEncoding.GetString(bytes); + } + catch + { + return Encoding.Default.GetString(bytes); + } + } + } + + private static Encoding CreateShiftJisEncoding() + { + try + { + var providerType = Type.GetType( + "System.Text.CodePagesEncodingProvider, System.Text.Encoding.CodePages", + throwOnError: false); + var provider = providerType?.GetProperty("Instance")?.GetValue(null) as EncodingProvider; + if (provider != null) + Encoding.RegisterProvider(provider); + } + catch + { + // Unity's Mono runtime already exposes legacy code pages directly. Registration + // is only required by runtimes that split them into System.Text.Encoding.CodePages. + } + return Encoding.GetEncoding( + 932, + EncoderFallback.ExceptionFallback, + DecoderFallback.ExceptionFallback); + } + + private static int FindWhitespace(string line) + { + for (var index = 1; index < line.Length; index++) + if (char.IsWhiteSpace(line[index])) + return index; + return -1; + } + + private static bool TryParsePositiveDouble(string value, out double result) + { + return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result) && result > 0; + } + + private static int ParseLeadingInteger(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return 0; + + var digits = new string(value.Trim().TakeWhile(char.IsDigit).ToArray()); + return int.TryParse(digits, NumberStyles.None, CultureInfo.InvariantCulture, out var result) ? result : 0; + } + + private static string EmptyFallback(string value, string fallback) => + string.IsNullOrWhiteSpace(value) ? fallback : value; + + private static void AddWarning(BmsDocument document, string warning) + { + if (!document.Warnings.Contains(warning)) + document.Warnings.Add(warning); + } + + private static void ObservePlayableChannel(BmsMetadata metadata, string channel, string payload) + { + if (metadata == null || string.IsNullOrWhiteSpace(payload) || !ContainsPlayableObject(payload)) + return; + + var keyIndex = BmsKeyLayout.ResolveStandardKeyIndex(channel); + if (keyIndex >= 0) + metadata.UsedKeyChannelMask |= 1 << keyIndex; + if (channel == "16" || channel == "26" || channel == "56" || channel == "66") + metadata.HasScratch = true; + } + + private static bool ContainsPlayableObject(string payload) + { + for (var index = 0; index + 1 < payload.Length; index += 2) + if (payload[index] != '0' || payload[index + 1] != '0') + return true; + return false; + } + + private static void FinalizeKeyLayout(BmsMetadata metadata, int forcedKeyCount) + { + if (forcedKeyCount >= 4 && forcedKeyCount <= 7) + { + metadata.KeyCount = forcedKeyCount; + return; + } + + if (metadata.UsedKeyChannelMask == BmsKeyLayout.SixKeyChannelMask) + metadata.KeyCount = 6; + else if ((metadata.UsedKeyChannelMask & (1 << 6)) != 0) + metadata.KeyCount = 7; + else if ((metadata.UsedKeyChannelMask & (1 << 5)) != 0) + metadata.KeyCount = 6; + else if (metadata.UsedKeyChannelMask == BmsKeyLayout.FourKeyChannelMask) + metadata.KeyCount = 4; + else + metadata.KeyCount = 5; + } + + private static int CountBits(int value) + { + var count = 0; + while (value != 0) + { + count += value & 1; + value >>= 1; + } + return count; + } + + private sealed class ConditionalProcessor + { + private readonly Random _random; + private readonly Stack _randomValues = new Stack(); + private readonly Stack _conditions = new Stack(); + + public ConditionalProcessor(int seed) + { + _random = new Random(seed); + } + + public bool IsActive => _conditions.Count == 0 || _conditions.Peek().CurrentActive; + public bool HasOpenCondition => _conditions.Count > 0; + + public bool TryHandle(string line) + { + var separator = FindWhitespace(line); + var command = (separator < 0 ? line.Substring(1) : line.Substring(1, separator - 1)).ToUpperInvariant(); + var valueText = separator < 0 ? string.Empty : line.Substring(separator).Trim(); + int.TryParse(valueText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value); + + switch (command) + { + case "RANDOM": + _randomValues.Push(value > 0 ? _random.Next(1, value + 1) : 1); + return true; + case "SETRANDOM": + if (_randomValues.Count > 0) _randomValues.Pop(); + _randomValues.Push(value); + return true; + case "ENDRANDOM": + if (_randomValues.Count > 0) _randomValues.Pop(); + return true; + case "IF": + { + var parentActive = IsActive; + var matched = (_randomValues.Count > 0 ? _randomValues.Peek() : 1) == value; + _conditions.Push(new ConditionalFrame(parentActive, matched, parentActive && matched)); + return true; + } + case "ELSEIF": + { + if (_conditions.Count == 0) return true; + var frame = _conditions.Pop(); + var matched = !frame.BranchMatched && (_randomValues.Count > 0 ? _randomValues.Peek() : 1) == value; + frame.CurrentActive = frame.ParentActive && matched; + frame.BranchMatched |= matched; + _conditions.Push(frame); + return true; + } + case "ELSE": + { + if (_conditions.Count == 0) return true; + var frame = _conditions.Pop(); + var matched = !frame.BranchMatched; + frame.CurrentActive = frame.ParentActive && matched; + frame.BranchMatched = true; + _conditions.Push(frame); + return true; + } + case "ENDIF": + if (_conditions.Count > 0) _conditions.Pop(); + return true; + default: + return false; + } + } + + private struct ConditionalFrame + { + public ConditionalFrame(bool parentActive, bool branchMatched, bool currentActive) + { + ParentActive = parentActive; + BranchMatched = branchMatched; + CurrentActive = currentActive; + } + + public bool ParentActive; + public bool BranchMatched; + public bool CurrentActive; + } + } + } + + public static class BmsKeyLayout + { + public static readonly string[] StandardKeyChannels = + { "11", "12", "13", "14", "15", "18", "19" }; + public const int FourKeyChannelMask = + (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4); + // Community 6K charts use the seven-key channel grid with the center + // channel (14) empty: 11/12/13/[14]/15/18/19. + public const int SixKeyChannelMask = + (1 << 0) | (1 << 1) | (1 << 2) | (1 << 4) | (1 << 5) | (1 << 6); + + public static int ResolveStandardKeyIndex(string channel) + { + if (string.IsNullOrWhiteSpace(channel) || channel.Length != 2) + return -1; + + var normalized = channel[0] == '5' + ? "1" + channel.Substring(1, 1) + : channel; + return Array.IndexOf(StandardKeyChannels, normalized); + } + + public static string[] ResolveKeyChannels(BmsMetadata metadata) + { + metadata ??= new BmsMetadata(); + var keyCount = Math.Max(4, Math.Min(7, metadata.KeyCount)); + var channels = new List(keyCount); + + if (keyCount == 4 || keyCount == 6) + { + // Canonical 4K uses 11/12/14/15, while canonical 6K uses + // 11/12/13/15/18/19. Authoritative pack/table contracts must preserve + // these non-contiguous layouts even when a chart contains stray objects + // in an otherwise unused center channel. + var canonicalMask = keyCount == 4 + ? FourKeyChannelMask + : SixKeyChannelMask; + var preferredMask = keyCount == 6 || + (metadata.UsedKeyChannelMask & canonicalMask) == canonicalMask + ? canonicalMask + : metadata.UsedKeyChannelMask; + for (var index = 0; index < StandardKeyChannels.Length && channels.Count < keyCount; index++) + if ((preferredMask & (1 << index)) != 0) + channels.Add(StandardKeyChannels[index]); + } + + for (var index = 0; index < StandardKeyChannels.Length && channels.Count < keyCount; index++) + if (!channels.Contains(StandardKeyChannels[index])) + channels.Add(StandardKeyChannels[index]); + return channels.ToArray(); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Bms/BmsParser.cs.meta b/Assets/ProjectQuint/Runtime/Core/Bms/BmsParser.cs.meta new file mode 100644 index 0000000..8825724 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Bms/BmsParser.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 995f0998f5d77462eb5d93d5ddd9c47e \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Bms/BmsPlayableChartConverter.cs b/Assets/ProjectQuint/Runtime/Core/Bms/BmsPlayableChartConverter.cs new file mode 100644 index 0000000..c6bb4d6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Bms/BmsPlayableChartConverter.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProjectQuint.Core.Charts; +using ProjectQuint.Core.Timing; + +namespace ProjectQuint.Core.Bms +{ + public readonly struct BmsPlayableChartConversion + { + public BmsPlayableChartConversion(PlayableChart chart, int[] laneMap) + { + Chart = chart ?? throw new ArgumentNullException(nameof(chart)); + LaneMap = laneMap ?? throw new ArgumentNullException(nameof(laneMap)); + } + + public PlayableChart Chart { get; } + public int[] LaneMap { get; } + } + + /// + /// The only boundary that turns compiled BMS data into runtime gameplay data. + /// It preserves scratch as a fixed left lane, applies modifiers to key lanes only, + /// and leaves the source chart untouched. + /// + public static class BmsPlayableChartConverter + { + public static BmsPlayableChartConversion Convert( + BmsChart source, + LaneModifier laneModifier = LaneModifier.Off, + int randomSeed = 0) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + var keyCount = Math.Max(4, Math.Min(7, source.Metadata?.KeyCount ?? 5)); + var hasScratch = source.Metadata?.HasScratch == true; + var laneMap = LanePermutation.Create(laneModifier, keyCount, hasScratch, randomSeed); + var chart = new PlayableChart + { + Metadata = ConvertMetadata(source.Metadata, source.TempoMap), + TempoMap = source.TempoMap ?? TempoMap.Constant(source.Metadata?.InitialBpm > 0 + ? source.Metadata.InitialBpm + : 120.0), + AudioResources = new Dictionary( + source.WavDefinitions, + StringComparer.Ordinal), + VisualResources = new Dictionary( + source.BmpDefinitions, + StringComparer.Ordinal), + ScratchObjectCount = source.ScratchObjectCount, + }; + chart.ScrollMap = source.ScrollMap ?? new ScrollMap(chart.TempoMap); + chart.Warnings.AddRange(source.Warnings); + + foreach (var note in source.Notes) + { + if (note.Lane < 0 || note.Lane >= laneMap.Length) + continue; + + chart.Notes.Add(new PlayableNote + { + Id = note.Id, + Lane = laneMap[note.Lane], + StartTime = note.StartTime, + EndTime = note.EndTime, + StartBeat = note.StartBeat, + EndBeat = note.EndBeat, + SoundId = note.SoundId, + TailSoundId = note.TailSoundId, + IsScratch = note.IsScratch, + }); + } + + foreach (var item in source.BackgroundSounds) + { + chart.BackgroundAudioEvents.Add(new PlayableAudioEvent + { + Time = item.Time, + SoundId = item.SoundId, + }); + } + + foreach (var item in source.LaneSoundEvents) + { + if (item.Lane < 0 || item.Lane >= laneMap.Length) + continue; + chart.LaneSoundEvents.Add(new PlayableLaneSoundEvent + { + Lane = laneMap[item.Lane], + Time = item.Time, + SoundId = item.SoundId, + }); + } + + foreach (var item in source.BgaEvents) + { + chart.VisualEvents.Add(new PlayableVisualEvent + { + Time = item.Time, + ResourceId = item.ResourceId, + Layer = ConvertLayer(item.Layer), + }); + } + + chart.Notes.Sort((left, right) => + { + var time = left.StartTime.CompareTo(right.StartTime); + return time != 0 ? time : left.Lane.CompareTo(right.Lane); + }); + chart.BackgroundAudioEvents.Sort((left, right) => left.Time.CompareTo(right.Time)); + chart.LaneSoundEvents.Sort((left, right) => + { + var time = left.Time.CompareTo(right.Time); + return time != 0 ? time : left.Lane.CompareTo(right.Lane); + }); + chart.VisualEvents.Sort((left, right) => left.Time.CompareTo(right.Time)); + var lastNote = chart.Notes.Count == 0 + ? 0.0 + : chart.Notes.Max(note => note.IsLong ? note.EndTime : note.StartTime); + var lastBackgroundAudio = chart.BackgroundAudioEvents.Count == 0 + ? 0.0 + : chart.BackgroundAudioEvents.Max(item => item.Time); + chart.Duration = Math.Max(lastNote, lastBackgroundAudio) + 2.0; + return new BmsPlayableChartConversion(chart, laneMap); + } + + private static PlayableChartMetadata ConvertMetadata( + BmsMetadata source, + TempoMap tempoMap) + { + source ??= new BmsMetadata(); + var minimumTempo = source.MinimumBpm; + var maximumTempo = source.MaximumBpm; + if (tempoMap?.Nodes != null && tempoMap.Nodes.Count > 0) + { + minimumTempo = tempoMap.Nodes.Min(node => node.Bpm); + maximumTempo = tempoMap.Nodes.Max(node => node.Bpm); + } + return new PlayableChartMetadata + { + SourcePath = source.ChartPath, + Title = source.Title, + Subtitle = source.Subtitle, + Artist = source.Artist, + SubArtist = source.SubArtist, + Genre = source.Genre, + StageImage = source.StageFile, + BackgroundImage = source.BackBmp, + SourceHash = source.Md5, + InitialTempo = source.InitialBpm, + MinimumTempo = minimumTempo, + MaximumTempo = maximumTempo, + GaugeTotal = source.Total, + PlayLevel = source.PlayLevel, + Difficulty = source.Difficulty, + KeyCount = Math.Max(4, Math.Min(7, source.KeyCount)), + HasScratch = source.HasScratch, + }; + } + + private static PlayableVisualLayer ConvertLayer(BmsBgaLayer layer) => layer switch + { + BmsBgaLayer.Base => PlayableVisualLayer.Base, + BmsBgaLayer.Poor => PlayableVisualLayer.Poor, + BmsBgaLayer.Layer => PlayableVisualLayer.Overlay, + _ => throw new ArgumentOutOfRangeException(nameof(layer), layer, null), + }; + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Bms/BmsPlayableChartConverter.cs.meta b/Assets/ProjectQuint/Runtime/Core/Bms/BmsPlayableChartConverter.cs.meta new file mode 100644 index 0000000..24fea0b --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Bms/BmsPlayableChartConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 61f617e6ec3b74517a372d950d8a7b95 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Bms/BmsTimelineCompiler.cs b/Assets/ProjectQuint/Runtime/Core/Bms/BmsTimelineCompiler.cs new file mode 100644 index 0000000..f90ff8f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Bms/BmsTimelineCompiler.cs @@ -0,0 +1,493 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using ProjectQuint.Core.Timing; + +namespace ProjectQuint.Core.Bms +{ + public static class BmsTimelineCompiler + { + public static BmsChart Compile(BmsDocument document) + { + if (document == null) + throw new ArgumentNullException(nameof(document)); + + var chart = new BmsChart + { + Metadata = document.Metadata, + WavDefinitions = new Dictionary(document.WavDefinitions, StringComparer.Ordinal), + BmpDefinitions = new Dictionary(document.BmpDefinitions, StringComparer.Ordinal), + }; + chart.Warnings.AddRange(document.Warnings); + + var effectiveEvents = MergeDuplicateChannelRows(document.Events); + var maxMeasure = Math.Max( + effectiveEvents.Count == 0 ? 0 : effectiveEvents.Max(item => item.Position.Measure), + document.MeasureLengths.Count == 0 ? 0 : document.MeasureLengths.Keys.Max()); + var measureStarts = BuildMeasureStarts(document.MeasureLengths, maxMeasure + 1); + + var timedEvents = BuildTimedEvents( + document, + effectiveEvents, + measureStarts, + chart.Warnings, + out var tempoMap); + chart.TempoMap = tempoMap; + chart.Metadata.MinimumBpm = tempoMap.Nodes.Min(node => node.Bpm); + chart.Metadata.MaximumBpm = tempoMap.Nodes.Max(node => node.Bpm); + chart.ScrollMap = CompileScrollMap(document, timedEvents, tempoMap, chart.Warnings); + CompileBackgroundSounds(timedEvents, chart); + CompileBgaEvents(timedEvents, chart); + CompileVisibleNotes(timedEvents, document.Metadata.LnObj, chart); + CompileLongNoteChannels(timedEvents, chart); + CompileScratchObjects(timedEvents, document.Metadata.LnObj, chart); + CompileInvisibleKeySounds(timedEvents, chart); + + chart.ScratchObjectCount = timedEvents.Count(item => + item.Raw.Channel == "16" || item.Raw.Channel == "56" || + item.Raw.Channel == "26" || item.Raw.Channel == "66"); + + chart.Notes.Sort((left, right) => + { + var time = left.StartTime.CompareTo(right.StartTime); + return time != 0 ? time : left.Lane.CompareTo(right.Lane); + }); + for (var index = 0; index < chart.Notes.Count; index++) + chart.Notes[index].Id = index; + + chart.BackgroundSounds.Sort((left, right) => left.Time.CompareTo(right.Time)); + chart.LaneSoundEvents.Sort((left, right) => + { + var time = left.Time.CompareTo(right.Time); + return time != 0 ? time : left.Lane.CompareTo(right.Lane); + }); + chart.BgaEvents.Sort((left, right) => left.Time.CompareTo(right.Time)); + var lastNote = chart.Notes.Count == 0 ? 0.0 : chart.Notes.Max(note => note.IsLong ? note.EndTime : note.StartTime); + var lastBgm = chart.BackgroundSounds.Count == 0 ? 0.0 : chart.BackgroundSounds.Max(item => item.Time); + chart.Duration = Math.Max(lastNote, lastBgm) + 2.0; + return chart; + } + + private static List MergeDuplicateChannelRows(IEnumerable events) + { + var backgroundEvents = new List(); + var merged = new Dictionary(); + + foreach (var item in events.OrderBy(item => item.Sequence)) + { + if (item.Channel == "01") + { + backgroundEvents.Add(item); + continue; + } + + merged[new MergeKey(item.Position, item.Channel)] = item; + } + + backgroundEvents.AddRange(merged.Values); + backgroundEvents.Sort(CompareRawEvents); + return backgroundEvents; + } + + private static double[] BuildMeasureStarts(IReadOnlyDictionary measureLengths, int measureCount) + { + var starts = new double[measureCount + 1]; + for (var measure = 0; measure < measureCount; measure++) + { + var ratio = measureLengths.TryGetValue(measure, out var customLength) ? customLength : 1.0; + starts[measure + 1] = starts[measure] + 4.0 * ratio; + } + + return starts; + } + + private static List BuildTimedEvents( + BmsDocument document, + List events, + double[] measureStarts, + List warnings, + out TempoMap tempoMap) + { + var result = new List(events.Count); + var bpm = document.Metadata.InitialBpm > 0 ? document.Metadata.InitialBpm : 120.0; + var currentBeat = 0.0; + var currentTime = 0.0; + var tempoNodes = new List { new TempoMapNode(0, 0, bpm) }; + var index = 0; + + while (index < events.Count) + { + var position = events[index].Position; + var beat = measureStarts[position.Measure] + + 4.0 * GetMeasureRatio(document.MeasureLengths, position.Measure) * position.Fraction.Value; + var eventTime = currentTime + Math.Max(0.0, beat - currentBeat) * 60.0 / bpm; + currentBeat = beat; + + var groupStart = index; + while (index < events.Count && events[index].Position.Equals(position)) + { + result.Add(new TimedEvent(events[index], eventTime, beat)); + index++; + } + + var hasTempoChange = false; + for (var eventIndex = groupStart; eventIndex < index; eventIndex++) + { + var item = events[eventIndex]; + if (item.Channel == "03" && int.TryParse(item.ObjectId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var directBpm) && directBpm > 0) + { + bpm = directBpm; + hasTempoChange = true; + } + else if (item.Channel == "08" && document.ExtendedBpms.TryGetValue(item.ObjectId, out var extendedBpm) && extendedBpm > 0) + { + bpm = extendedBpm; + hasTempoChange = true; + } + } + + var stopDuration = 0.0; + for (var eventIndex = groupStart; eventIndex < index; eventIndex++) + { + var item = events[eventIndex]; + if (item.Channel != "09") + continue; + + if (document.Stops.TryGetValue(item.ObjectId, out var stopUnits)) + stopDuration += stopUnits * 60.0 / bpm / 48.0; + else + AddWarning(warnings, $"Undefined STOP object {item.ObjectId}."); + } + + if (hasTempoChange || stopDuration > 0) + { + var node = new TempoMapNode(beat, eventTime, bpm, stopDuration); + if (Math.Abs(tempoNodes[tempoNodes.Count - 1].Beat - beat) <= 0.000000001) + tempoNodes[tempoNodes.Count - 1] = node; + else + tempoNodes.Add(node); + } + currentTime = eventTime + stopDuration; + } + + tempoMap = new TempoMap(tempoNodes); + return result; + } + + private static ScrollMap CompileScrollMap( + BmsDocument document, + IEnumerable events, + TempoMap tempoMap, + List warnings) + { + var nodes = new List(); + foreach (var item in events) + { + if (item.Raw.Channel != "SC") + continue; + + if (!document.Scrolls.TryGetValue(item.Raw.ObjectId, out var multiplier)) + { + AddWarning(warnings, $"Undefined SCROLL object {item.Raw.ObjectId}."); + continue; + } + + nodes.Add(new ScrollMapNode(item.Beat, multiplier)); + } + return new ScrollMap(tempoMap, nodes); + } + + private static void CompileBackgroundSounds(IEnumerable events, BmsChart chart) + { + foreach (var item in events) + { + if (item.Raw.Channel != "01") + continue; + + chart.BackgroundSounds.Add(new BmsAudioEvent + { + Time = item.Time, + SoundId = item.Raw.ObjectId, + }); + } + } + + private static void CompileBgaEvents(IEnumerable events, BmsChart chart) + { + foreach (var item in events) + { + BmsBgaLayer layer; + switch (item.Raw.Channel) + { + case "04": layer = BmsBgaLayer.Base; break; + case "06": layer = BmsBgaLayer.Poor; break; + case "07": layer = BmsBgaLayer.Layer; break; + default: continue; + } + + chart.BgaEvents.Add(new BmsBgaEvent + { + Time = item.Time, + ResourceId = item.Raw.ObjectId, + Layer = layer, + }); + } + } + + private static void CompileVisibleNotes(List events, string lnObject, BmsChart chart) + { + var channels = BmsKeyLayout.ResolveKeyChannels(chart.Metadata); + var keyOffset = chart.Metadata.HasScratch ? 1 : 0; + for (var key = 0; key < channels.Length; key++) + { + var lane = key + keyOffset; + var channel = channels[key]; + var laneEvents = events + .Where(item => item.Raw.Channel == channel) + .OrderBy(item => item.Time) + .ThenBy(item => item.Raw.Sequence) + .ToList(); + + var laneNotes = new List(); + foreach (var item in laneEvents) + { + if (!string.IsNullOrEmpty(lnObject) && string.Equals(item.Raw.ObjectId, lnObject, StringComparison.Ordinal)) + { + var head = laneNotes.LastOrDefault(note => !note.IsLong && note.StartTime < item.Time); + if (head == null) + { + AddWarning(chart.Warnings, $"LNOBJ tail without a head in lane {lane + 1}."); + continue; + } + + head.EndTime = item.Time; + head.EndBeat = item.Beat; + head.TailSoundId = item.Raw.ObjectId; + continue; + } + + laneNotes.Add(new BmsNote + { + Lane = lane, + StartTime = item.Time, + EndTime = item.Time, + StartBeat = item.Beat, + EndBeat = item.Beat, + SoundId = item.Raw.ObjectId, + }); + } + + chart.Notes.AddRange(laneNotes); + } + } + + private static void CompileLongNoteChannels(List events, BmsChart chart) + { + var channels = BmsKeyLayout.ResolveKeyChannels(chart.Metadata); + var keyOffset = chart.Metadata.HasScratch ? 1 : 0; + for (var key = 0; key < channels.Length; key++) + { + var lane = key + keyOffset; + var channel = "5" + channels[key].Substring(1, 1); + var laneEvents = events + .Where(item => item.Raw.Channel == channel) + .OrderBy(item => item.Time) + .ThenBy(item => item.Raw.Sequence) + .ToList(); + + for (var index = 0; index + 1 < laneEvents.Count; index += 2) + { + var head = laneEvents[index]; + var tail = laneEvents[index + 1]; + chart.Notes.Add(new BmsNote + { + Lane = lane, + StartTime = head.Time, + EndTime = tail.Time, + StartBeat = head.Beat, + EndBeat = tail.Beat, + SoundId = head.Raw.ObjectId, + TailSoundId = tail.Raw.ObjectId, + }); + } + + if ((laneEvents.Count & 1) != 0) + AddWarning(chart.Warnings, $"Unpaired long-note object in lane {lane + 1}."); + } + } + + private static void CompileScratchObjects(List events, string lnObject, BmsChart chart) + { + if (!chart.Metadata.HasScratch) + return; + + CompileVisibleChannel(events, "16", 0, lnObject, chart, isScratch: true); + CompileVisibleChannel(events, "26", 0, lnObject, chart, isScratch: true); + CompileLongNoteChannel(events, "56", 0, chart, isScratch: true); + CompileLongNoteChannel(events, "66", 0, chart, isScratch: true); + } + + private static void CompileInvisibleKeySounds(List events, BmsChart chart) + { + var channels = BmsKeyLayout.ResolveKeyChannels(chart.Metadata); + var keyOffset = chart.Metadata.HasScratch ? 1 : 0; + for (var key = 0; key < channels.Length; key++) + { + var visibleChannel = channels[key]; + if (visibleChannel.Length != 2) + continue; + CompileInvisibleChannel( + events, + "3" + visibleChannel.Substring(1, 1), + key + keyOffset, + chart); + } + + if (chart.Metadata.HasScratch) + { + CompileInvisibleChannel(events, "36", 0, chart); + CompileInvisibleChannel(events, "46", 0, chart); + } + } + + private static void CompileInvisibleChannel( + IEnumerable events, + string channel, + int lane, + BmsChart chart) + { + foreach (var item in events) + { + if (item.Raw.Channel != channel || string.IsNullOrWhiteSpace(item.Raw.ObjectId)) + continue; + chart.LaneSoundEvents.Add(new BmsLaneSoundEvent + { + Lane = lane, + Time = item.Time, + SoundId = item.Raw.ObjectId, + }); + } + } + + private static void CompileVisibleChannel( + List events, + string channel, + int lane, + string lnObject, + BmsChart chart, + bool isScratch = false) + { + var channelEvents = events + .Where(item => item.Raw.Channel == channel) + .OrderBy(item => item.Time) + .ThenBy(item => item.Raw.Sequence) + .ToList(); + var notes = new List(); + foreach (var item in channelEvents) + { + if (!string.IsNullOrEmpty(lnObject) && + string.Equals(item.Raw.ObjectId, lnObject, StringComparison.Ordinal)) + { + var head = notes.LastOrDefault(note => !note.IsLong && note.StartTime < item.Time); + if (head != null) + { + head.EndTime = item.Time; + head.EndBeat = item.Beat; + head.TailSoundId = item.Raw.ObjectId; + } + continue; + } + + notes.Add(new BmsNote + { + Lane = lane, + StartTime = item.Time, + EndTime = item.Time, + StartBeat = item.Beat, + EndBeat = item.Beat, + SoundId = item.Raw.ObjectId, + IsScratch = isScratch, + }); + } + chart.Notes.AddRange(notes); + } + + private static void CompileLongNoteChannel( + List events, + string channel, + int lane, + BmsChart chart, + bool isScratch = false) + { + var channelEvents = events + .Where(item => item.Raw.Channel == channel) + .OrderBy(item => item.Time) + .ThenBy(item => item.Raw.Sequence) + .ToList(); + for (var index = 0; index + 1 < channelEvents.Count; index += 2) + { + var head = channelEvents[index]; + var tail = channelEvents[index + 1]; + chart.Notes.Add(new BmsNote + { + Lane = lane, + StartTime = head.Time, + EndTime = tail.Time, + StartBeat = head.Beat, + EndBeat = tail.Beat, + SoundId = head.Raw.ObjectId, + TailSoundId = tail.Raw.ObjectId, + IsScratch = isScratch, + }); + } + } + + private static double GetMeasureRatio(IReadOnlyDictionary measureLengths, int measure) + { + return measureLengths.TryGetValue(measure, out var ratio) ? ratio : 1.0; + } + + private static int CompareRawEvents(BmsRawEvent left, BmsRawEvent right) + { + var position = left.Position.CompareTo(right.Position); + return position != 0 ? position : left.Sequence.CompareTo(right.Sequence); + } + + private static void AddWarning(ICollection warnings, string warning) + { + if (!warnings.Contains(warning)) + warnings.Add(warning); + } + + private readonly struct MergeKey : IEquatable + { + public MergeKey(BmsPosition position, string channel) + { + Position = position; + Channel = channel; + } + + private BmsPosition Position { get; } + private string Channel { get; } + + public bool Equals(MergeKey other) => Position.Equals(other.Position) && Channel == other.Channel; + public override bool Equals(object obj) => obj is MergeKey other && Equals(other); + public override int GetHashCode() => HashCode.Combine(Position, Channel); + } + + private sealed class TimedEvent + { + public TimedEvent(BmsRawEvent raw, double time, double beat) + { + Raw = raw; + Time = time; + Beat = beat; + } + + public BmsRawEvent Raw { get; } + public double Time { get; } + public double Beat { get; } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Bms/BmsTimelineCompiler.cs.meta b/Assets/ProjectQuint/Runtime/Core/Bms/BmsTimelineCompiler.cs.meta new file mode 100644 index 0000000..16f782f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Bms/BmsTimelineCompiler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: baa77492ad43445629ed6490886a40e3 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Charts.meta b/Assets/ProjectQuint/Runtime/Core/Charts.meta new file mode 100644 index 0000000..fa43b35 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Charts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d2bc03fb2c08c47b1b20db316c595ca2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Charts/PlayableChart.cs b/Assets/ProjectQuint/Runtime/Core/Charts/PlayableChart.cs new file mode 100644 index 0000000..5919f0e --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Charts/PlayableChart.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Timing; + +namespace ProjectQuint.Core.Charts +{ + public enum LaneModifier + { + Off, + Mirror, + Random, + } + + public enum PlayableVisualLayer + { + Base, + Poor, + Overlay, + } + + public static class ChartKeyMode + { + public static string FormatLabel(int keyCount, bool hasScratch) + { + var normalized = Math.Max(4, Math.Min(7, keyCount)); + return hasScratch ? $"1+{normalized}K" : $"{normalized}K"; + } + } + + public sealed class PlayableChartMetadata + { + public string SourcePath { get; set; } = string.Empty; + public string Title { get; set; } = "Untitled"; + public string Subtitle { get; set; } = string.Empty; + public string Artist { get; set; } = "Unknown Artist"; + public string SubArtist { get; set; } = string.Empty; + public string Genre { get; set; } = string.Empty; + public string StageImage { get; set; } = string.Empty; + public string BackgroundImage { get; set; } = string.Empty; + public string SourceHash { get; set; } = string.Empty; + public string DifficultyLabel { get; set; } = string.Empty; + public double InitialTempo { get; set; } = 120.0; + public double MinimumTempo { get; set; } + public double MaximumTempo { get; set; } + public double GaugeTotal { get; set; } + public int PlayLevel { get; set; } + public int Difficulty { get; set; } + public int KeyCount { get; set; } = 5; + public bool HasScratch { get; set; } + public int TotalLaneCount => Math.Max(1, KeyCount) + (HasScratch ? 1 : 0); + } + + public sealed class PlayableNote + { + public int Id { get; set; } + public int Lane { get; set; } + public double StartTime { get; set; } + public double EndTime { get; set; } + public double StartBeat { get; set; } = double.NaN; + public double EndBeat { get; set; } = double.NaN; + public string SoundId { get; set; } = string.Empty; + public string TailSoundId { get; set; } = string.Empty; + public bool IsScratch { get; set; } + public bool IsLong => EndTime > StartTime; + } + + public sealed class PlayableAudioEvent + { + public double Time { get; set; } + public string SoundId { get; set; } = string.Empty; + } + + public sealed class PlayableLaneSoundEvent + { + public int Lane { get; set; } + public double Time { get; set; } + public string SoundId { get; set; } = string.Empty; + } + + public sealed class PlayableVisualEvent + { + public double Time { get; set; } + public string ResourceId { get; set; } = string.Empty; + public PlayableVisualLayer Layer { get; set; } + } + + /// + /// Format-neutral data consumed by gameplay, audio, BGA and result presentation. + /// Source-format parsers must convert into this model before runtime systems see a chart. + /// + public sealed class PlayableChart + { + private static readonly TempoMap DefaultTempoMap = TempoMap.Constant(120.0); + + public PlayableChartMetadata Metadata { get; set; } = new PlayableChartMetadata(); + public TempoMap TempoMap { get; set; } = DefaultTempoMap; + public ScrollMap ScrollMap { get; set; } = new ScrollMap(DefaultTempoMap); + public Dictionary AudioResources { get; set; } = + new Dictionary(StringComparer.Ordinal); + public Dictionary VisualResources { get; set; } = + new Dictionary(StringComparer.Ordinal); + public List Notes { get; } = new List(); + public List BackgroundAudioEvents { get; } = new List(); + public List LaneSoundEvents { get; } = new List(); + public List VisualEvents { get; } = new List(); + public List Warnings { get; } = new List(); + public int ScratchObjectCount { get; set; } + public double Duration { get; set; } + } + + public static class LanePermutation + { + public static int[] Create( + LaneModifier modifier, + int keyCount = 5, + bool hasScratch = false, + int randomSeed = 0) + { + if (keyCount < 4 || keyCount > 7) + throw new ArgumentOutOfRangeException(nameof(keyCount), "BMS key count must be between 4 and 7."); + + var keyOffset = hasScratch ? 1 : 0; + var laneMap = new int[keyCount + keyOffset]; + if (hasScratch) + laneMap[0] = 0; + for (var key = 0; key < keyCount; key++) + laneMap[key + keyOffset] = key + keyOffset; + + switch (modifier) + { + case LaneModifier.Off: + return laneMap; + case LaneModifier.Mirror: + Array.Reverse(laneMap, keyOffset, keyCount); + return laneMap; + case LaneModifier.Random: + { + var random = new Random(randomSeed); + for (var index = laneMap.Length - 1; index > keyOffset; index--) + { + var swapIndex = random.Next(keyOffset, index + 1); + (laneMap[index], laneMap[swapIndex]) = (laneMap[swapIndex], laneMap[index]); + } + + var unchanged = true; + for (var lane = keyOffset; lane < laneMap.Length; lane++) + unchanged &= laneMap[lane] == lane; + if (unchanged) + (laneMap[keyOffset], laneMap[keyOffset + 1]) = + (laneMap[keyOffset + 1], laneMap[keyOffset]); + return laneMap; + } + default: + throw new ArgumentOutOfRangeException(nameof(modifier), modifier, null); + } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Charts/PlayableChart.cs.meta b/Assets/ProjectQuint/Runtime/Core/Charts/PlayableChart.cs.meta new file mode 100644 index 0000000..a76d1d4 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Charts/PlayableChart.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 06a0a05da456f45cf80b873aee380ba0 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Course.meta b/Assets/ProjectQuint/Runtime/Core/Course.meta new file mode 100644 index 0000000..4b69cb5 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Course.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c4d67179ecaf44a5937315f956dfa7a9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Course/CourseLaneModifierPolicy.cs b/Assets/ProjectQuint/Runtime/Core/Course/CourseLaneModifierPolicy.cs new file mode 100644 index 0000000..4889078 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Course/CourseLaneModifierPolicy.cs @@ -0,0 +1,26 @@ +using ProjectQuint.Core.Charts; + +namespace ProjectQuint.Core.Course +{ + /// + /// Competitive course runs permit only the authored lane order or its mirror. + /// RANDOM is normalized to the authored order before any stage is transformed. + /// + public static class CourseLaneModifierPolicy + { + public static bool IsAllowed(LaneModifier modifier) + { + return modifier == LaneModifier.Off || modifier == LaneModifier.Mirror; + } + + public static LaneModifier Normalize(LaneModifier modifier) + { + return modifier == LaneModifier.Mirror ? LaneModifier.Mirror : LaneModifier.Off; + } + + public static LaneModifier Normalize(LaneModifier modifier, bool mirrorAllowed) + { + return mirrorAllowed ? Normalize(modifier) : LaneModifier.Off; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Course/CourseLaneModifierPolicy.cs.meta b/Assets/ProjectQuint/Runtime/Core/Course/CourseLaneModifierPolicy.cs.meta new file mode 100644 index 0000000..6b3b1de --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Course/CourseLaneModifierPolicy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d88302233d278417d9827be5358f8de2 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Course/CourseProgress.cs b/Assets/ProjectQuint/Runtime/Core/Course/CourseProgress.cs new file mode 100644 index 0000000..92c892e --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Course/CourseProgress.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Judgement; + +namespace ProjectQuint.Core.Course +{ + public readonly struct CourseStageResult + { + public CourseStageResult(RhythmJudgementEngine judgement, bool cleared) + { + if (judgement == null) + throw new ArgumentNullException(nameof(judgement)); + + Cleared = cleared; + Score = judgement.Score; + AccuracyPercent = judgement.AccuracyPercent; + ExScore = judgement.KoolCount * 2 + judgement.CoolCount; + ExScoreRate = judgement.JudgedNoteCount <= 0 + ? 0.0 + : ExScore * 100.0 / (judgement.JudgedNoteCount * 2.0); + BreakPointCount = judgement.MissCount; + MaxCombo = judgement.MaxCombo; + JudgedNotes = judgement.JudgedNoteCount; + TotalScoringUnits = judgement.TotalScoringUnitCount; + KoolCount = judgement.KoolCount; + CoolCount = judgement.CoolCount; + GoodCount = judgement.GoodCount; + MissCount = judgement.MissCount; + PurePerfectCount = judgement.PurePerfectCount; + } + + public bool Cleared { get; } + public int Score { get; } + public double AccuracyPercent { get; } + public int ExScore { get; } + public double ExScoreRate { get; } + public int BreakPointCount { get; } + public int MaxCombo { get; } + public int JudgedNotes { get; } + public int TotalScoringUnits { get; } + public int KoolCount { get; } + public int CoolCount { get; } + public int GoodCount { get; } + public int MissCount { get; } + public int PurePerfectCount { get; } + } + + public sealed class CourseProgress + { + public const int ScoreFormatVersion = 2; + + private long _accuracyValueTotal; + private int _stageScoreTotal; + private readonly List _stageResults = + new List(4); + + public int StagesPlayed { get; private set; } + public int ClearedStages { get; private set; } + public int JudgedNotes { get; private set; } + public int KoolCount { get; private set; } + public int CoolCount { get; private set; } + public int GoodCount { get; private set; } + public int MissCount { get; private set; } + public int FastCount { get; private set; } + public int LateCount { get; private set; } + public int PurePerfectCount { get; private set; } + public int CurrentCombo { get; private set; } + public int MaxCombo { get; private set; } + public IReadOnlyList StageResults => _stageResults; + + public int ExScore => KoolCount * 2 + CoolCount; + public int BreakPointCount => MissCount; + public int Score => _stageScoreTotal; + public double AccuracyPercent => JudgedNotes <= 0 + ? 0.0 + : _accuracyValueTotal * 100.0 / (JudgedNotes * 1000.0); + public double ExScoreRate => JudgedNotes <= 0 + ? 0.0 + : ExScore * 100.0 / (JudgedNotes * 2.0); + public double MissRate => JudgedNotes <= 0 + ? 100.0 + : BreakPointCount * 100.0 / JudgedNotes; + + public int CombinedScore(RhythmJudgementEngine currentStage) + { + return _stageScoreTotal + (currentStage?.Score ?? 0); + } + + public double CombinedAccuracyPercent(RhythmJudgementEngine currentStage) + { + var currentJudgedNotes = currentStage?.JudgedNoteCount ?? 0; + var combinedJudgedNotes = JudgedNotes + currentJudgedNotes; + if (combinedJudgedNotes <= 0) + return 100.0; + + var currentAccuracyValue = + (currentStage?.KoolCount ?? 0) * 1000L + + (currentStage?.CoolCount ?? 0) * 700L + + (currentStage?.GoodCount ?? 0) * 200L; + return (_accuracyValueTotal + currentAccuracyValue) * 100.0 / + (combinedJudgedNotes * 1000.0); + } + + public void AddStage(RhythmJudgementEngine judgement, bool cleared) + { + if (judgement == null) + throw new ArgumentNullException(nameof(judgement)); + + var stageResult = new CourseStageResult(judgement, cleared); + _stageResults.Add(stageResult); + StagesPlayed++; + if (cleared) + ClearedStages++; + _stageScoreTotal += stageResult.Score; + JudgedNotes += judgement.JudgedNoteCount; + KoolCount += judgement.KoolCount; + CoolCount += judgement.CoolCount; + GoodCount += judgement.GoodCount; + MissCount += judgement.MissCount; + FastCount += judgement.FastCount; + LateCount += judgement.LateCount; + PurePerfectCount += judgement.PurePerfectCount; + CurrentCombo = judgement.Combo; + MaxCombo = Math.Max(MaxCombo, judgement.MaxCombo); + _accuracyValueTotal += + judgement.KoolCount * 1000L + + judgement.CoolCount * 700L + + judgement.GoodCount * 200L; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Course/CourseProgress.cs.meta b/Assets/ProjectQuint/Runtime/Core/Course/CourseProgress.cs.meta new file mode 100644 index 0000000..52651aa --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Course/CourseProgress.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 50b6be1327764ee4ac0141e729418c72 diff --git a/Assets/ProjectQuint/Runtime/Core/Gauge.meta b/Assets/ProjectQuint/Runtime/Core/Gauge.meta new file mode 100644 index 0000000..5ed0f24 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Gauge.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5538736f60e3447cb97e3a501798db0c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Gauge/GaugeFallbackChain.cs b/Assets/ProjectQuint/Runtime/Core/Gauge/GaugeFallbackChain.cs new file mode 100644 index 0000000..6df2fd3 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Gauge/GaugeFallbackChain.cs @@ -0,0 +1,120 @@ +using System; +using ProjectQuint.Core.Judgement; + +namespace ProjectQuint.Core.Gauge +{ + /// + /// Tracks survival-gauge fallbacks from the beginning of a song so that a + /// transition uses the exact state the destination gauge would have had. + /// All gauges are allocated up front; applying judgements does not allocate. + /// + public sealed class GaugeFallbackChain + { + private readonly PlayGauge[] _gauges; + private int _activeIndex; + + public GaugeFallbackChain( + GaugeMode initialMode, + double declaredTotal, + int totalNotes, + bool fallbackEnabled, + double? restoredValue = null, + CourseGaugeProfile courseGaugeProfile = CourseGaugeProfile.Lr2) + { + var canFallback = + fallbackEnabled && + !restoredValue.HasValue && + initialMode != GaugeMode.Class; + var modes = canFallback + ? ResolveModes(initialMode) + : new[] { initialMode }; + + _gauges = new PlayGauge[modes.Length]; + for (var index = 0; index < modes.Length; index++) + { + _gauges[index] = new PlayGauge( + modes[index], + declaredTotal, + totalNotes, + index == 0 ? restoredValue : null, + courseGaugeProfile); + } + } + + public PlayGauge ActiveGauge => _gauges[_activeIndex]; + public int GaugeCount => _gauges.Length; + public bool HasFallback => _activeIndex + 1 < _gauges.Length; + + public GaugeTransition Apply(JudgementResult result) + { + for (var index = 0; index < _gauges.Length; index++) + _gauges[index].Apply(result); + return AdvancePastFailedGauges(); + } + + public GaugeTransition Apply(JudgementGrade grade) + { + for (var index = 0; index < _gauges.Length; index++) + _gauges[index].Apply(grade); + return AdvancePastFailedGauges(); + } + + public GaugeTransition ApplyEmptyInputPenalty() + { + for (var index = 0; index < _gauges.Length; index++) + _gauges[index].ApplyEmptyInputPenalty(); + return AdvancePastFailedGauges(); + } + + internal PlayGauge GaugeAt(int index) + { + if (index < 0 || index >= _gauges.Length) + throw new ArgumentOutOfRangeException(nameof(index)); + return _gauges[index]; + } + + private GaugeTransition AdvancePastFailedGauges() + { + if (!ActiveGauge.IsFailed || !HasFallback) + return GaugeTransition.None; + + var previousMode = ActiveGauge.Mode; + do + { + _activeIndex++; + } + while (ActiveGauge.IsFailed && HasFallback); + + return new GaugeTransition(previousMode, ActiveGauge.Mode); + } + + private static GaugeMode[] ResolveModes(GaugeMode initialMode) + { + switch (initialMode) + { + case GaugeMode.ExHard: + return new[] { GaugeMode.ExHard, GaugeMode.Hard, GaugeMode.Normal }; + case GaugeMode.Hard: + return new[] { GaugeMode.Hard, GaugeMode.Normal }; + default: + return new[] { initialMode }; + } + } + } + + public readonly struct GaugeTransition + { + public static readonly GaugeTransition None = default; + + public GaugeTransition(GaugeMode from, GaugeMode to) + { + From = from; + To = to; + Changed = from != to; + } + + public GaugeMode From { get; } + public GaugeMode To { get; } + public bool Changed { get; } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Gauge/GaugeFallbackChain.cs.meta b/Assets/ProjectQuint/Runtime/Core/Gauge/GaugeFallbackChain.cs.meta new file mode 100644 index 0000000..49af50f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Gauge/GaugeFallbackChain.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8f9710e41db44ce3a2317d8ef1f95ed7 diff --git a/Assets/ProjectQuint/Runtime/Core/Gauge/PlayGauge.cs b/Assets/ProjectQuint/Runtime/Core/Gauge/PlayGauge.cs new file mode 100644 index 0000000..d9537df --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Gauge/PlayGauge.cs @@ -0,0 +1,263 @@ +using System; +using ProjectQuint.Core.Judgement; + +namespace ProjectQuint.Core.Gauge +{ + public enum GaugeMode + { + Normal = 0, + Hard = 1, + Class = 2, + ExHard = 3, + } + + public enum CourseGaugeProfile + { + Lr2, + FiveKey, + SevenKey, + } + + /// + /// Owns ProjectQuint's normal, hard, ex-hard, and course life-gauge state. + /// Regular survival play uses one consistent ProjectQuint profile across + /// every supported key mode. Source-specific course rules remain isolated + /// in the course profile. + /// + public sealed class PlayGauge + { + public const double NormalInitialValue = 20.0; + public const double NormalMinimumValue = 2.0; + public const double NormalClearBorder = 75.0; + public const double HardInitialValue = 100.0; + public const double ClassInitialValue = 100.0; + public const double MaximumValue = 100.0; + + private readonly double _normalFullHitIncrement; + private readonly double _standardHardRecoveryScale; + private readonly CourseGaugeProfile _courseGaugeProfile; + + public PlayGauge( + GaugeMode mode, + double declaredTotal, + int totalNotes, + double? restoredValue = null, + CourseGaugeProfile courseGaugeProfile = CourseGaugeProfile.Lr2) + { + Mode = mode; + TotalNotes = Math.Max(0, totalNotes); + Total = declaredTotal > 0 ? declaredTotal : CalculateDefaultTotal(TotalNotes); + _courseGaugeProfile = courseGaugeProfile; + _normalFullHitIncrement = TotalNotes > 0 ? Total / TotalNotes : 0; + _standardHardRecoveryScale = CalculateStandardHardRecoveryScale(Total, TotalNotes); + Reset(); + if (restoredValue.HasValue) + Restore(restoredValue.Value); + } + + public GaugeMode Mode { get; } + public int TotalNotes { get; } + public double Total { get; } + public double Value { get; private set; } + public double MinimumValue => Mode == GaugeMode.Normal ? NormalMinimumValue : 0.0; + public double ClearBorder => Mode == GaugeMode.Normal ? NormalClearBorder : 0.0; + public bool IsFailed => Mode != GaugeMode.Normal && Value <= 0.0; + public bool IsCleared => Mode == GaugeMode.Normal + ? Value >= NormalClearBorder + : !IsFailed; + + public void Reset() + { + Value = Mode switch + { + GaugeMode.Normal => NormalInitialValue, + GaugeMode.Class => ClassInitialValue, + _ => HardInitialValue, + }; + } + + public void Restore(double value) + { + Value = Math.Max(MinimumValue, Math.Min(MaximumValue, value)); + } + + public void Apply(JudgementResult result) + { + Apply(result.Grade); + } + + public void Apply(JudgementGrade grade) + { + if (IsFailed || grade == JudgementGrade.None) + return; + + var delta = Mode switch + { + GaugeMode.Normal => ResolveNormalDelta(grade), + GaugeMode.Class => ResolveCourseDelta(grade, Value, _courseGaugeProfile), + GaugeMode.ExHard => ResolveExHardDelta(grade), + _ => ResolveHardDelta(grade), + }; + Value = Math.Max(MinimumValue, Math.Min(MaximumValue, Value + delta)); + } + + public void ApplyEmptyInputPenalty() + { + if (IsFailed) + return; + + var delta = Mode switch + { + GaugeMode.Normal => -2.0, + GaugeMode.Class => ResolveCourseEmptyInputDelta(Value, _courseGaugeProfile), + GaugeMode.ExHard => -8.0, + _ => ApplyStandardHardDamageReduction(-5.0, Value), + }; + Value = Math.Max(MinimumValue, Math.Min(MaximumValue, Value + delta)); + } + + public static double CalculateDefaultTotal(int totalNotes) + { + var notes = Math.Max(0, totalNotes); + return Math.Max(260.0, 7.605 * notes / (0.01 * notes + 6.5)); + } + + private double ResolveNormalDelta(JudgementGrade grade) + { + switch (grade) + { + case JudgementGrade.Kool: + case JudgementGrade.Cool: + return _normalFullHitIncrement; + case JudgementGrade.Good: + return _normalFullHitIncrement * 0.5; + case JudgementGrade.Miss: + return -6.0; + default: + return 0.0; + } + } + + private double ResolveHardDelta(JudgementGrade grade) + { + switch (grade) + { + case JudgementGrade.Kool: + return 0.15 * _standardHardRecoveryScale; + case JudgementGrade.Cool: + return 0.12 * _standardHardRecoveryScale; + case JudgementGrade.Good: + return 0.03 * _standardHardRecoveryScale; + case JudgementGrade.Miss: + return ApplyStandardHardDamageReduction(-10.0, Value); + default: + return 0.0; + } + } + + private double ResolveExHardDelta(JudgementGrade grade) + { + switch (grade) + { + case JudgementGrade.Kool: + return 0.15 * _standardHardRecoveryScale; + case JudgementGrade.Cool: + return 0.06 * _standardHardRecoveryScale; + case JudgementGrade.Good: + return 0.0; + case JudgementGrade.Miss: + return -16.0; + default: + return 0.0; + } + } + + private static double ResolveCourseDelta( + JudgementGrade grade, + double currentValue, + CourseGaugeProfile profile) + { + var delta = profile switch + { + CourseGaugeProfile.FiveKey => grade switch + { + JudgementGrade.Kool => 0.01, + JudgementGrade.Cool => 0.01, + JudgementGrade.Good => 0.0, + JudgementGrade.Miss => -1.0, + _ => 0.0, + }, + CourseGaugeProfile.SevenKey => grade switch + { + JudgementGrade.Kool => 0.15, + JudgementGrade.Cool => 0.12, + JudgementGrade.Good => 0.06, + JudgementGrade.Miss => -3.0, + _ => 0.0, + }, + _ => grade switch + { + JudgementGrade.Kool => 0.10, + JudgementGrade.Cool => 0.10, + JudgementGrade.Good => 0.05, + JudgementGrade.Miss => -3.0, + _ => 0.0, + }, + }; + return ApplyCourseDamageReduction(delta, currentValue, profile); + } + + private static double ResolveCourseEmptyInputDelta( + double currentValue, + CourseGaugeProfile profile) + { + var delta = profile switch + { + CourseGaugeProfile.FiveKey => -0.5, + CourseGaugeProfile.SevenKey => -1.5, + _ => -2.0, + }; + return ApplyCourseDamageReduction(delta, currentValue, profile); + } + + private static double ApplyCourseDamageReduction( + double delta, + double currentValue, + CourseGaugeProfile profile) + { + if (delta >= 0.0 || profile == CourseGaugeProfile.FiveKey) + return delta; + if (profile == CourseGaugeProfile.Lr2) + return currentValue < 30.0 ? delta * 0.6 : delta; + if (currentValue < 5.0) return delta * 0.4; + if (currentValue < 10.0) return delta * 0.5; + if (currentValue < 15.0) return delta * 0.6; + if (currentValue < 20.0) return delta * 0.7; + if (currentValue < 25.0) return delta * 0.8; + return delta; + } + + private static double CalculateStandardHardRecoveryScale(double total, int totalNotes) + { + if (totalNotes <= 0) + return 0.0; + + var fullHitRecovery = Math.Max( + 0.0, + Math.Min(0.15, (2.0 * total - 320.0) / totalNotes)); + return fullHitRecovery / 0.15; + } + + private static double ApplyStandardHardDamageReduction(double delta, double currentValue) + { + if (delta >= 0.0) + return delta; + if (currentValue < 10.0) return delta * 0.4; + if (currentValue < 20.0) return delta * 0.5; + if (currentValue < 30.0) return delta * 0.6; + if (currentValue < 40.0) return delta * 0.7; + if (currentValue < 50.0) return delta * 0.8; + return delta; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Gauge/PlayGauge.cs.meta b/Assets/ProjectQuint/Runtime/Core/Gauge/PlayGauge.cs.meta new file mode 100644 index 0000000..c8f5431 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Gauge/PlayGauge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f17a55945f92a41b8b11cafbae0b788a diff --git a/Assets/ProjectQuint/Runtime/Core/Identity.meta b/Assets/ProjectQuint/Runtime/Core/Identity.meta new file mode 100644 index 0000000..e57c9d6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Identity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ce17824f818ae4ce59c3510373fb2848 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Identity/ChartId.cs b/Assets/ProjectQuint/Runtime/Core/Identity/ChartId.cs new file mode 100644 index 0000000..7a627df --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Identity/ChartId.cs @@ -0,0 +1,46 @@ +using System; + +namespace ProjectQuint.Core.Identity +{ + public readonly struct ChartId : IEquatable + { + private ChartId(string value) + { + Value = value ?? string.Empty; + } + + public string Value { get; } + public bool IsValid => !string.IsNullOrWhiteSpace(Value); + + public static ChartId FromMd5(string md5) => + Create("md5", md5); + + public static ChartId FromSha256(string sha256) => + Create("sha256", sha256); + + public static ChartId FromPathFallback(string pathHash) => + Create("path-sha256", pathHash); + + public bool Equals(ChartId other) => + string.Equals(Value, other.Value, StringComparison.Ordinal); + + public override bool Equals(object obj) => + obj is ChartId other && Equals(other); + + public override int GetHashCode() => + StringComparer.Ordinal.GetHashCode(Value ?? string.Empty); + + public override string ToString() => Value ?? string.Empty; + + public static bool operator ==(ChartId left, ChartId right) => left.Equals(right); + public static bool operator !=(ChartId left, ChartId right) => !left.Equals(right); + + private static ChartId Create(string algorithm, string digest) + { + var normalized = (digest ?? string.Empty).Trim().ToLowerInvariant(); + return string.IsNullOrEmpty(normalized) + ? default + : new ChartId($"{algorithm}:{normalized}"); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Identity/ChartId.cs.meta b/Assets/ProjectQuint/Runtime/Core/Identity/ChartId.cs.meta new file mode 100644 index 0000000..fa66246 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Identity/ChartId.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8a50d6052e42b438f902e8410ddb3682 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Judgement.meta b/Assets/ProjectQuint/Runtime/Core/Judgement.meta new file mode 100644 index 0000000..56e65d6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Judgement.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 831a80b86d35b4af99c56b93f4ad623d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Judgement/LongNotePolicies.cs b/Assets/ProjectQuint/Runtime/Core/Judgement/LongNotePolicies.cs new file mode 100644 index 0000000..19a25be --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Judgement/LongNotePolicies.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Charts; +using ProjectQuint.Core.Timing; + +namespace ProjectQuint.Core.Judgement +{ + public sealed class LongNoteReleasePolicy + { + public static LongNoteReleasePolicy DjmaxLike { get; } = new LongNoteReleasePolicy(0.100); + + public LongNoteReleasePolicy(double earlyReleaseGraceSeconds) + { + if (earlyReleaseGraceSeconds < 0 || + double.IsNaN(earlyReleaseGraceSeconds) || + double.IsInfinity(earlyReleaseGraceSeconds)) + throw new ArgumentOutOfRangeException(nameof(earlyReleaseGraceSeconds)); + EarlyReleaseGraceSeconds = earlyReleaseGraceSeconds; + } + + public double EarlyReleaseGraceSeconds { get; } + + public bool Accepts(double releaseTime, double tailTime) => + releaseTime + EarlyReleaseGraceSeconds + 0.000001 >= tailTime; + } + + public sealed class LongNoteTickPolicy + { + public const double SixteenthNoteBeats = 0.25; + public static LongNoteTickPolicy MusicalSixteenths { get; } = + new LongNoteTickPolicy(SixteenthNoteBeats); + + public LongNoteTickPolicy(double beatInterval) + { + if (!(beatInterval > 0) || double.IsNaN(beatInterval) || double.IsInfinity(beatInterval)) + throw new ArgumentOutOfRangeException(nameof(beatInterval)); + BeatInterval = beatInterval; + } + + public double BeatInterval { get; } + + public double[] CreateTickTimes(PlayableNote note, TempoMap tempoMap) + { + if (note?.IsLong != true) + return Array.Empty(); + if (tempoMap == null) + throw new ArgumentNullException(nameof(tempoMap)); + + var hasMusicalPosition = IsFinite(note.StartBeat) && + IsFinite(note.EndBeat) && + note.EndBeat > note.StartBeat; + var startBeat = hasMusicalPosition + ? note.StartBeat + : tempoMap.TimeToBeat(note.StartTime); + var endBeat = hasMusicalPosition + ? note.EndBeat + : tempoMap.TimeToBeat(note.EndTime); + if (endBeat <= startBeat) + return Array.Empty(); + + var tickCount = (int)Math.Floor((endBeat - startBeat + 0.000000001) / BeatInterval); + if (tickCount <= 0) + return Array.Empty(); + + var result = new List(tickCount); + for (var tick = 1; tick <= tickCount; tick++) + { + var beat = startBeat + tick * BeatInterval; + if (beat > endBeat + 0.000000001) + break; + result.Add(Math.Min(note.EndTime, tempoMap.BeatToTime(beat))); + } + return result.ToArray(); + } + + private static bool IsFinite(double value) => + !double.IsNaN(value) && !double.IsInfinity(value); + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Judgement/LongNotePolicies.cs.meta b/Assets/ProjectQuint/Runtime/Core/Judgement/LongNotePolicies.cs.meta new file mode 100644 index 0000000..37e37e1 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Judgement/LongNotePolicies.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a557f3222278452785f524b4dd244e27 diff --git a/Assets/ProjectQuint/Runtime/Core/Judgement/RhythmJudgementEngine.cs b/Assets/ProjectQuint/Runtime/Core/Judgement/RhythmJudgementEngine.cs new file mode 100644 index 0000000..ce109cb --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Judgement/RhythmJudgementEngine.cs @@ -0,0 +1,575 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProjectQuint.Core.Charts; +using ProjectQuint.Core.Timing; + +namespace ProjectQuint.Core.Judgement +{ + public enum JudgementGrade + { + None, + Kool, + Cool, + Good, + Miss, + } + + public enum RuntimeNoteState + { + Pending, + Holding, + BrokenHolding, + Completed, + Failed, + } + + public readonly struct JudgementResult + { + public JudgementResult( + int noteId, + JudgementGrade grade, + double offsetSeconds, + bool longNoteBreak = false, + bool isHoldTick = false, + bool suppressGameplayPenalty = false) + { + NoteId = noteId; + Grade = grade; + OffsetSeconds = offsetSeconds; + LongNoteBreak = longNoteBreak; + IsHoldTick = isHoldTick; + SuppressGameplayPenalty = suppressGameplayPenalty; + } + + public int NoteId { get; } + public JudgementGrade Grade { get; } + public double OffsetSeconds { get; } + public bool LongNoteBreak { get; } + public bool IsHoldTick { get; } + public bool SuppressGameplayPenalty { get; } + public bool IsEarly => OffsetSeconds < 0; + } + + public readonly struct TimingSample + { + public TimingSample(double songTime, double offsetSeconds) + { + SongTime = songTime; + OffsetSeconds = offsetSeconds; + } + + public double SongTime { get; } + public double OffsetSeconds { get; } + } + + public sealed class JudgementProfile + { + public static JudgementProfile ProjectQuint { get; } = new JudgementProfile( + koolSeconds: 0.042, + coolSeconds: 0.083, + goodSeconds: 0.125); + + public JudgementProfile(double koolSeconds, double coolSeconds, double goodSeconds) + { + if (!(koolSeconds > 0 && koolSeconds <= coolSeconds && coolSeconds <= goodSeconds)) + throw new ArgumentException("Judgement windows must be positive and ascending."); + + KoolSeconds = koolSeconds; + CoolSeconds = coolSeconds; + GoodSeconds = goodSeconds; + } + + public double KoolSeconds { get; } + public double CoolSeconds { get; } + public double GoodSeconds { get; } + + public JudgementGrade Grade(double absoluteOffset) + { + if (absoluteOffset <= KoolSeconds) return JudgementGrade.Kool; + if (absoluteOffset <= CoolSeconds) return JudgementGrade.Cool; + if (absoluteOffset <= GoodSeconds) return JudgementGrade.Good; + return JudgementGrade.Miss; + } + } + + public sealed class RhythmJudgementEngine + { + private const int MaxTimingSamples = 24; + public const int TimingHistogramBinCount = 25; + public const double PerfectTimingNeutralSeconds = 0.022; + public const double LongNoteReleaseGraceSeconds = 0.100; + private readonly JudgementProfile _profile; + private readonly LongNoteReleasePolicy _longNoteReleasePolicy; + private readonly Func _suppressMissPenalty; + private readonly List _notes; + private readonly List[] _notesByLane; + private readonly Dictionary _notesById; + private readonly List _recentTimingSamples = new List(MaxTimingSamples); + private readonly int[] _timingHistogram = new int[TimingHistogramBinCount]; + private readonly int[] _lanePendingCursors; + private readonly int[] _judgementCounts = new int[Enum.GetValues(typeof(JudgementGrade)).Length]; + private readonly RuntimeNote[] _activeLongNotes; + private readonly List _failedLongNotes; + private readonly int _totalScoringUnitCount; + private int _missCursor; + private int _unfinishedNoteCount; + private int _accuracyJudgedNotes; + private long _accuracyValueTotal; + private int _timingSampleCount; + private int _suppressedMissCount; + private double _timingOffsetTotalSeconds; + + public event Action Judged; + + public RhythmJudgementEngine( + IEnumerable notes, + JudgementProfile profile = null, + int laneCount = 0, + TempoMap tempoMap = null, + LongNoteTickPolicy longNoteTickPolicy = null, + LongNoteReleasePolicy longNoteReleasePolicy = null, + Func suppressMissPenalty = null) + { + _profile = profile ?? JudgementProfile.ProjectQuint; + var effectiveTempoMap = tempoMap ?? TempoMap.Constant(150.0); + var effectiveTickPolicy = longNoteTickPolicy ?? LongNoteTickPolicy.MusicalSixteenths; + _longNoteReleasePolicy = longNoteReleasePolicy ?? LongNoteReleasePolicy.DjmaxLike; + _suppressMissPenalty = suppressMissPenalty; + _notes = (notes ?? Array.Empty()) + .Select(note => new RuntimeNote( + note, + effectiveTickPolicy.CreateTickTimes(note, effectiveTempoMap))) + .OrderBy(note => note.Source.StartTime) + .ThenBy(note => note.Source.Lane) + .ToList(); + _notesById = _notes.ToDictionary(note => note.Source.Id); + var inferredLaneCount = _notes.Count == 0 + ? 5 + : _notes.Max(note => note.Source.Lane) + 1; + laneCount = laneCount > 0 ? laneCount : inferredLaneCount; + if (laneCount < 1 || laneCount > 8) + throw new ArgumentOutOfRangeException(nameof(laneCount), "Gameplay lane count must be between 1 and 8."); + _notesByLane = new List[laneCount]; + _lanePendingCursors = new int[laneCount]; + _activeLongNotes = new RuntimeNote[laneCount]; + _failedLongNotes = new List(laneCount); + for (var lane = 0; lane < _notesByLane.Length; lane++) + _notesByLane[lane] = new List(); + foreach (var note in _notes) + { + if (note.Source.Lane >= 0 && note.Source.Lane < _notesByLane.Length) + _notesByLane[note.Source.Lane].Add(note); + } + _totalScoringUnitCount = _notes.Sum(note => 1 + note.TotalHoldTickCount); + _unfinishedNoteCount = _notes.Count; + } + + public int Combo { get; private set; } + public int MaxCombo { get; private set; } + public int Score => _totalScoringUnitCount == 0 + ? 0 + : (int)Math.Round( + _accuracyValueTotal * 1_000_000.0 / (_totalScoringUnitCount * 1000.0), + MidpointRounding.AwayFromZero) + PurePerfectCount; + public int TotalScoringUnitCount => _totalScoringUnitCount; + public int PurePerfectCount { get; private set; } + public int KoolCount => GetJudgementCount(JudgementGrade.Kool); + public int CoolCount => GetJudgementCount(JudgementGrade.Cool); + public int GoodCount => GetJudgementCount(JudgementGrade.Good); + public int MissCount => GetJudgementCount(JudgementGrade.Miss); + public int VisibleMissCount => Math.Max(0, MissCount - _suppressedMissCount); + public int FastCount { get; private set; } + public int LateCount { get; private set; } + public JudgementResult? LastResult { get; private set; } + public double LastResultTime { get; private set; } = double.NegativeInfinity; + public int ResultSequence { get; private set; } + public IReadOnlyList RecentTimingSamples => _recentTimingSamples; + public IReadOnlyList TimingHistogram => _timingHistogram; + public double PerfectTimingWindowSeconds => _profile.KoolSeconds; + public double TimingWindowSeconds => _profile.GoodSeconds; + public double AverageTimingOffsetMilliseconds => _timingSampleCount == 0 + ? 0 + : _timingOffsetTotalSeconds * 1000.0 / _timingSampleCount; + public int JudgedNoteCount => _accuracyJudgedNotes; + public double AccuracyPercent => _accuracyJudgedNotes == 0 + ? 100.0 + : _accuracyValueTotal * 100.0 / (_accuracyJudgedNotes * 1000.0); + public bool IsFinished => _unfinishedNoteCount == 0; + + public void RestoreCourseCombo(int combo, int maxCombo) + { + Combo = Math.Max(0, combo); + MaxCombo = Math.Max(Combo, maxCombo); + } + + public static bool IsPerfectTimingNeutral(double offsetSeconds) => + Math.Abs(offsetSeconds) <= PerfectTimingNeutralSeconds + 0.000000001; + + public RuntimeNoteState GetState(int noteId) + { + return _notesById.TryGetValue(noteId, out var note) ? note.State : RuntimeNoteState.Completed; + } + + public int GetJudgementCount(JudgementGrade grade) + { + var index = (int)grade; + return index > (int)JudgementGrade.None && index < _judgementCounts.Length + ? _judgementCounts[index] + : 0; + } + + public JudgementResult? Press(int lane, double songTime, double timingOffsetSeconds = 0) + { + if (lane < 0 || lane >= _notesByLane.Length) + return null; + + var judgementTime = songTime + timingOffsetSeconds; + ProcessPendingMisses(judgementTime, songTime); + ProcessLongNoteTail(lane, songTime); + AdvanceLaneCursor(lane); + + RuntimeNote candidate = null; + var candidateOffset = 0.0; + var bestAbsoluteOffset = double.PositiveInfinity; + var laneNotes = _notesByLane[lane]; + for (var index = _lanePendingCursors[lane]; index < laneNotes.Count; index++) + { + var note = laneNotes[index]; + if (note.Source.StartTime > judgementTime + _profile.GoodSeconds) + break; + if (note.State != RuntimeNoteState.Pending) + continue; + + var offset = judgementTime - note.Source.StartTime; + var absoluteOffset = Math.Abs(offset); + if (absoluteOffset > _profile.GoodSeconds || absoluteOffset >= bestAbsoluteOffset) + continue; + + candidate = note; + candidateOffset = offset; + bestAbsoluteOffset = absoluteOffset; + } + + if (candidate == null) + return null; + + var grade = _profile.Grade(bestAbsoluteOffset); + if (candidate.Source.IsLong) + { + candidate.State = RuntimeNoteState.Holding; + _activeLongNotes[lane] = candidate; + } + else + { + candidate.State = RuntimeNoteState.Completed; + FinishNote(candidate); + } + ApplyHitScore(grade); + RecordAccuracy(candidate, grade); + if (grade == JudgementGrade.Kool && IsPerfectTimingNeutral(candidateOffset)) + PurePerfectCount++; + RecordTiming(candidateOffset, songTime); + AdvanceLaneCursor(lane); + + var result = new JudgementResult(candidate.Source.Id, grade, candidateOffset); + SetLastResult(result, songTime); + return result; + } + + public JudgementResult? Release(int lane, double songTime) + { + if (lane < 0 || lane >= _activeLongNotes.Length) + return null; + + var held = _activeLongNotes[lane]; + if (held == null || held.State != RuntimeNoteState.Holding) + return null; + + AwardHoldComboTicks(held, Math.Min(songTime, held.Source.EndTime)); + if (!_longNoteReleasePolicy.Accepts(songTime, held.Source.EndTime)) + { + held.State = RuntimeNoteState.BrokenHolding; + Combo = 0; + FinalizeUnawardedHoldTicks(held); + var failure = new JudgementResult( + held.Source.Id, + JudgementGrade.Miss, + songTime - held.Source.EndTime, + longNoteBreak: true); + SetLastResult(failure, songTime); + return failure; + } + + AwardHoldComboTicks(held, held.Source.EndTime); + held.State = RuntimeNoteState.Completed; + _activeLongNotes[lane] = null; + FinishNote(held); + return null; + } + + public void Update(double songTime, double timingOffsetSeconds = 0) + { + var judgementTime = songTime + timingOffsetSeconds; + ProcessPendingMisses(judgementTime, songTime); + ProcessFailedLongNoteTails(songTime); + for (var lane = 0; lane < _activeLongNotes.Length; lane++) + ProcessLongNoteTail(lane, songTime); + } + + private void ProcessFailedLongNoteTails(double songTime) + { + for (var index = _failedLongNotes.Count - 1; index >= 0; index--) + { + var note = _failedLongNotes[index]; + if (songTime < note.Source.EndTime) + continue; + + note.State = RuntimeNoteState.Failed; + FinishNote(note); + _failedLongNotes.RemoveAt(index); + } + } + + private void ProcessLongNoteTail(int lane, double judgementTime) + { + var note = _activeLongNotes[lane]; + if (note == null) + return; + + if (note.State == RuntimeNoteState.Holding) + AwardHoldComboTicks(note, Math.Min(judgementTime, note.Source.EndTime)); + if (judgementTime < note.Source.EndTime) + return; + + note.State = note.State == RuntimeNoteState.Holding + ? RuntimeNoteState.Completed + : RuntimeNoteState.Failed; + _activeLongNotes[lane] = null; + FinishNote(note); + } + + private void AwardHoldComboTicks(RuntimeNote note, double heldUntilTime) + { + if (note.State != RuntimeNoteState.Holding) + return; + + heldUntilTime = Math.Min(heldUntilTime, note.Source.EndTime); + var earnedTickCount = note.AwardedHoldTickCount; + while (earnedTickCount < note.HoldTickTimes.Length && + note.HoldTickTimes[earnedTickCount] <= heldUntilTime + 0.000001) + earnedTickCount++; + var newTickCount = earnedTickCount - note.AwardedHoldTickCount; + if (newTickCount <= 0) + return; + + var firstNewTick = note.AwardedHoldTickCount + 1; + note.AwardedHoldTickCount = earnedTickCount; + Combo += newTickCount; + MaxCombo = Math.Max(MaxCombo, Combo); + + // A COOL-or-better head confirms that the player entered the long note + // cleanly. Every subsequently held tick is therefore an exact KOOL/Pure + // unit. Lower head grades stay lower so a late/early entry cannot be + // repaired into a perfect long note merely by continuing to hold it. + var headWasCoolOrBetter = note.FinalGrade == JudgementGrade.Kool || + note.FinalGrade == JudgementGrade.Cool; + var tickGrade = headWasCoolOrBetter + ? JudgementGrade.Kool + : note.FinalGrade; + RecordScoringUnits(tickGrade, newTickCount); + if (headWasCoolOrBetter) + PurePerfectCount += newTickCount; + + // Ticks are real score/combo judgements and must retrigger the HUD. They + // are marked separately so presentation can react while the gauge does + // not receive recovery or damage a second time for the same long note. + for (var tick = firstNewTick; tick <= earnedTickCount; tick++) + { + var tickSongTime = note.HoldTickTimes[tick - 1]; + SetLastResult( + new JudgementResult( + note.Source.Id, + tickGrade, + offsetSeconds: 0, + isHoldTick: true), + tickSongTime); + } + } + + private void ProcessPendingMisses(double judgementTime, double songTime) + { + while (_missCursor < _notes.Count) + { + var note = _notes[_missCursor]; + if (judgementTime <= note.Source.StartTime + _profile.GoodSeconds) + break; + + _missCursor++; + if (note.State != RuntimeNoteState.Pending) + continue; + + var deadLongNote = note.Source.IsLong && songTime < note.Source.EndTime; + note.State = deadLongNote + ? RuntimeNoteState.BrokenHolding + : RuntimeNoteState.Failed; + if (deadLongNote) + _failedLongNotes.Add(note); + else + FinishNote(note); + var suppressGameplayPenalty = + _suppressMissPenalty?.Invoke(note.Source) == true; + if (!suppressGameplayPenalty) + Combo = 0; + var remainingHoldTicks = Math.Max( + 0, + note.TotalHoldTickCount - note.AwardedHoldTickCount); + RecordAccuracy(note, JudgementGrade.Miss); + FinalizeUnawardedHoldTicks(note); + if (suppressGameplayPenalty) + { + // Assisted scratch misses remain part of the final score and + // result breakdown, but must be completely silent during play: + // no MISS judgement event, no combo-break pulse, and no live + // MISS counter increment. + _suppressedMissCount += 1 + remainingHoldTicks; + if (note.Source.Lane >= 0 && note.Source.Lane < _notesByLane.Length) + AdvanceLaneCursor(note.Source.Lane); + continue; + } + SetLastResult( + new JudgementResult( + note.Source.Id, + JudgementGrade.Miss, + judgementTime - note.Source.StartTime), + songTime); + if (note.Source.Lane >= 0 && note.Source.Lane < _notesByLane.Length) + AdvanceLaneCursor(note.Source.Lane); + } + } + + private void AdvanceLaneCursor(int lane) + { + var notes = _notesByLane[lane]; + var cursor = _lanePendingCursors[lane]; + while (cursor < notes.Count && notes[cursor].State != RuntimeNoteState.Pending) + cursor++; + _lanePendingCursors[lane] = cursor; + } + + private void FinishNote(RuntimeNote note) + { + if (note.IsFinished) + return; + + note.IsFinished = true; + _unfinishedNoteCount--; + } + + private void ApplyHitScore(JudgementGrade grade) + { + Combo++; + MaxCombo = Math.Max(MaxCombo, Combo); + } + + private void RecordAccuracy(RuntimeNote note, JudgementGrade grade) + { + if (note.AccuracyValue.HasValue) + { + _accuracyValueTotal -= note.AccuracyValue.Value; + _judgementCounts[(int)note.FinalGrade]--; + } + else + { + _accuracyJudgedNotes++; + } + + var value = GradeValue(grade); + note.FinalGrade = grade; + note.AccuracyValue = value; + _accuracyValueTotal += value; + _judgementCounts[(int)grade]++; + } + + private void FinalizeUnawardedHoldTicks(RuntimeNote note) + { + if (note == null || note.HoldTicksFinalized) + return; + + note.HoldTicksFinalized = true; + var remaining = note.TotalHoldTickCount - note.AwardedHoldTickCount; + if (remaining > 0) + RecordScoringUnits(JudgementGrade.Miss, remaining); + } + + private void RecordScoringUnits(JudgementGrade grade, int count) + { + if (count <= 0 || grade == JudgementGrade.None) + return; + + _accuracyJudgedNotes += count; + _accuracyValueTotal += (long)GradeValue(grade) * count; + _judgementCounts[(int)grade] += count; + } + + private void SetLastResult(JudgementResult result, double songTime) + { + LastResult = result; + LastResultTime = songTime; + ResultSequence++; + Judged?.Invoke(result); + } + + private void RecordTiming(double offsetSeconds, double songTime) + { + _timingSampleCount++; + _timingOffsetTotalSeconds += offsetSeconds; + var normalizedOffset = Math.Max(-1.0, Math.Min(1.0, offsetSeconds / _profile.GoodSeconds)); + var histogramIndex = (int)Math.Round( + (normalizedOffset + 1.0) * 0.5 * (TimingHistogramBinCount - 1), + MidpointRounding.AwayFromZero); + _timingHistogram[Math.Max(0, Math.Min(TimingHistogramBinCount - 1, histogramIndex))]++; + if (IsPerfectTimingNeutral(offsetSeconds)) + return; + + if (offsetSeconds < 0) + FastCount++; + else if (offsetSeconds > 0) + LateCount++; + + if (_recentTimingSamples.Count == MaxTimingSamples) + _recentTimingSamples.RemoveAt(0); + _recentTimingSamples.Add(new TimingSample(songTime, offsetSeconds)); + } + + private static int GradeValue(JudgementGrade grade) + { + switch (grade) + { + case JudgementGrade.Kool: return 1000; + case JudgementGrade.Cool: return 700; + case JudgementGrade.Good: return 200; + default: return 0; + } + } + + private sealed class RuntimeNote + { + public RuntimeNote(PlayableNote source, double[] holdTickTimes) + { + Source = source; + HoldTickTimes = holdTickTimes ?? Array.Empty(); + } + + public PlayableNote Source { get; } + public RuntimeNoteState State { get; set; } + public JudgementGrade FinalGrade { get; set; } + public int? AccuracyValue { get; set; } + public bool IsFinished { get; set; } + public int AwardedHoldTickCount { get; set; } + public double[] HoldTickTimes { get; } + public int TotalHoldTickCount => HoldTickTimes.Length; + public bool HoldTicksFinalized { get; set; } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Judgement/RhythmJudgementEngine.cs.meta b/Assets/ProjectQuint/Runtime/Core/Judgement/RhythmJudgementEngine.cs.meta new file mode 100644 index 0000000..aea883f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Judgement/RhythmJudgementEngine.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 97c17f28bde3e452bb4ad0bf51210d12 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Progression.meta b/Assets/ProjectQuint/Runtime/Core/Progression.meta new file mode 100644 index 0000000..c44875a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Progression.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dc48a4e4f54c34f28870b1b3532ca1f0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Progression/ProfileCollection.cs b/Assets/ProjectQuint/Runtime/Core/Progression/ProfileCollection.cs new file mode 100644 index 0000000..2600d86 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Progression/ProfileCollection.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; + +namespace ProjectQuint.Core.Progression +{ + public readonly struct CollectionGroupSummary + { + public CollectionGroupSummary( + string id, + string displayName, + int played, + int normalClear, + int hardClear, + int failed, + int fullCombo, + int allPure) + { + Id = id ?? string.Empty; + DisplayName = string.IsNullOrWhiteSpace(displayName) ? "UNKNOWN" : displayName; + Played = Math.Max(0, played); + NormalClear = Math.Max(0, normalClear); + HardClear = Math.Max(0, hardClear); + Failed = Math.Max(0, failed); + FullCombo = Math.Max(0, fullCombo); + AllPure = Math.Max(0, allPure); + } + + public string Id { get; } + public string DisplayName { get; } + public int Played { get; } + public int NormalClear { get; } + public int HardClear { get; } + public int Failed { get; } + public int FullCombo { get; } + public int AllPure { get; } + public int Cleared => NormalClear + HardClear; + public double ClearRate => Played <= 0 ? 0.0 : Cleared * 100.0 / Played; + } + + public sealed class ProfileCollectionSnapshot + { + public static readonly ProfileCollectionSnapshot Empty = new ProfileCollectionSnapshot( + default, + Array.Empty(), + Array.Empty(), + Array.Empty()); + + public ProfileCollectionSnapshot( + CollectionGroupSummary total, + IReadOnlyList packs, + IReadOnlyList keyModes, + IReadOnlyList difficulties) + { + Total = total; + Packs = packs ?? Array.Empty(); + KeyModes = keyModes ?? Array.Empty(); + Difficulties = difficulties ?? Array.Empty(); + } + + public CollectionGroupSummary Total { get; } + public IReadOnlyList Packs { get; } + public IReadOnlyList KeyModes { get; } + public IReadOnlyList Difficulties { get; } + } + + public readonly struct CourseCollectionEntry + { + public CourseCollectionEntry( + string courseId, + string providerDisplayName, + string courseName, + bool cleared, + int playCount, + int clearCount, + int bestStageReached, + double bestAccuracyPercent, + int bestTrophyRank, + string bestTrophyName) + { + CourseId = courseId ?? string.Empty; + ProviderDisplayName = providerDisplayName ?? string.Empty; + CourseName = string.IsNullOrWhiteSpace(courseName) ? "UNKNOWN COURSE" : courseName; + Cleared = cleared; + PlayCount = Math.Max(0, playCount); + ClearCount = Math.Max(0, clearCount); + BestStageReached = Math.Max(0, bestStageReached); + BestAccuracyPercent = Math.Max(0.0, Math.Min(100.0, bestAccuracyPercent)); + BestTrophyRank = Math.Max(0, bestTrophyRank); + BestTrophyName = bestTrophyName ?? string.Empty; + } + + public string CourseId { get; } + public string ProviderDisplayName { get; } + public string CourseName { get; } + public bool Cleared { get; } + public int PlayCount { get; } + public int ClearCount { get; } + public int BestStageReached { get; } + public double BestAccuracyPercent { get; } + public int BestTrophyRank { get; } + public string BestTrophyName { get; } + } + + public readonly struct CourseCertificationBadge + { + public CourseCertificationBadge(CourseCollectionEntry course) + { + CourseId = course.CourseId; + ProviderDisplayName = course.ProviderDisplayName; + CourseName = course.CourseName; + BestAccuracyPercent = course.BestAccuracyPercent; + TrophyRank = course.BestTrophyRank; + TrophyName = course.BestTrophyName; + } + + public string CourseId { get; } + public string ProviderDisplayName { get; } + public string CourseName { get; } + public double BestAccuracyPercent { get; } + public int TrophyRank { get; } + public string TrophyName { get; } + public string CertificationLabel => string.IsNullOrWhiteSpace(ProviderDisplayName) + ? CourseName + : $"{ProviderDisplayName} // {CourseName}"; + } + + public sealed class ProfileCourseSnapshot + { + public static readonly ProfileCourseSnapshot Empty = new ProfileCourseSnapshot( + 0, + 0, + 0, + Array.Empty()); + + public ProfileCourseSnapshot( + int recordedCourses, + int clearedCourses, + int totalPlayCount, + IReadOnlyList entries, + string featuredBadgeCourseId = null) + { + RecordedCourses = Math.Max(0, recordedCourses); + ClearedCourses = Math.Max(0, clearedCourses); + TotalPlayCount = Math.Max(0, totalPlayCount); + Entries = entries ?? Array.Empty(); + CertificationBadges = BuildCertificationBadges(Entries); + FeaturedBadgeCourseId = ResolveFeaturedBadgeCourseId( + CertificationBadges, + featuredBadgeCourseId); + } + + public int RecordedCourses { get; } + public int ClearedCourses { get; } + public int TotalPlayCount { get; } + public IReadOnlyList Entries { get; } + public IReadOnlyList CertificationBadges { get; } + public string FeaturedBadgeCourseId { get; } + public int BadgeCount => CertificationBadges.Count; + public bool HasFeaturedBadge => BadgeCount > 0; + public CourseCertificationBadge FeaturedBadge + { + get + { + foreach (var badge in CertificationBadges) + { + if (string.Equals(badge.CourseId, FeaturedBadgeCourseId, StringComparison.OrdinalIgnoreCase)) + return badge; + } + return default; + } + } + + private static IReadOnlyList BuildCertificationBadges( + IReadOnlyList entries) + { + var badges = new List(); + foreach (var entry in entries ?? Array.Empty()) + { + if (entry.Cleared) + badges.Add(new CourseCertificationBadge(entry)); + } + return badges; + } + + private static string ResolveFeaturedBadgeCourseId( + IReadOnlyList badges, + string requestedCourseId) + { + var requested = (requestedCourseId ?? string.Empty).Trim(); + foreach (var badge in badges ?? Array.Empty()) + { + if (string.Equals(badge.CourseId, requested, StringComparison.OrdinalIgnoreCase)) + return badge.CourseId; + } + return badges != null && badges.Count > 0 ? badges[0].CourseId : string.Empty; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Progression/ProfileCollection.cs.meta b/Assets/ProjectQuint/Runtime/Core/Progression/ProfileCollection.cs.meta new file mode 100644 index 0000000..649bb9d --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Progression/ProfileCollection.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: abc0e008a277e4267aad53209773e6bb \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/Progression/ProfileProgression.cs b/Assets/ProjectQuint/Runtime/Core/Progression/ProfileProgression.cs new file mode 100644 index 0000000..8491b20 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Progression/ProfileProgression.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using ProjectQuint.Core.Charts; + +namespace ProjectQuint.Core.Progression +{ + public readonly struct RatingContribution + { + public RatingContribution(string chartId, double playRating) + { + ChartId = chartId ?? string.Empty; + PlayRating = Math.Max(0.0, playRating); + } + + public string ChartId { get; } + public double PlayRating { get; } + } + + public readonly struct RatingChartEntry + { + public RatingChartEntry( + string chartId, + string title, + string artist, + string difficultyLabel, + double accuracyPercent, + bool hardClear, + double playRating) + { + ChartId = chartId ?? string.Empty; + Title = string.IsNullOrWhiteSpace(title) ? "UNKNOWN CHART" : title; + Artist = artist ?? string.Empty; + DifficultyLabel = difficultyLabel ?? string.Empty; + AccuracyPercent = Math.Max(0.0, Math.Min(100.0, accuracyPercent)); + HardClear = hardClear; + PlayRating = Math.Max(0.0, playRating); + } + + public string ChartId { get; } + public string Title { get; } + public string Artist { get; } + public string DifficultyLabel { get; } + public double AccuracyPercent { get; } + public bool HardClear { get; } + public double PlayRating { get; } + } + + public readonly struct RatingSummary + { + public RatingSummary( + string providerId, + string providerDisplayName, + int keyCount, + bool hasScratch, + double value, + int filledSlots, + IReadOnlyList bestPlays = null, + int activityPlayCount = 0) + { + ProviderId = (providerId ?? string.Empty).Trim(); + ProviderDisplayName = string.IsNullOrWhiteSpace(providerDisplayName) + ? ProviderId + : providerDisplayName.Trim(); + KeyCount = Math.Max(0, keyCount); + HasScratch = hasScratch; + Value = Math.Max(0.0, value); + FilledSlots = Math.Max(0, Math.Min(RatingRules.BestCount, filledSlots)); + BestPlays = bestPlays ?? Array.Empty(); + ActivityPlayCount = Math.Max(0, activityPlayCount); + } + + public string ProviderId { get; } + public string ProviderDisplayName { get; } + public int KeyCount { get; } + public bool HasScratch { get; } + public double Value { get; } + public int FilledSlots { get; } + public IReadOnlyList BestPlays { get; } + public int ActivityPlayCount { get; } + public bool HasContext => + !string.IsNullOrWhiteSpace(ProviderId) && + KeyCount >= 4 && KeyCount <= 7; + + public string ContextLabel => !HasContext + ? "RATING" + : $"{ProviderDisplayName.ToUpperInvariant()} {RatingRules.KeyModeLabel(KeyCount, HasScratch)} RATING"; + } + + public readonly struct RatingUpdate + { + public RatingUpdate( + bool eligible, + double playRating, + RatingSummary previous, + RatingSummary current) + { + Eligible = eligible; + PlayRating = Math.Max(0.0, playRating); + Previous = previous; + Current = current; + } + + public bool Eligible { get; } + public double PlayRating { get; } + public RatingSummary Previous { get; } + public RatingSummary Current { get; } + public double Delta => Current.Value - Previous.Value; + } + + public readonly struct ProfileProgressSnapshot + { + public ProfileProgressSnapshot( + string displayName, + long totalXp, + int validPlayCount, + double activePlaySeconds, + string lastRatingProviderId, + string lastRatingProviderDisplayName, + int lastRatingKeyCount, + bool lastRatingHasScratch) + { + DisplayName = string.IsNullOrWhiteSpace(displayName) ? "PLAYER" : displayName; + TotalXp = Math.Max(0, totalXp); + Level = ProfileLevelRules.LevelForTotalXp(TotalXp); + LevelStartXp = ProfileLevelRules.TotalXpForLevel(Level); + NextLevelXp = ProfileLevelRules.TotalXpForLevel(Level + 1); + ValidPlayCount = Math.Max(0, validPlayCount); + ActivePlaySeconds = Math.Max(0.0, activePlaySeconds); + LastRatingProviderId = (lastRatingProviderId ?? string.Empty).Trim(); + LastRatingProviderDisplayName = string.IsNullOrWhiteSpace(lastRatingProviderDisplayName) + ? LastRatingProviderId + : lastRatingProviderDisplayName.Trim(); + LastRatingKeyCount = Math.Max(0, lastRatingKeyCount); + LastRatingHasScratch = lastRatingHasScratch; + } + + public string DisplayName { get; } + public long TotalXp { get; } + public int Level { get; } + public long LevelStartXp { get; } + public long NextLevelXp { get; } + public int ValidPlayCount { get; } + public double ActivePlaySeconds { get; } + public string LastRatingProviderId { get; } + public string LastRatingProviderDisplayName { get; } + public int LastRatingKeyCount { get; } + public bool LastRatingHasScratch { get; } + public long XpIntoLevel => TotalXp - LevelStartXp; + public long XpForLevel => Math.Max(1, NextLevelXp - LevelStartXp); + public double LevelProgress => Math.Max(0.0, Math.Min(1.0, XpIntoLevel / (double)XpForLevel)); + } + + public readonly struct ProfileProgressUpdate + { + public ProfileProgressUpdate( + bool applied, + int xpAwarded, + ProfileProgressSnapshot previous, + ProfileProgressSnapshot current, + bool firstPlay, + bool firstNormalClear, + bool firstHardClear, + bool firstFullCombo, + bool firstAllPure) + { + Applied = applied; + XpAwarded = Math.Max(0, xpAwarded); + Previous = previous; + Current = current; + FirstPlay = firstPlay; + FirstNormalClear = firstNormalClear; + FirstHardClear = firstHardClear; + FirstFullCombo = firstFullCombo; + FirstAllPure = firstAllPure; + } + + public bool Applied { get; } + public int XpAwarded { get; } + public ProfileProgressSnapshot Previous { get; } + public ProfileProgressSnapshot Current { get; } + public bool FirstPlay { get; } + public bool FirstNormalClear { get; } + public bool FirstHardClear { get; } + public bool FirstFullCombo { get; } + public bool FirstAllPure { get; } + public bool LeveledUp => Current.Level > Previous.Level; + } + + public static class RatingRules + { + public const int RulesVersion = 5; + public const int BestCount = 30; + public const double HardClearBonus = 0.50; + public const double MinimumChartConstant = 0.50; + + private static readonly double[] AccuracyAnchors = + { + 0.0, 80.0, 90.0, 95.0, 97.0, 98.0, 99.0, 99.5, 100.0, + }; + + private static readonly double[] FactorAnchors = + { + 0.0, 0.30, 0.60, 0.80, 0.88, 0.93, 0.97, 0.985, 1.0, + }; + + public static double AccuracyFactor(double accuracyPercent) + { + var accuracy = Math.Max(0.0, Math.Min(100.0, accuracyPercent)); + for (var index = 1; index < AccuracyAnchors.Length; index++) + { + if (accuracy > AccuracyAnchors[index]) + continue; + + var start = AccuracyAnchors[index - 1]; + var end = AccuracyAnchors[index]; + var progress = end <= start ? 0.0 : (accuracy - start) / (end - start); + return FactorAnchors[index - 1] + + (FactorAnchors[index] - FactorAnchors[index - 1]) * progress; + } + return 1.0; + } + + public static double CalculatePlayRating( + double chartConstant, + double accuracyPercent, + bool hardClear) + { + if (chartConstant <= 0.0) + return 0.0; + return chartConstant * AccuracyFactor(accuracyPercent) + + (hardClear ? HardClearBonus : 0.0); + } + + public static RatingSummary CalculateBest30( + string providerId, + string providerDisplayName, + int keyCount, + bool hasScratch, + IEnumerable contributions) + { + var bestByChart = new Dictionary(StringComparer.Ordinal); + foreach (var item in contributions ?? Array.Empty()) + { + if (string.IsNullOrWhiteSpace(item.ChartId) || item.PlayRating <= 0.0) + continue; + if (!bestByChart.TryGetValue(item.ChartId, out var current) || item.PlayRating > current) + bestByChart[item.ChartId] = item.PlayRating; + } + + var best = bestByChart.Values + .OrderByDescending(value => value) + .Take(BestCount) + .ToArray(); + return new RatingSummary( + providerId, + providerDisplayName, + keyCount, + hasScratch, + best.Sum() / BestCount, + best.Length); + } + + public static string KeyModeLabel(int keyCount, bool hasScratch) => + ChartKeyMode.FormatLabel(keyCount, hasScratch); + + public static bool TryResolveChartConstant( + string difficultyValue, + string difficultyLabel, + out double chartConstant) + { + if (!string.IsNullOrWhiteSpace(difficultyValue)) + return TryParseChartConstant(difficultyValue, out chartConstant); + return TryParseChartConstant(difficultyLabel, out chartConstant); + } + + public static bool TryParseChartConstant(string difficultyText, out double chartConstant) + { + chartConstant = 0.0; + var label = (difficultyText ?? string.Empty).Trim(); + if (label.Length == 0) + return false; + + var upper = label.ToUpperInvariant(); + if (upper.Contains("GIMMICK") || upper.Contains("SPECIAL") || + upper.Contains("DUMMY") || upper.Contains("OLD")) + return false; + + var numericEnd = label.Length; + var plus = false; + if (numericEnd > 0 && label[numericEnd - 1] == '+') + { + plus = true; + numericEnd--; + while (numericEnd > 0 && char.IsWhiteSpace(label[numericEnd - 1])) + numericEnd--; + } + + var start = numericEnd; + var dotSeen = false; + var digitSeen = false; + while (start > 0) + { + var character = label[start - 1]; + if (char.IsDigit(character)) + { + digitSeen = true; + start--; + continue; + } + if (character == '.' && !dotSeen) + { + dotSeen = true; + start--; + continue; + } + break; + } + if (!digitSeen) + return false; + if (start > 0 && (label[start - 1] == '-' || label[start - 1] == '.')) + return false; + + if (!double.TryParse( + label.Substring(start, numericEnd - start), + NumberStyles.AllowDecimalPoint, + CultureInfo.InvariantCulture, + out chartConstant) || + chartConstant < 0.0 || + double.IsNaN(chartConstant) || + double.IsInfinity(chartConstant)) + { + chartConstant = 0.0; + return false; + } + + // Some established tables start at level 0 (for example Satellite + // sl0). It is a real lowest tier, not a missing difficulty value. + // A positive half-step keeps it below level 1 while allowing it to + // participate in the same rating pipeline. + if (chartConstant <= 0.0) + chartConstant = MinimumChartConstant; + + if (plus && + Math.Abs(chartConstant - Math.Round(chartConstant)) < 0.000001) + chartConstant += 0.5; + return true; + } + } + + public static class ProfileLevelRules + { + public const int XpRulesVersion = 1; + + public static long TotalXpForLevel(int level) + { + var normalized = Math.Max(1, level) - 1L; + return 20L * normalized * normalized + 50L * normalized; + } + + public static int NextLevelXp(int level) => checked(40 * Math.Max(1, level) + 30); + + public static int LevelForTotalXp(long totalXp) + { + var xp = Math.Max(0L, totalXp); + var low = 1; + var high = 2; + while (TotalXpForLevel(high) <= xp && high < 1_000_000) + high *= 2; + while (low + 1 < high) + { + var middle = low + (high - low) / 2; + if (TotalXpForLevel(middle) <= xp) + low = middle; + else + high = middle; + } + return low; + } + + public static int CalculateBaseXp(double activePlaySeconds, int judgedUnits) + { + var value = 20 + + (int)Math.Floor(Math.Max(0.0, activePlaySeconds) / 10.0) * 4 + + Math.Max(0, judgedUnits) / 100 * 2; + return Math.Max(20, Math.Min(100, value)); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Progression/ProfileProgression.cs.meta b/Assets/ProjectQuint/Runtime/Core/Progression/ProfileProgression.cs.meta new file mode 100644 index 0000000..285907c --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Progression/ProfileProgression.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 459ea391724314c1397e5b3531e642d3 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Core/ProjectQuint.Core.asmdef b/Assets/ProjectQuint/Runtime/Core/ProjectQuint.Core.asmdef new file mode 100644 index 0000000..f5c1673 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/ProjectQuint.Core.asmdef @@ -0,0 +1,14 @@ +{ + "name": "ProjectQuint.Core", + "rootNamespace": "ProjectQuint.Core", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": true +} diff --git a/Assets/ProjectQuint/Runtime/Core/ProjectQuint.Core.asmdef.meta b/Assets/ProjectQuint/Runtime/Core/ProjectQuint.Core.asmdef.meta new file mode 100644 index 0000000..b373a48 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/ProjectQuint.Core.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3b5f07eda49ea4e18b3acb164cc392c1 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Release.meta b/Assets/ProjectQuint/Runtime/Core/Release.meta new file mode 100644 index 0000000..06b7913 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Release.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 823010ad9f0f4b81b0b9bbee2ff34369 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Release/ProjectQuintReleaseVersion.cs b/Assets/ProjectQuint/Runtime/Core/Release/ProjectQuintReleaseVersion.cs new file mode 100644 index 0000000..e9359e5 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Release/ProjectQuintReleaseVersion.cs @@ -0,0 +1,29 @@ +using System.Globalization; + +namespace ProjectQuint.Core.Release +{ + /// + /// Public product version metadata shared by the runtime, release tooling, and update contract. + /// Persistent-data schema versions remain independent from this product version. + /// + public static class ProjectQuintReleaseVersion + { + public const string ProductVersion = "0.1.0"; + public const string ReleaseChannel = "alpha"; + public const int ChannelRevision = 4; + public const int BuildNumber = 4; + public const string SparkleFeedUrl = + "https://projectquint.duckdns.org/updates/appcast.xml"; + public const string SparklePublicEdKey = + "w94qM58k8MAhIxYYQiR1ivyR5CyMW1tlRV68lQoLehA="; + + public static string DisplayVersion => + $"{ProductVersion}-{ReleaseChannel}.{ChannelRevision.ToString(CultureInfo.InvariantCulture)}"; + + public static string BuildNumberText => + BuildNumber.ToString(CultureInfo.InvariantCulture); + + public static string DisplayVersionWithBuild => + $"{DisplayVersion} (build {BuildNumberText})"; + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Release/ProjectQuintReleaseVersion.cs.meta b/Assets/ProjectQuint/Runtime/Core/Release/ProjectQuintReleaseVersion.cs.meta new file mode 100644 index 0000000..5e2b8a0 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Release/ProjectQuintReleaseVersion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e73b647666ee4452a7f5d0ed2735461d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Timing.meta b/Assets/ProjectQuint/Runtime/Core/Timing.meta new file mode 100644 index 0000000..99412dc --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Timing.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3efd064767d294d688e7a4fafe935965 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncCalibrationEstimator.cs b/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncCalibrationEstimator.cs new file mode 100644 index 0000000..e8c2bee --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncCalibrationEstimator.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; + +namespace ProjectQuint.Core.Timing +{ + public enum AudioSyncStability + { + Insufficient, + Good, + Fair, + Unstable, + OutOfRange, + } + + public readonly struct AudioSyncCalibrationResult + { + public AudioSyncCalibrationResult( + int recommendedLatencyMilliseconds, + double medianMilliseconds, + double medianAbsoluteDeviationMilliseconds, + int acceptedSampleCount, + int rejectedSampleCount, + AudioSyncStability stability) + { + RecommendedLatencyMilliseconds = recommendedLatencyMilliseconds; + MedianMilliseconds = medianMilliseconds; + MedianAbsoluteDeviationMilliseconds = medianAbsoluteDeviationMilliseconds; + AcceptedSampleCount = acceptedSampleCount; + RejectedSampleCount = rejectedSampleCount; + Stability = stability; + } + + public int RecommendedLatencyMilliseconds { get; } + public double MedianMilliseconds { get; } + public double MedianAbsoluteDeviationMilliseconds { get; } + public int AcceptedSampleCount { get; } + public int RejectedSampleCount { get; } + public AudioSyncStability Stability { get; } + public bool CanApply => AcceptedSampleCount >= AudioSyncCalibrationEstimator.MinimumAcceptedSamples && + Stability != AudioSyncStability.Unstable && + Stability != AudioSyncStability.OutOfRange; + } + + /// + /// Robustly estimates audible output latency from tap-minus-scheduled-click samples. + /// The estimator is deterministic and deliberately independent from Unity and FMOD. + /// + public static class AudioSyncCalibrationEstimator + { + public const int PracticeClickCount = 4; + public const int MeasurementClickCount = 24; + public const int MinimumAcceptedSamples = 18; + public const int MaximumLatencyMilliseconds = 500; + private const double MinimumOutlierThresholdMilliseconds = 30.0; + + public static AudioSyncCalibrationResult Estimate(IReadOnlyList samplesMilliseconds) + { + if (samplesMilliseconds == null) + throw new ArgumentNullException(nameof(samplesMilliseconds)); + if (samplesMilliseconds.Count == 0) + return new AudioSyncCalibrationResult( + 0, 0, 0, 0, 0, AudioSyncStability.Insufficient); + + var sorted = CopyFinite(samplesMilliseconds); + if (sorted.Count == 0) + return new AudioSyncCalibrationResult( + 0, 0, 0, 0, samplesMilliseconds.Count, AudioSyncStability.Insufficient); + + sorted.Sort(); + var initialMedian = Median(sorted); + var deviations = new List(sorted.Count); + for (var index = 0; index < sorted.Count; index++) + deviations.Add(Math.Abs(sorted[index] - initialMedian)); + deviations.Sort(); + var initialMad = Median(deviations); + var threshold = Math.Max(MinimumOutlierThresholdMilliseconds, initialMad * 3.0); + var accepted = new List(sorted.Count); + for (var index = 0; index < sorted.Count; index++) + { + if (Math.Abs(sorted[index] - initialMedian) <= threshold) + accepted.Add(sorted[index]); + } + + if (accepted.Count == 0) + return new AudioSyncCalibrationResult( + 0, initialMedian, initialMad, 0, samplesMilliseconds.Count, + AudioSyncStability.Insufficient); + + accepted.Sort(); + var median = Median(accepted); + deviations.Clear(); + for (var index = 0; index < accepted.Count; index++) + deviations.Add(Math.Abs(accepted[index] - median)); + deviations.Sort(); + var mad = Median(deviations); + var stability = median > MaximumLatencyMilliseconds + ? AudioSyncStability.OutOfRange + : accepted.Count < MinimumAcceptedSamples + ? AudioSyncStability.Insufficient + : mad <= 12.0 + ? AudioSyncStability.Good + : mad <= 22.0 + ? AudioSyncStability.Fair + : AudioSyncStability.Unstable; + var recommendation = (int)Math.Round( + Math.Max(0.0, Math.Min(MaximumLatencyMilliseconds, median)), + MidpointRounding.AwayFromZero); + return new AudioSyncCalibrationResult( + recommendation, + median, + mad, + accepted.Count, + samplesMilliseconds.Count - accepted.Count, + stability); + } + + private static List CopyFinite(IReadOnlyList values) + { + var result = new List(values.Count); + for (var index = 0; index < values.Count; index++) + { + var value = values[index]; + if (!double.IsNaN(value) && !double.IsInfinity(value)) + result.Add(value); + } + return result; + } + + private static double Median(IReadOnlyList sorted) + { + var middle = sorted.Count / 2; + return sorted.Count % 2 == 0 + ? (sorted[middle - 1] + sorted[middle]) * 0.5 + : sorted[middle]; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncCalibrationEstimator.cs.meta b/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncCalibrationEstimator.cs.meta new file mode 100644 index 0000000..ebf5c1d --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncCalibrationEstimator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f9ad2638954b4c7682a159ec2ad15d3d diff --git a/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncTapMatcher.cs b/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncTapMatcher.cs new file mode 100644 index 0000000..eef6726 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncTapMatcher.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; + +namespace ProjectQuint.Core.Timing +{ + /// + /// Matches a tap only against the next expected click. This deliberately avoids + /// nearest-neighbour matching, which can fold a large output latency onto the + /// following metronome click and report a false value near zero. + /// + public static class AudioSyncTapMatcher + { + public const double MaximumOffsetSeconds = 0.650; + + public static bool TryMatchExpected( + IReadOnlyList clickTimes, + int expectedClickIndex, + double tapTime, + out double offsetSeconds) + { + if (clickTimes == null) + throw new ArgumentNullException(nameof(clickTimes)); + offsetSeconds = 0.0; + if (expectedClickIndex < 0 || expectedClickIndex >= clickTimes.Count) + return false; + + offsetSeconds = tapTime - clickTimes[expectedClickIndex]; + return offsetSeconds >= 0.0 && offsetSeconds <= MaximumOffsetSeconds; + } + + public static bool HasExpired( + IReadOnlyList clickTimes, + int expectedClickIndex, + double currentTime) + { + if (clickTimes == null) + throw new ArgumentNullException(nameof(clickTimes)); + return expectedClickIndex >= 0 && + expectedClickIndex < clickTimes.Count && + currentTime > clickTimes[expectedClickIndex] + MaximumOffsetSeconds; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncTapMatcher.cs.meta b/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncTapMatcher.cs.meta new file mode 100644 index 0000000..8bbf493 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Timing/AudioSyncTapMatcher.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 37c9a6416f6248c39db4c32d614aa5d3 diff --git a/Assets/ProjectQuint/Runtime/Core/Timing/MusicalTimingMaps.cs b/Assets/ProjectQuint/Runtime/Core/Timing/MusicalTimingMaps.cs new file mode 100644 index 0000000..b403f61 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Timing/MusicalTimingMaps.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ProjectQuint.Core.Timing +{ + /// + /// A deterministic musical-time anchor. Time is the instant at which Beat is + /// judged, before an optional STOP at that same beat. Bpm is the tempo used + /// after that STOP and until the next node. + /// + public readonly struct TempoMapNode + { + public TempoMapNode(double beat, double time, double bpm, double stopDuration = 0) + { + if (double.IsNaN(beat) || double.IsInfinity(beat)) + throw new ArgumentOutOfRangeException(nameof(beat)); + if (double.IsNaN(time) || double.IsInfinity(time)) + throw new ArgumentOutOfRangeException(nameof(time)); + if (!(bpm > 0) || double.IsNaN(bpm) || double.IsInfinity(bpm)) + throw new ArgumentOutOfRangeException(nameof(bpm)); + if (stopDuration < 0 || double.IsNaN(stopDuration) || double.IsInfinity(stopDuration)) + throw new ArgumentOutOfRangeException(nameof(stopDuration)); + + Beat = beat; + Time = time; + Bpm = bpm; + StopDuration = stopDuration; + } + + public double Beat { get; } + public double Time { get; } + public double Bpm { get; } + public double StopDuration { get; } + public double StopEndTime => Time + StopDuration; + } + + /// + /// Converts quarter-note beat positions and DSP song seconds in both directions. + /// BeatToTime returns the pre-STOP judgement instant for an exact STOP position; + /// TimeToBeat holds that beat for the entire STOP interval. + /// + public sealed class TempoMap + { + private const double Epsilon = 0.000000001; + private readonly TempoMapNode[] _nodes; + + public TempoMap(IEnumerable nodes) + { + _nodes = (nodes ?? throw new ArgumentNullException(nameof(nodes))).ToArray(); + if (_nodes.Length == 0) + throw new ArgumentException("A tempo map requires at least one node.", nameof(nodes)); + + for (var index = 1; index < _nodes.Length; index++) + { + if (_nodes[index].Beat <= _nodes[index - 1].Beat + Epsilon) + throw new ArgumentException("Tempo-map beats must be strictly increasing.", nameof(nodes)); + if (_nodes[index].Time + Epsilon < _nodes[index - 1].StopEndTime) + throw new ArgumentException("Tempo-map times must not overlap the previous STOP.", nameof(nodes)); + } + } + + public IReadOnlyList Nodes => _nodes; + public double InitialBpm => _nodes[0].Bpm; + + public static TempoMap Constant(double bpm) => + new TempoMap(new[] { new TempoMapNode(0, 0, bpm) }); + + public double BeatToTime(double beat) + { + var index = FindNodeAtOrBeforeBeat(beat); + var node = _nodes[index]; + if (Math.Abs(beat - node.Beat) <= Epsilon) + return node.Time; + if (beat < node.Beat) + return node.Time + (beat - node.Beat) * 60.0 / node.Bpm; + return node.StopEndTime + (beat - node.Beat) * 60.0 / node.Bpm; + } + + public double TimeToBeat(double time) + { + var index = FindNodeAtOrBeforeTime(time); + var node = _nodes[index]; + if (time < node.Time - Epsilon) + return node.Beat + (time - node.Time) * node.Bpm / 60.0; + if (time <= node.StopEndTime + Epsilon && time >= node.Time - Epsilon) + return node.Beat; + return node.Beat + (time - node.StopEndTime) * node.Bpm / 60.0; + } + + public bool IsStopped(double time) + { + var node = _nodes[FindNodeAtOrBeforeTime(time)]; + return node.StopDuration > 0 && + time >= node.Time - Epsilon && + time < node.StopEndTime - Epsilon; + } + + private int FindNodeAtOrBeforeBeat(double beat) + { + if (beat < _nodes[0].Beat) + return 0; + + var low = 0; + var high = _nodes.Length - 1; + while (low <= high) + { + var middle = low + ((high - low) >> 1); + if (_nodes[middle].Beat <= beat + Epsilon) + low = middle + 1; + else + high = middle - 1; + } + return Math.Max(0, high); + } + + private int FindNodeAtOrBeforeTime(double time) + { + if (time < _nodes[0].Time) + return 0; + + var low = 0; + var high = _nodes.Length - 1; + while (low <= high) + { + var middle = low + ((high - low) >> 1); + if (_nodes[middle].Time <= time + Epsilon) + low = middle + 1; + else + high = middle - 1; + } + return Math.Max(0, high); + } + } + + public readonly struct ScrollMapNode + { + public ScrollMapNode(double beat, double multiplier) + { + if (beat < 0 || double.IsNaN(beat) || double.IsInfinity(beat)) + throw new ArgumentOutOfRangeException(nameof(beat)); + if (double.IsNaN(multiplier) || double.IsInfinity(multiplier)) + throw new ArgumentOutOfRangeException(nameof(multiplier)); + + Beat = beat; + Multiplier = multiplier; + } + + public double Beat { get; } + public double Multiplier { get; } + } + + /// + /// Visual scroll is deliberately separate from judgement time. Its coordinate + /// integrates the active BMS #SCROLL multiplier over musical beat, so BPM and + /// STOP still come from TempoMap while scroll gimmicks never alter judgement. + /// + public sealed class ScrollMap + { + private const double Epsilon = 0.000000001; + private readonly ScrollMapNode[] _nodes; + private readonly double[] _positions; + + public ScrollMap(TempoMap tempoMap) + : this(tempoMap, Array.Empty()) + { + } + + public ScrollMap(TempoMap tempoMap, IEnumerable nodes) + { + TempoMap = tempoMap ?? throw new ArgumentNullException(nameof(tempoMap)); + + var ordered = (nodes ?? throw new ArgumentNullException(nameof(nodes))) + .OrderBy(node => node.Beat) + .ToArray(); + var normalized = new List(ordered.Length + 1); + if (ordered.Length == 0 || ordered[0].Beat > Epsilon) + normalized.Add(new ScrollMapNode(0, 1)); + + foreach (var node in ordered) + { + if (normalized.Count > 0 && + Math.Abs(normalized[normalized.Count - 1].Beat - node.Beat) <= Epsilon) + normalized[normalized.Count - 1] = node; + else + normalized.Add(node); + } + + if (normalized.Count == 0) + normalized.Add(new ScrollMapNode(0, 1)); + + _nodes = normalized.ToArray(); + _positions = new double[_nodes.Length]; + for (var index = 1; index < _nodes.Length; index++) + { + _positions[index] = _positions[index - 1] + + (_nodes[index].Beat - _nodes[index - 1].Beat) * + _nodes[index - 1].Multiplier; + } + } + + public TempoMap TempoMap { get; } + public IReadOnlyList Nodes => _nodes; + public bool HasVariableScroll => + _nodes.Count(node => Math.Abs(node.Multiplier - 1.0) > Epsilon) > 0; + public bool HasReverseScroll => _nodes.Any(node => node.Multiplier < -Epsilon); + + public double PositionAtTime(double songTime) => PositionAtBeat(TempoMap.TimeToBeat(songTime)); + + public double PositionAtBeat(double beat) + { + // BMS scroll objects cannot precede measure zero. Keep the countdown + // preroll at the normal 1x rate even if a chart changes scroll at beat 0. + if (beat < 0) + return beat; + + var index = FindNodeAtOrBeforeBeat(beat); + return _positions[index] + (beat - _nodes[index].Beat) * _nodes[index].Multiplier; + } + + public double TimeAtPosition(double position) + { + if (position < 0) + return TempoMap.BeatToTime(position); + + if (TryGetTimeAtForwardDistance(TempoMap.BeatToTime(0), position, out var time)) + return time; + return double.PositiveInfinity; + } + + /// + /// Finds the first future judgement time whose visual position is the requested + /// distance ahead. Zero and reverse segments are traversed instead of being + /// treated as elapsed song time, which keeps note-window culling correct. + /// + public bool TryGetTimeAtForwardDistance(double songTime, double distance, out double time) + { + if (distance < 0 || double.IsNaN(distance) || double.IsInfinity(distance)) + throw new ArgumentOutOfRangeException(nameof(distance)); + + var beat = TempoMap.TimeToBeat(songTime); + var position = PositionAtBeat(beat); + var target = position + distance; + if (distance <= Epsilon) + { + time = songTime; + return true; + } + + if (beat < 0) + { + if (CrossesForward(position, 0, target, 1, out var fraction)) + { + time = TempoMap.BeatToTime(beat + (0 - beat) * fraction); + return true; + } + beat = 0; + position = 0; + } + + var index = FindNodeAtOrBeforeBeat(beat); + while (index + 1 < _nodes.Length) + { + var nextBeat = _nodes[index + 1].Beat; + var nextPosition = position + (nextBeat - beat) * _nodes[index].Multiplier; + if (CrossesForward(position, nextPosition, target, _nodes[index].Multiplier, out var fraction)) + { + time = TempoMap.BeatToTime(beat + (nextBeat - beat) * fraction); + return true; + } + + beat = nextBeat; + position = nextPosition; + index++; + } + + var multiplier = _nodes[index].Multiplier; + if (multiplier > Epsilon && target >= position - Epsilon) + { + var targetBeat = beat + Math.Max(0, target - position) / multiplier; + time = TempoMap.BeatToTime(targetBeat); + return true; + } + + time = double.PositiveInfinity; + return false; + } + + private static bool CrossesForward( + double start, + double end, + double target, + double multiplier, + out double fraction) + { + if (multiplier > Epsilon && target >= start - Epsilon && target <= end + Epsilon) + { + fraction = Math.Abs(end - start) <= Epsilon ? 0 : (target - start) / (end - start); + return true; + } + + fraction = 0; + return false; + } + + private int FindNodeAtOrBeforeBeat(double beat) + { + var low = 0; + var high = _nodes.Length - 1; + while (low <= high) + { + var middle = low + ((high - low) >> 1); + if (_nodes[middle].Beat <= beat + Epsilon) + low = middle + 1; + else + high = middle - 1; + } + return Math.Max(0, high); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Timing/MusicalTimingMaps.cs.meta b/Assets/ProjectQuint/Runtime/Core/Timing/MusicalTimingMaps.cs.meta new file mode 100644 index 0000000..3b3fcc6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Timing/MusicalTimingMaps.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 287be2f5502b49849f5a30773b68e98a diff --git a/Assets/ProjectQuint/Runtime/Core/Timing/ResilientSongClock.cs b/Assets/ProjectQuint/Runtime/Core/Timing/ResilientSongClock.cs new file mode 100644 index 0000000..68dfd8d --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Timing/ResilientSongClock.cs @@ -0,0 +1,95 @@ +using System; + +namespace ProjectQuint.Core.Timing +{ + public sealed class ResilientSongClock + { + public const double DspStallThresholdSeconds = 0.250; + private const double DspAdvanceEpsilonSeconds = 0.000001; + private const double RecoveryToleranceSeconds = 0.005; + + private bool _initialized; + private double _currentSongTime; + private double _lastDspTime; + private double _lastDspSongTime; + private double _lastDspAdvanceRealtime; + private double _lastSampleRealtime; + + public bool IsFallbackActive { get; private set; } + public double CurrentSongTime => _currentSongTime; + + public void Reset(double currentDspTime, double startDspTime, double currentRealtime) + { + _initialized = true; + _currentSongTime = currentDspTime - startDspTime; + _lastDspTime = currentDspTime; + _lastDspSongTime = _currentSongTime; + _lastDspAdvanceRealtime = currentRealtime; + _lastSampleRealtime = currentRealtime; + IsFallbackActive = false; + } + + public void Reanchor( + double songTime, + double currentDspTime, + double currentRealtime) + { + _initialized = true; + _currentSongTime = songTime; + _lastDspTime = currentDspTime; + _lastDspSongTime = songTime; + _lastDspAdvanceRealtime = currentRealtime; + _lastSampleRealtime = currentRealtime; + IsFallbackActive = false; + } + + public double Sample(double currentDspTime, double startDspTime, double currentRealtime) + { + if (!_initialized) + Reset(currentDspTime, startDspTime, currentRealtime); + + var realtimeDelta = Math.Max(0, currentRealtime - _lastSampleRealtime); + _lastSampleRealtime = currentRealtime; + var dspSongTime = currentDspTime - startDspTime; + var dspAdvanced = currentDspTime > _lastDspTime + DspAdvanceEpsilonSeconds; + + if (IsFallbackActive) + { + _currentSongTime += realtimeDelta; + if (dspAdvanced && dspSongTime >= _currentSongTime - RecoveryToleranceSeconds) + { + _currentSongTime = Math.Max(_currentSongTime, dspSongTime); + IsFallbackActive = false; + } + } + else if (dspAdvanced) + { + _currentSongTime = Math.Max(_currentSongTime, dspSongTime); + } + else + { + var stalledFor = Math.Max(0, currentRealtime - _lastDspAdvanceRealtime); + if (stalledFor >= DspStallThresholdSeconds) + { + _currentSongTime = Math.Max(_currentSongTime, _lastDspSongTime + stalledFor); + IsFallbackActive = true; + } + } + + if (dspAdvanced) + { + _lastDspTime = currentDspTime; + _lastDspSongTime = dspSongTime; + _lastDspAdvanceRealtime = currentRealtime; + } + + return _currentSongTime; + } + + public static double ResolveInputEventSongTime( + double inputEventTime, + double currentInputTime, + double currentSongTime) => + currentSongTime + inputEventTime - currentInputTime; + } +} diff --git a/Assets/ProjectQuint/Runtime/Core/Timing/ResilientSongClock.cs.meta b/Assets/ProjectQuint/Runtime/Core/Timing/ResilientSongClock.cs.meta new file mode 100644 index 0000000..ca32264 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Core/Timing/ResilientSongClock.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fd5a9a33c48fb42df9852b599f5a3eeb \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity.meta b/Assets/ProjectQuint/Runtime/Unity.meta new file mode 100644 index 0000000..f727b63 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f6ab15a3928614b1dae5cecabd46a518 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/AssemblyInfo.cs b/Assets/ProjectQuint/Runtime/Unity/AssemblyInfo.cs new file mode 100644 index 0000000..8fd40f2 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ProjectQuint.Application")] +[assembly: InternalsVisibleTo("ProjectQuint.Bootstrap")] +[assembly: InternalsVisibleTo("ProjectQuint.Media.Audio")] +[assembly: InternalsVisibleTo("ProjectQuint.Media.Images")] +[assembly: InternalsVisibleTo("ProjectQuint.Media.Video")] +[assembly: InternalsVisibleTo("ProjectQuint.Presentation")] +[assembly: InternalsVisibleTo("ProjectQuint.Tests.EditMode")] diff --git a/Assets/ProjectQuint/Runtime/Unity/AssemblyInfo.cs.meta b/Assets/ProjectQuint/Runtime/Unity/AssemblyInfo.cs.meta new file mode 100644 index 0000000..fa5bfcd --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/AssemblyInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2af160e05b69349aeb7ac43baa29c62f \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio.meta b/Assets/ProjectQuint/Runtime/Unity/Audio.meta new file mode 100644 index 0000000..34a3d2f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a8a18e0ff9fe24ad6a98a191cf6c35dd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/AssemblyInfo.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/AssemblyInfo.cs new file mode 100644 index 0000000..6bfc724 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ProjectQuint.Application")] +[assembly: InternalsVisibleTo("ProjectQuint.Bootstrap")] +[assembly: InternalsVisibleTo("ProjectQuint.Tests.EditMode")] diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/AssemblyInfo.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/AssemblyInfo.cs.meta new file mode 100644 index 0000000..1f8ad2a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/AssemblyInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2fb26024c41234d0798636f56c78e9f8 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/AudioAssetStore.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioAssetStore.cs new file mode 100644 index 0000000..7a092d5 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioAssetStore.cs @@ -0,0 +1,738 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using ProjectQuint.Core.Charts; +using ProjectQuint.Library; +using UnityEngine; +using UnityEngine.Networking; + +namespace ProjectQuint.Audio +{ + internal enum AudioAssetStoreState + { + Empty, + Loading, + Ready, + Cancelled, + Failed, + Disposed, + } + + internal enum AudioResourceLoadStatus + { + Loaded, + SilentPlaceholder, + Missing, + DecodeFailed, + TooLarge, + Cancelled, + } + + internal readonly struct AudioResourceLoadResult + { + public AudioResourceLoadResult( + AudioResourceLoadStatus status, + AudioClip clip = null, + string detail = null) + { + Status = status; + Clip = clip; + Detail = detail ?? string.Empty; + } + + public AudioResourceLoadStatus Status { get; } + public AudioClip Clip { get; } + public string Detail { get; } + } + + /// + /// Owns the decoded Unity clips for one prepared song. File resolution, asynchronous + /// decoding and the per-song PCM budget live here so both playback modes share exactly + /// the same assets and failure state. + /// + internal sealed class AudioAssetStore : IDisposable + { + private static readonly string[] AudioExtensionFallbacks = { ".wav", ".ogg", ".mp3" }; + internal const long DefaultDecodedPcmBudgetBytes = 1536L * 1024L * 1024L; + + private readonly Dictionary _clips = + new Dictionary(StringComparer.Ordinal); + private readonly long _decodedPcmBudgetBytes; + + public AudioAssetStore(long decodedPcmBudgetBytes = DefaultDecodedPcmBudgetBytes) + { + if (decodedPcmBudgetBytes <= 0) + throw new ArgumentOutOfRangeException(nameof(decodedPcmBudgetBytes)); + _decodedPcmBudgetBytes = decodedPcmBudgetBytes; + } + + public AudioAssetStoreState State { get; private set; } = AudioAssetStoreState.Empty; + public int LoadedClipCount => _clips.Count; + public int MissingClipCount { get; private set; } + public int DecodeFailedClipCount { get; private set; } + public int SilentPlaceholderCount { get; private set; } + public long EstimatedDecodedPcmBytes { get; private set; } + public long DecodedPcmBudgetBytes => _decodedPcmBudgetBytes; + public string LoadError { get; private set; } = string.Empty; + + public IEnumerator Load( + PlayableChart chart, + Action progress, + Func cancellationRequested = null) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + ThrowIfDisposed(); + + ClearAssets(); + State = AudioAssetStoreState.Loading; + MissingClipCount = 0; + DecodeFailedClipCount = 0; + SilentPlaceholderCount = 0; + LoadError = string.Empty; + + if (IsCancelled(cancellationRequested)) + { + State = AudioAssetStoreState.Cancelled; + yield break; + } + + var soundIds = new HashSet(StringComparer.Ordinal); + foreach (var note in chart.Notes) + if (!string.IsNullOrWhiteSpace(note.SoundId)) soundIds.Add(note.SoundId); + foreach (var item in chart.BackgroundAudioEvents) + if (!string.IsNullOrWhiteSpace(item.SoundId)) soundIds.Add(item.SoundId); + foreach (var item in chart.LaneSoundEvents) + if (!string.IsNullOrWhiteSpace(item.SoundId)) soundIds.Add(item.SoundId); + + var orderedIds = soundIds.OrderBy(item => item, StringComparer.Ordinal).ToList(); + for (var index = 0; index < orderedIds.Count; index++) + { + if (CancelLoad(cancellationRequested)) + yield break; + + var soundId = orderedIds[index]; + progress?.Invoke(index, orderedIds.Count, soundId); + + if (!chart.AudioResources.TryGetValue(soundId, out var declaredPath)) + { + MissingClipCount++; + continue; + } + + var resolvedPath = ResolveReferencedAudioPath(chart.Metadata.SourcePath, declaredPath); + if (string.IsNullOrEmpty(resolvedPath)) + { + MissingClipCount++; + continue; + } + + if (!TryClassifyWaveResource( + resolvedPath, + soundId, + out var resourceStatus, + out var resourceError)) + { + DecodeFailedClipCount++; + Debug.LogWarning( + $"[ProjectQuint] Skipped keysound {soundId} '{resolvedPath}': {resourceError}"); + continue; + } + if (resourceStatus == AudioResourceLoadStatus.SilentPlaceholder) + { + SilentPlaceholderCount++; + continue; + } + + AudioClip clip = null; + var loadError = string.Empty; + if (string.Equals(Path.GetExtension(resolvedPath), ".wav", StringComparison.OrdinalIgnoreCase) && + WaveAudioDecoder.IsMicrosoftAdpcm(resolvedPath)) + { + // The format probe reads only the small RIFF header. The expensive full + // file read and ADPCM decode run off the Unity main thread. + var decodeTask = Task.Run(() => DecodeMicrosoftAdpcm(resolvedPath)); + while (!decodeTask.IsCompleted) + { + if (CancelLoad(cancellationRequested)) + { + ObserveFaultAfterCancellation(decodeTask); + yield break; + } + yield return null; + } + + if (decodeTask.IsFaulted) + { + loadError = decodeTask.Exception?.GetBaseException().Message ?? "WAV decode probe failed."; + } + else + { + var decode = decodeTask.Result; + loadError = decode.Error; + if (decode.Audio != null) + clip = CreateDecodedClip(soundId, decode.Audio, out loadError); + } + } + + if (clip == null && string.IsNullOrEmpty(loadError)) + { + using var request = UnityWebRequestMultimedia.GetAudioClip( + BuildLocalFileUri(resolvedPath), + GetAudioType(resolvedPath)); + if (request.downloadHandler is DownloadHandlerAudioClip audioHandler) + { + audioHandler.streamAudio = false; + audioHandler.compressed = false; + } + + var operation = request.SendWebRequest(); + while (!operation.isDone) + { + if (IsCancelled(cancellationRequested)) + { + request.Abort(); + CancelLoad(cancellationRequested); + yield break; + } + yield return null; + } + + if (request.result == UnityWebRequest.Result.Success) + { + try + { + clip = DownloadHandlerAudioClip.GetContent(request); + } + catch (Exception exception) + { + loadError = exception.Message; + } + } + else + { + loadError = request.error; + } + } + + if (CancelLoad(cancellationRequested)) + { + DestroyUnityObject(clip); + yield break; + } + + if (clip == null) + { + DecodeFailedClipCount++; + Debug.LogWarning($"[ProjectQuint] Skipped keysound {soundId} '{resolvedPath}': {loadError}"); + continue; + } + + var clipBytes = CalculateDecodedPcmBytes(clip.samples, clip.channels); + if (clipBytes > _decodedPcmBudgetBytes - EstimatedDecodedPcmBytes) + { + DestroyUnityObject(clip); + Fail( + $"Decoded keysounds exceed the per-song audio budget " + + $"({FormatBytes(_decodedPcmBudgetBytes)}). Last asset: {soundId}."); + yield break; + } + + clip.name = $"BMS {soundId}"; + _clips[soundId] = clip; + EstimatedDecodedPcmBytes += clipBytes; + } + + if (orderedIds.Count > 0 && + _clips.Count == 0 && + MissingClipCount + DecodeFailedClipCount > 0) + { + Fail( + $"No playable keysounds could be loaded " + + $"({MissingClipCount} missing, {DecodeFailedClipCount} decode failed)."); + yield break; + } + + State = AudioAssetStoreState.Ready; + progress?.Invoke(orderedIds.Count, orderedIds.Count, string.Empty); + } + + internal void PrepareForSequentialMix() + { + ThrowIfDisposed(); + ClearAssets(); + MissingClipCount = 0; + DecodeFailedClipCount = 0; + SilentPlaceholderCount = 0; + LoadError = string.Empty; + State = AudioAssetStoreState.Ready; + } + + internal IEnumerator LoadTransientClip( + PlayableChart chart, + string soundId, + Action completed, + Func cancellationRequested = null) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + + if (!chart.AudioResources.TryGetValue(soundId ?? string.Empty, out var declaredPath)) + { + completed?.Invoke(new AudioResourceLoadResult( + AudioResourceLoadStatus.Missing, + detail: $"Undefined keysound {soundId}.")); + yield break; + } + + var resolvedPath = ResolveReferencedAudioPath(chart.Metadata.SourcePath, declaredPath); + if (string.IsNullOrEmpty(resolvedPath)) + { + completed?.Invoke(new AudioResourceLoadResult( + AudioResourceLoadStatus.Missing, + detail: $"Keysound file not found: {declaredPath}")); + yield break; + } + + if (!TryClassifyWaveResource( + resolvedPath, + soundId, + out var resourceStatus, + out var resourceError)) + { + completed?.Invoke(new AudioResourceLoadResult( + AudioResourceLoadStatus.DecodeFailed, + detail: resourceError)); + yield break; + } + if (resourceStatus == AudioResourceLoadStatus.SilentPlaceholder) + { + completed?.Invoke(new AudioResourceLoadResult( + AudioResourceLoadStatus.SilentPlaceholder)); + yield break; + } + + AudioClip clip = null; + var loadError = string.Empty; + if (string.Equals(Path.GetExtension(resolvedPath), ".wav", StringComparison.OrdinalIgnoreCase) && + WaveAudioDecoder.IsMicrosoftAdpcm(resolvedPath)) + { + var decodeTask = Task.Run(() => DecodeMicrosoftAdpcm(resolvedPath)); + while (!decodeTask.IsCompleted) + { + if (IsCancelled(cancellationRequested)) + { + ObserveFaultAfterCancellation(decodeTask); + completed?.Invoke(new AudioResourceLoadResult( + AudioResourceLoadStatus.Cancelled, + detail: "Audio loading cancelled.")); + yield break; + } + yield return null; + } + + if (decodeTask.IsFaulted) + loadError = decodeTask.Exception?.GetBaseException().Message ?? "WAV decode failed."; + else + { + var decode = decodeTask.Result; + loadError = decode.Error; + if (decode.Audio != null) + clip = CreateDecodedClip(soundId, decode.Audio, out loadError); + } + } + + if (clip == null && string.IsNullOrEmpty(loadError)) + { + using var request = UnityWebRequestMultimedia.GetAudioClip( + BuildLocalFileUri(resolvedPath), + GetAudioType(resolvedPath)); + if (request.downloadHandler is DownloadHandlerAudioClip audioHandler) + { + audioHandler.streamAudio = false; + audioHandler.compressed = false; + } + + var operation = request.SendWebRequest(); + while (!operation.isDone) + { + if (IsCancelled(cancellationRequested)) + { + request.Abort(); + completed?.Invoke(new AudioResourceLoadResult( + AudioResourceLoadStatus.Cancelled, + detail: "Audio loading cancelled.")); + yield break; + } + yield return null; + } + + if (request.result == UnityWebRequest.Result.Success) + { + try + { + clip = DownloadHandlerAudioClip.GetContent(request); + } + catch (Exception exception) + { + loadError = exception.Message; + } + } + else + loadError = request.error; + } + + if (IsCancelled(cancellationRequested)) + { + DestroyUnityObject(clip); + completed?.Invoke(new AudioResourceLoadResult( + AudioResourceLoadStatus.Cancelled, + detail: "Audio loading cancelled.")); + yield break; + } + completed?.Invoke(clip != null + ? new AudioResourceLoadResult(AudioResourceLoadStatus.Loaded, clip) + : new AudioResourceLoadResult( + AudioResourceLoadStatus.DecodeFailed, + detail: string.IsNullOrWhiteSpace(loadError) + ? $"Keysound {soundId} could not be decoded." + : loadError)); + } + + internal void RecordTransientOutcome( + string soundId, + AudioResourceLoadStatus status, + string detail) + { + switch (status) + { + case AudioResourceLoadStatus.SilentPlaceholder: + SilentPlaceholderCount++; + return; + case AudioResourceLoadStatus.Missing: + MissingClipCount++; + break; + case AudioResourceLoadStatus.DecodeFailed: + DecodeFailedClipCount++; + break; + default: + return; + } + + Debug.LogWarning( + $"[ProjectQuint] Skipped keysound {soundId}: " + + $"{(string.IsNullOrWhiteSpace(detail) ? status.ToString() : detail)}"); + } + + internal static bool TryReadClipPcm( + AudioClip clip, + string soundId, + long decodedPcmBudgetBytes, + out PcmAudioData pcm, + out AudioResourceLoadStatus failureStatus, + out string error) + { + pcm = null; + failureStatus = AudioResourceLoadStatus.DecodeFailed; + error = string.Empty; + if (clip == null) + return true; + if (clip.loadState != AudioDataLoadState.Loaded && !clip.LoadAudioData()) + { + error = $"Could not load PCM data for keysound {soundId}."; + return false; + } + + var sampleCount64 = checked((long)clip.samples * clip.channels); + if (sampleCount64 <= 0) + { + error = $"Keysound {soundId} has an invalid PCM size."; + return false; + } + if (sampleCount64 > int.MaxValue || + sampleCount64 > decodedPcmBudgetBytes / sizeof(float)) + { + failureStatus = AudioResourceLoadStatus.TooLarge; + error = + $"Decoded keysound {soundId} exceeds the per-resource audio budget " + + $"({FormatBytes(decodedPcmBudgetBytes)})."; + return false; + } + + var samples = new float[(int)sampleCount64]; + if (!clip.GetData(samples, 0)) + { + error = $"Keysound {soundId} cannot be decoded to PCM. Re-encode the source audio as WAV or OGG."; + return false; + } + pcm = new PcmAudioData(samples, clip.channels, clip.frequency); + failureStatus = AudioResourceLoadStatus.Loaded; + return true; + } + + internal static void ReleaseTransientClip(AudioClip clip) => DestroyUnityObject(clip); + + public bool TryGetClip(string soundId, out AudioClip clip) => + _clips.TryGetValue(soundId ?? string.Empty, out clip) && clip != null; + + public Dictionary GetClipDurations() + { + var durations = new Dictionary(_clips.Count, StringComparer.Ordinal); + foreach (var item in _clips) + if (item.Value != null) durations[item.Key] = Math.Max(item.Value.length, 0.01f); + return durations; + } + + public bool TryReadPcm(string soundId, out PcmAudioData pcm, out string error) + { + if (!TryGetClip(soundId, out var clip)) + { + pcm = null; + error = string.Empty; + return true; + } + return TryReadClipPcm( + clip, + soundId, + _decodedPcmBudgetBytes, + out pcm, + out _, + out error); + } + + public void Clear() + { + ThrowIfDisposed(); + ClearAssets(); + MissingClipCount = 0; + DecodeFailedClipCount = 0; + SilentPlaceholderCount = 0; + LoadError = string.Empty; + State = AudioAssetStoreState.Empty; + } + + public void Dispose() + { + if (State == AudioAssetStoreState.Disposed) + return; + ClearAssets(); + State = AudioAssetStoreState.Disposed; + } + + internal static long CalculateDecodedPcmBytes(int frameCount, int channels) + { + if (frameCount < 0) + throw new ArgumentOutOfRangeException(nameof(frameCount)); + if (channels <= 0) + throw new ArgumentOutOfRangeException(nameof(channels)); + return checked((long)frameCount * channels * sizeof(float)); + } + + internal static string ResolveReferencedAudioPath(string chartPath, string declaredPath) + { + var resolved = BmsResourceResolver.ResolveAudio(chartPath, declaredPath); + if (!string.IsNullOrEmpty(resolved)) + return resolved; + if (string.IsNullOrWhiteSpace(chartPath) || string.IsNullOrWhiteSpace(declaredPath)) + return string.Empty; + + var chartDirectory = Path.GetDirectoryName(chartPath); + if (string.IsNullOrEmpty(chartDirectory)) + return string.Empty; + + var normalizedRelativePath = declaredPath + .Trim() + .Trim('"') + .Replace('\\', Path.DirectorySeparatorChar) + .Replace('/', Path.DirectorySeparatorChar); + var declaredCandidate = Path.IsPathRooted(normalizedRelativePath) + ? normalizedRelativePath + : Path.Combine(chartDirectory, normalizedRelativePath); + var candidateDirectory = Path.GetDirectoryName(declaredCandidate); + var candidateStem = Path.GetFileNameWithoutExtension(declaredCandidate); + if (string.IsNullOrEmpty(candidateDirectory) || string.IsNullOrEmpty(candidateStem) || + !Directory.Exists(candidateDirectory)) + return string.Empty; + + foreach (var extension in AudioExtensionFallbacks) + { + var candidate = Path.Combine(candidateDirectory, candidateStem + extension); + if (File.Exists(candidate)) + return candidate; + } + + try + { + foreach (var candidate in Directory.EnumerateFiles(candidateDirectory)) + { + if (!string.Equals(Path.GetFileNameWithoutExtension(candidate), candidateStem, + StringComparison.OrdinalIgnoreCase)) + continue; + if (AudioExtensionFallbacks.Any(extension => + string.Equals(Path.GetExtension(candidate), extension, StringComparison.OrdinalIgnoreCase))) + return candidate; + } + } + catch (IOException) + { + return string.Empty; + } + catch (UnauthorizedAccessException) + { + return string.Empty; + } + + return string.Empty; + } + + internal static string BuildLocalFileUri(string filePath) + { + if (string.IsNullOrWhiteSpace(filePath)) + throw new ArgumentException("A local audio path is required.", nameof(filePath)); + + // System.Uri treats a valid-looking %xx sequence as an already escaped URL byte. + // BMS packs can legally contain a literal '%' in a directory or file name, so escape + // it before constructing the file URI or UnityWebRequest will open a different path. + var literalPath = Path.GetFullPath(filePath); + return "file://" + string.Join( + "/", + literalPath + .Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None) + .Select(Uri.EscapeDataString)); + } + + private bool CancelLoad(Func cancellationRequested) + { + if (!IsCancelled(cancellationRequested)) + return false; + ClearAssets(); + State = AudioAssetStoreState.Cancelled; + return true; + } + + private void Fail(string error) + { + ClearAssets(); + LoadError = error ?? "Audio asset loading failed."; + State = AudioAssetStoreState.Failed; + } + + private void ClearAssets() + { + foreach (var clip in _clips.Values) + DestroyUnityObject(clip); + _clips.Clear(); + EstimatedDecodedPcmBytes = 0; + } + + private void ThrowIfDisposed() + { + if (State == AudioAssetStoreState.Disposed) + throw new ObjectDisposedException(nameof(AudioAssetStore)); + } + + private static bool IsCancelled(Func cancellationRequested) => + cancellationRequested?.Invoke() == true; + + internal static bool TryClassifyWaveResource( + string resolvedPath, + string soundId, + out AudioResourceLoadStatus status, + out string error) + { + status = AudioResourceLoadStatus.Loaded; + error = string.Empty; + if (!string.Equals( + Path.GetExtension(resolvedPath), + ".wav", + StringComparison.OrdinalIgnoreCase)) + return true; + + var inspection = WaveAudioDecoder.InspectContainer(resolvedPath); + switch (inspection.Status) + { + case WaveContainerStatus.AudioData: + return true; + case WaveContainerStatus.SilentPlaceholder: + status = AudioResourceLoadStatus.SilentPlaceholder; + return true; + default: + status = AudioResourceLoadStatus.DecodeFailed; + error = + $"Keysound {soundId} is not a valid WAV container: " + + inspection.Detail; + return false; + } + } + + private static BackgroundWaveDecodeResult DecodeMicrosoftAdpcm(string path) + { + return WaveAudioDecoder.TryDecodeFile(path, out var audio, out var error) + ? new BackgroundWaveDecodeResult(audio, string.Empty) + : new BackgroundWaveDecodeResult(null, error); + } + + private static AudioClip CreateDecodedClip( + string soundId, + DecodedWaveAudio decoded, + out string error) + { + error = string.Empty; + var clip = AudioClip.Create( + $"BMS {soundId}", + decoded.FrameCount, + decoded.Channels, + decoded.SampleRate, + false); + if (clip != null && clip.SetData(decoded.Samples, 0)) + return clip; + DestroyUnityObject(clip); + error = "AudioClip.SetData rejected the decoded samples."; + return null; + } + + private static AudioType GetAudioType(string path) + { + switch (Path.GetExtension(path).ToLowerInvariant()) + { + case ".ogg": return AudioType.OGGVORBIS; + case ".mp3": return AudioType.MPEG; + default: return AudioType.WAV; + } + } + + private static void ObserveFaultAfterCancellation(Task task) + { + task.ContinueWith( + completed => { _ = completed.Exception; }, + TaskContinuationOptions.OnlyOnFaulted); + } + + private static void DestroyUnityObject(UnityEngine.Object target) + { + if (target == null) + return; + if (Application.isPlaying) + UnityEngine.Object.Destroy(target); + else + UnityEngine.Object.DestroyImmediate(target); + } + + private static string FormatBytes(long bytes) => $"{bytes / (1024d * 1024d):0.#} MiB"; + + private readonly struct BackgroundWaveDecodeResult + { + public BackgroundWaveDecodeResult(DecodedWaveAudio audio, string error) + { + Audio = audio; + Error = error ?? string.Empty; + } + + public DecodedWaveAudio Audio { get; } + public string Error { get; } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/AudioAssetStore.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioAssetStore.cs.meta new file mode 100644 index 0000000..cf0079b --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioAssetStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dd4f69d881f884738aed3561ef698e14 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/AudioBackendContracts.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioBackendContracts.cs new file mode 100644 index 0000000..c525b07 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioBackendContracts.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections; +using ProjectQuint.Core.Charts; +using UnityEngine; + +namespace ProjectQuint.Audio +{ + public readonly struct AudioOutputDeviceInfo + { + public AudioOutputDeviceInfo( + string persistentId, + string name, + int sampleRate, + int channels, + bool isSystemDefault = false) + { + PersistentId = persistentId ?? string.Empty; + Name = string.IsNullOrWhiteSpace(name) ? "UNKNOWN DEVICE" : name.Trim(); + SampleRate = Math.Max(0, sampleRate); + Channels = Math.Max(0, channels); + IsSystemDefault = isSystemDefault; + } + + public string PersistentId { get; } + public string Name { get; } + public int SampleRate { get; } + public int Channels { get; } + public bool IsSystemDefault { get; } + public string DetailLabel => IsSystemDefault + ? "FOLLOW macOS SYSTEM OUTPUT" + : SampleRate > 0 + ? $"{SampleRate:N0} Hz // {Channels} CH" + : "DEVICE FORMAT UNAVAILABLE"; + + public static AudioOutputDeviceInfo SystemDefault { get; } = + new AudioOutputDeviceInfo(string.Empty, "SYSTEM DEFAULT", 0, 0, true); + } + + public readonly struct AudioOutputConfigurationSnapshot + { + public AudioOutputConfigurationSnapshot( + string backendId, + string outputType, + string deviceName, + int bufferLength, + int bufferCount, + int sampleRate, + string devicePersistentId = "") + { + BackendId = backendId ?? string.Empty; + OutputType = outputType ?? string.Empty; + DeviceName = deviceName ?? string.Empty; + DevicePersistentId = devicePersistentId ?? string.Empty; + BufferLength = Math.Max(0, bufferLength); + BufferCount = Math.Max(0, bufferCount); + SampleRate = Math.Max(0, sampleRate); + } + + public string BackendId { get; } + public string OutputType { get; } + public string DeviceName { get; } + public string DevicePersistentId { get; } + public int BufferLength { get; } + public int BufferCount { get; } + public int SampleRate { get; } + public double EstimatedMixerLatencyMilliseconds => + BufferLength > 0 && BufferCount > 0 && SampleRate > 0 + ? BufferLength * (double)BufferCount * 1000.0 / SampleRate + : 0; + public string AppliedLabel => BufferLength > 0 && BufferCount > 0 && SampleRate > 0 + ? $"{BackendId} // {OutputType} // {DeviceName} // " + + $"{BufferLength} x {BufferCount} @ {SampleRate} Hz // " + + $"EST {EstimatedMixerLatencyMilliseconds:0.0} ms" + : $"{BackendId} // APPLIED CONFIGURATION UNAVAILABLE"; + } + + /// + /// Monotonic clock owned by the selected audio backend. Gameplay consumes seconds only and + /// never reaches into Unity or a future native backend for its clock directly. + /// + public interface IAudioClock + { + string ClockId { get; } + double CurrentTime { get; } + int OutputSampleRate { get; } + } + + /// + /// Per-song audio session contract shared by loading, gameplay and diagnostics. Backend + /// implementations own their native clips, channels, scheduling clock and pause semantics. + /// + public interface ISongAudioBackend : IDisposable + { + string BackendId { get; } + IAudioClock Clock { get; } + double StartDspTime { get; } + int LoadedClipCount { get; } + int MissingClipCount { get; } + int DecodeFailedClipCount { get; } + int SilentPlaceholderCount { get; } + int ScheduledSourceCount { get; } + int PendingScheduledEventCount { get; } + int DroppedLateEventCount { get; } + bool SmoothMixReady { get; } + string SmoothMixError { get; } + bool SmoothMixCacheHit { get; } + string SmoothMixCacheStatus { get; } + string LoadError { get; } + long EstimatedDecodedPcmBytes { get; } + long EstimatedPremixedPcmBytes { get; } + long EstimatedTotalPcmBytes { get; } + + IEnumerator Load( + PlayableChart chart, + Action progress, + Func cancellationRequested = null, + bool reactiveKeySoundsEnabled = true); + + void PrepareReactivePlayback(double leadSeconds = 0.35); + + IEnumerator PrepareSmoothPlaybackMix( + Action progress, + Func cancellationRequested = null); + + void BeginPlayback(bool reactiveKeySoundsEnabled, double leadSeconds = 0.35); + void Update(); + void PlayKeySound(string soundId, int lane); + void SetMasterVolume(float normalizedVolume); + void Pause(); + void Resume(); + void Stop(); + } + + /// + /// Composition boundary used by SongLoadCoordinator. A future FMOD implementation can be + /// selected here without introducing FMOD types into loading or gameplay assemblies. + /// + public interface ISongAudioBackendFactory + { + string BackendId { get; } + IAudioClock Clock { get; } + AudioOutputConfigurationSnapshot OutputConfiguration { get; } + ISongAudioBackend Create(GameObject resourceOwner); + } + + public sealed class UnityDspAudioClock : IAudioClock + { + private UnityDspAudioClock() { } + + public static UnityDspAudioClock Shared { get; } = new UnityDspAudioClock(); + public string ClockId => "UNITY_DSP"; + public double CurrentTime => AudioSettings.dspTime; + public int OutputSampleRate => AudioSettings.outputSampleRate; + } + + public sealed class UnitySongAudioBackendFactory : ISongAudioBackendFactory + { + public string BackendId => BmsAudioSystem.UnityBackendId; + public IAudioClock Clock => UnityDspAudioClock.Shared; + public AudioOutputConfigurationSnapshot OutputConfiguration + { + get + { + AudioSettings.GetDSPBufferSize(out var bufferLength, out var bufferCount); + return new AudioOutputConfigurationSnapshot( + BackendId, + "UNITY AUDIO", + "SYSTEM DEFAULT", + bufferLength, + bufferCount, + AudioSettings.outputSampleRate); + } + } + + public ISongAudioBackend Create(GameObject resourceOwner) => + new BmsAudioSystem(resourceOwner, Clock); + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/AudioBackendContracts.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioBackendContracts.cs.meta new file mode 100644 index 0000000..a3d919d --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioBackendContracts.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2d3afc7f7a5fe4515ba7b2723c0b6d3e \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/AudioDspBufferController.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioDspBufferController.cs new file mode 100644 index 0000000..f7d40b8 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioDspBufferController.cs @@ -0,0 +1,217 @@ +using System; +using ProjectQuint.Settings; +using UnityEngine; + +namespace ProjectQuint.Audio +{ + internal readonly struct AudioDspConfigurationSnapshot + { + public AudioDspConfigurationSnapshot( + AudioDspBufferProfile requestedProfile, + int requestedBufferSamples, + int bufferLength, + int bufferCount, + int sampleRate) + { + RequestedProfile = requestedProfile; + RequestedBufferSamples = Math.Max(1, requestedBufferSamples); + BufferLength = Math.Max(0, bufferLength); + BufferCount = Math.Max(0, bufferCount); + SampleRate = Math.Max(0, sampleRate); + } + + public AudioDspBufferProfile RequestedProfile { get; } + public int RequestedBufferSamples { get; } + public int BufferLength { get; } + public int BufferCount { get; } + public int SampleRate { get; } + public bool IsExactBufferMatch => BufferLength == RequestedBufferSamples; + public double EstimatedMixerLatencyMilliseconds => CalculateMixerLatencyMilliseconds( + BufferLength, + BufferCount, + SampleRate); + + public string AppliedLabel => BufferLength > 0 && BufferCount > 0 && SampleRate > 0 + ? $"APPLIED {BufferLength} x {BufferCount} @ {SampleRate} Hz // EST {EstimatedMixerLatencyMilliseconds:0.0} ms" + : "APPLIED CONFIGURATION UNAVAILABLE"; + + public static double CalculateMixerLatencyMilliseconds( + int bufferLength, + int bufferCount, + int sampleRate) + { + if (bufferLength <= 0 || bufferCount <= 0 || sampleRate <= 0) + return 0; + return bufferLength * (double)bufferCount * 1000.0 / sampleRate; + } + } + + internal interface IAudioConfigurationBackend : IDisposable + { + event Action ConfigurationChanged; + AudioConfiguration GetConfiguration(); + void GetDspBufferSize(out int bufferLength, out int bufferCount); + bool Reset(AudioConfiguration configuration); + } + + internal sealed class UnityAudioConfigurationBackend : IAudioConfigurationBackend + { + public UnityAudioConfigurationBackend() => + AudioSettings.OnAudioConfigurationChanged += OnAudioConfigurationChanged; + + public event Action ConfigurationChanged; + + public AudioConfiguration GetConfiguration() => AudioSettings.GetConfiguration(); + + public void GetDspBufferSize(out int bufferLength, out int bufferCount) => + AudioSettings.GetDSPBufferSize(out bufferLength, out bufferCount); + + public bool Reset(AudioConfiguration configuration) => AudioSettings.Reset(configuration); + + public void Dispose() => + AudioSettings.OnAudioConfigurationChanged -= OnAudioConfigurationChanged; + + private void OnAudioConfigurationChanged(bool deviceWasChanged) => + ConfigurationChanged?.Invoke(deviceWasChanged); + } + + /// + /// Owns runtime DSP-buffer reconfiguration. Callers must release every generated AudioClip + /// in prepareForReset before this controller invokes AudioSettings.Reset. + /// + internal sealed class AudioDspBufferController : IDisposable + { + private readonly IAudioConfigurationBackend _backend; + private bool _externalDeviceChangePending; + private bool _disposed; + + public AudioDspBufferController( + AudioDspBufferProfile requestedProfile, + IAudioConfigurationBackend backend = null) + { + RequestedProfile = Enum.IsDefined(typeof(AudioDspBufferProfile), requestedProfile) + ? requestedProfile + : GameSettingsDefaults.AudioDspBuffer; + _backend = backend ?? new UnityAudioConfigurationBackend(); + _backend.ConfigurationChanged += OnConfigurationChanged; + Snapshot = CaptureSnapshot(); + } + + public AudioDspBufferProfile RequestedProfile { get; private set; } + public AudioDspConfigurationSnapshot Snapshot { get; private set; } + public bool IsResetting { get; private set; } + public string LastError { get; private set; } = string.Empty; + + public bool TryApply( + AudioDspBufferProfile profile, + Func prepareForReset, + out AudioDspConfigurationSnapshot snapshot) + { + ThrowIfDisposed(); + profile = Enum.IsDefined(typeof(AudioDspBufferProfile), profile) + ? profile + : GameSettingsDefaults.AudioDspBuffer; + var requestedSamples = GameSettings.RequestedDspBufferSamples(profile); + RefreshSnapshot(); + if (Snapshot.BufferLength == requestedSamples) + { + RequestedProfile = profile; + Snapshot = CaptureSnapshot(); + LastError = string.Empty; + snapshot = Snapshot; + return true; + } + + if (prepareForReset == null) + { + LastError = "Audio reset preparation callback is missing."; + snapshot = Snapshot; + return false; + } + + IsResetting = true; + try + { + if (!prepareForReset()) + { + LastError = "Audio reset was blocked because song resources are still active."; + snapshot = Snapshot; + return false; + } + + var configuration = _backend.GetConfiguration(); + configuration.dspBufferSize = requestedSamples; + if (!_backend.Reset(configuration)) + { + LastError = $"The audio device rejected the {requestedSamples}-sample DSP buffer request."; + RefreshSnapshot(); + snapshot = Snapshot; + return false; + } + + RequestedProfile = profile; + RefreshSnapshot(); + LastError = string.Empty; + snapshot = Snapshot; + return true; + } + catch (Exception exception) + { + LastError = $"Audio reset failed: {exception.Message}"; + RefreshSnapshot(); + snapshot = Snapshot; + return false; + } + finally + { + IsResetting = false; + } + } + + public bool ConsumeExternalDeviceChange() + { + var pending = _externalDeviceChangePending; + _externalDeviceChangePending = false; + if (pending) + RefreshSnapshot(); + return pending; + } + + public void RefreshSnapshot() => Snapshot = CaptureSnapshot(); + + public void Dispose() + { + if (_disposed) + return; + _backend.ConfigurationChanged -= OnConfigurationChanged; + _backend.Dispose(); + _disposed = true; + } + + private AudioDspConfigurationSnapshot CaptureSnapshot() + { + var configuration = _backend.GetConfiguration(); + _backend.GetDspBufferSize(out var bufferLength, out var bufferCount); + return new AudioDspConfigurationSnapshot( + RequestedProfile, + GameSettings.RequestedDspBufferSamples(RequestedProfile), + bufferLength, + bufferCount, + configuration.sampleRate); + } + + private void OnConfigurationChanged(bool deviceWasChanged) + { + if (deviceWasChanged) + _externalDeviceChangePending = true; + if (!IsResetting) + RefreshSnapshot(); + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(AudioDspBufferController)); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/AudioDspBufferController.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioDspBufferController.cs.meta new file mode 100644 index 0000000..1528bad --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/AudioDspBufferController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 42d5b3d7137874d02b42b2787892fe5b \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/BmsAudioSystem.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/BmsAudioSystem.cs new file mode 100644 index 0000000..2ca7af6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/BmsAudioSystem.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections; +using ProjectQuint.Core.Charts; +using UnityEngine; + +namespace ProjectQuint.Audio +{ + /// + /// Compatibility facade for one song's audio session. Asset loading, offline rendering and + /// DSP playback are separate owners so gameplay never performs decode or mix work. + /// + public sealed class BmsAudioSystem : ISongAudioBackend + { + public const string UnityBackendId = "UNITY_AUDIO"; + private readonly AudioAssetStore _assets; + private readonly PremixedSongRenderer _premix; + private readonly SongAudioPlaybackEngine _playback; + private PlayableChart _chart; + private bool _disposed; + + public BmsAudioSystem(GameObject owner) + : this(owner, UnityDspAudioClock.Shared) + { + } + + internal BmsAudioSystem(GameObject owner, IAudioClock clock) + { + if (owner == null) + throw new ArgumentNullException(nameof(owner)); + Clock = clock ?? throw new ArgumentNullException(nameof(clock)); + _assets = new AudioAssetStore(); + _premix = new PremixedSongRenderer(); + _playback = new SongAudioPlaybackEngine(owner, Clock); + } + + internal const double SchedulingHorizon = SongAudioPlaybackEngine.SchedulingHorizon; + internal const double LateEventDropThresholdSeconds = + SongAudioPlaybackEngine.LateEventDropThresholdSeconds; + internal const int ScheduledSourceHeadroom = SongAudioPlaybackEngine.ScheduledSourceHeadroom; + + public string BackendId => UnityBackendId; + public IAudioClock Clock { get; } + public double StartDspTime => _playback.StartDspTime; + public int LoadedClipCount => _assets.LoadedClipCount; + public int MissingClipCount => _assets.MissingClipCount; + public int DecodeFailedClipCount => _assets.DecodeFailedClipCount; + public int SilentPlaceholderCount => _assets.SilentPlaceholderCount; + public int ScheduledSourceCount => _playback.ScheduledSourceCount; + public int PendingScheduledEventCount => _playback.PendingScheduledEventCount; + public int DroppedLateEventCount => _playback.DroppedLateEventCount; + public bool SmoothMixReady => _premix.IsReady; + public string SmoothMixError => _premix.Error; + public bool SmoothMixCacheHit => _premix.CacheHit; + public string SmoothMixCacheStatus => _premix.CacheStatus; + public string LoadError => _assets.LoadError; + public long EstimatedDecodedPcmBytes => _assets.EstimatedDecodedPcmBytes; + public long EstimatedPremixedPcmBytes => _premix.EstimatedPcmBytes; + public long EstimatedTotalPcmBytes => checked(EstimatedDecodedPcmBytes + EstimatedPremixedPcmBytes); + + internal AudioAssetStoreState AssetState => _assets.State; + internal PremixedSongState PremixState => _premix.State; + + public IEnumerator Load( + PlayableChart chart, + Action progress, + Func cancellationRequested = null, + bool reactiveKeySoundsEnabled = true) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + ThrowIfDisposed(); + ResetLoadedResources(); + _chart = chart; + if (!reactiveKeySoundsEnabled) + { + _assets.PrepareForSequentialMix(); + progress?.Invoke(0, 0, "Sequential smooth mix selected"); + yield break; + } + yield return _assets.Load(chart, progress, cancellationRequested); + } + + public void PrepareReactivePlayback(double leadSeconds = 0.35) + { + ThrowIfDisposed(); + EnsureAssetsReady(); + _playback.PrepareReactivePlayback(_chart, _assets, leadSeconds); + } + + public IEnumerator PrepareSmoothPlaybackMix( + Action progress, + Func cancellationRequested = null) + { + ThrowIfDisposed(); + EnsureAssetsReady(); + yield return _premix.Prepare(_chart, _assets, progress, cancellationRequested); + } + + public void BeginPlayback(bool reactiveKeySoundsEnabled, double leadSeconds = 0.35) + { + ThrowIfDisposed(); + EnsureAssetsReady(); + _playback.BeginPlayback( + _chart, + _assets, + _premix.Clip, + reactiveKeySoundsEnabled, + leadSeconds); + } + + public void Update() => _playback.Update(); + + public void PlayKeySound(string soundId, int lane) => _playback.PlayKeySound(soundId, lane); + + public void SetMasterVolume(float normalizedVolume) => + AudioListener.volume = Mathf.Clamp01(normalizedVolume); + + public void Pause() + { + ThrowIfDisposed(); + AudioListener.pause = true; + } + + public void Resume() + { + ThrowIfDisposed(); + AudioListener.pause = false; + } + + public void Stop() => _playback.Stop(); + + /// + /// Clears song-owned clips while keeping the source graph reusable. PreparedSong.Dispose + /// uses Dispose instead; this method remains for compatibility with older call sites. + /// + public void DisposeClips() + { + if (_disposed) + return; + ResetLoadedResources(); + } + + public void Dispose() + { + if (_disposed) + return; + _playback.Stop(); + _premix.Dispose(); + _assets.Dispose(); + _playback.Dispose(); + _chart = null; + _disposed = true; + } + + internal static string ResolveReferencedAudioPath(string chartPath, string declaredPath) => + AudioAssetStore.ResolveReferencedAudioPath(chartPath, declaredPath); + + internal static bool ShouldDropLateScheduledEvent(double scheduledDspTime, double currentDspTime) => + SongAudioPlaybackEngine.ShouldDropLateScheduledEvent(scheduledDspTime, currentDspTime); + + private void ResetLoadedResources() + { + _playback.Stop(); + _premix.Clear(); + _assets.Clear(); + _chart = null; + } + + private void EnsureAssetsReady() + { + if (_chart == null || _assets.State != AudioAssetStoreState.Ready) + { + var detail = string.IsNullOrWhiteSpace(_assets.LoadError) + ? $"Audio assets are not ready (state: {_assets.State})." + : _assets.LoadError; + throw new InvalidOperationException(detail); + } + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(BmsAudioSystem)); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/BmsAudioSystem.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/BmsAudioSystem.cs.meta new file mode 100644 index 0000000..d9a2287 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/BmsAudioSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ab2ab8839d3064ce8a8a9a4ca2fa73e3 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/FmodAudioSyncMetronome.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodAudioSyncMetronome.cs new file mode 100644 index 0000000..27b5ea3 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodAudioSyncMetronome.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using FMOD; +using FmodChannel = FMOD.Channel; +using FmodSound = FMOD.Sound; + +namespace ProjectQuint.Audio +{ + /// + /// A short procedural click scheduled on the same FMOD DSP clock used by gameplay. + /// + internal sealed class FmodAudioSyncMetronome : IDisposable + { + private const double ClickDurationSeconds = 0.035; + private readonly FmodCoreAudioContext _context; + private readonly List _channels = new List(); + private FmodSound _click; + private bool _disposed; + + internal FmodAudioSyncMetronome(FmodCoreAudioContext context, float volume) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + var wav = CreateClickWav(context.OutputSampleRate); + var exInfo = new CREATESOUNDEXINFO + { + cbsize = Marshal.SizeOf(), + length = checked((uint)wav.Length), + }; + FmodCoreAudioContext.Check( + context.System.createSound( + wav, + MODE.OPENMEMORY | MODE.CREATESAMPLE | MODE.LOOP_OFF | MODE._2D, + ref exInfo, + out _click), + "create audio-sync click"); + Volume = Math.Max(0f, Math.Min(1f, volume)); + } + + internal float Volume { get; } + + internal double[] ScheduleClicks(int count, double intervalSeconds, double leadSeconds) + { + ThrowIfDisposed(); + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count)); + if (intervalSeconds <= 0) + throw new ArgumentOutOfRangeException(nameof(intervalSeconds)); + + StopChannels(); + var start = _context.ScheduleAfter(leadSeconds); + var intervalSamples = (ulong)Math.Round(intervalSeconds * _context.OutputSampleRate); + var logicalTimes = new double[count]; + for (var index = 0; index < count; index++) + { + var rawClock = checked(start + intervalSamples * (ulong)index); + var channel = _context.PlayScheduledAt(_click, rawClock); + FmodCoreAudioContext.Check(channel.setVolume(Volume), "set audio-sync click volume"); + _channels.Add(channel); + logicalTimes[index] = _context.LogicalTimeForRawClock(rawClock); + } + return logicalTimes; + } + + internal void StopChannels() + { + for (var index = 0; index < _channels.Count; index++) + { + var channel = _channels[index]; + if (channel.hasHandle()) + channel.stop(); + channel.clearHandle(); + } + _channels.Clear(); + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + StopChannels(); + if (_click.hasHandle()) + _click.release(); + _click.clearHandle(); + } + + private static byte[] CreateClickWav(int sampleRate) + { + var sampleCount = Math.Max(1, (int)Math.Round(sampleRate * ClickDurationSeconds)); + var pcmBytes = checked(sampleCount * 2); + var data = new byte[44 + pcmBytes]; + WriteAscii(data, 0, "RIFF"); + WriteInt32(data, 4, 36 + pcmBytes); + WriteAscii(data, 8, "WAVE"); + WriteAscii(data, 12, "fmt "); + WriteInt32(data, 16, 16); + WriteInt16(data, 20, 1); + WriteInt16(data, 22, 1); + WriteInt32(data, 24, sampleRate); + WriteInt32(data, 28, sampleRate * 2); + WriteInt16(data, 32, 2); + WriteInt16(data, 34, 16); + WriteAscii(data, 36, "data"); + WriteInt32(data, 40, pcmBytes); + for (var index = 0; index < sampleCount; index++) + { + var t = index / (double)sampleRate; + var envelope = Math.Exp(-t * 95.0); + var waveform = Math.Sin(2.0 * Math.PI * 1450.0 * t) * envelope; + var sample = (short)Math.Round(Math.Max(-1.0, Math.Min(1.0, waveform)) * 24500.0); + WriteInt16(data, 44 + index * 2, sample); + } + return data; + } + + private static void WriteAscii(byte[] target, int offset, string value) + { + for (var index = 0; index < value.Length; index++) + target[offset + index] = (byte)value[index]; + } + + private static void WriteInt16(byte[] target, int offset, int value) + { + target[offset] = (byte)value; + target[offset + 1] = (byte)(value >> 8); + } + + private static void WriteInt32(byte[] target, int offset, int value) + { + target[offset] = (byte)value; + target[offset + 1] = (byte)(value >> 8); + target[offset + 2] = (byte)(value >> 16); + target[offset + 3] = (byte)(value >> 24); + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(FmodAudioSyncMetronome)); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/FmodAudioSyncMetronome.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodAudioSyncMetronome.cs.meta new file mode 100644 index 0000000..7a35c78 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodAudioSyncMetronome.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 44dad9469e144cd1817cce6f6a8299e7 diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreAudioBackend.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreAudioBackend.cs new file mode 100644 index 0000000..86e024e --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreAudioBackend.cs @@ -0,0 +1,1013 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using ProjectQuint.Core.Charts; +using UnityEngine; +using FmodChannel = FMOD.Channel; +using FmodChannelGroup = FMOD.ChannelGroup; +using FmodSound = FMOD.Sound; +using FmodSystem = FMOD.System; + +namespace ProjectQuint.Audio +{ + public enum SongAudioBackendKind + { + Unity, + FmodCore, + } + + internal static class AudioBackendSelection + { + internal const string BackendArgument = "-projectquint-audio-backend"; + + public static bool TryParse( + string[] arguments, + out SongAudioBackendKind backend, + out string error) + { + backend = SongAudioBackendKind.FmodCore; + error = string.Empty; + if (arguments == null) + return true; + + for (var index = 0; index < arguments.Length; index++) + { + if (!string.Equals( + arguments[index], + BackendArgument, + StringComparison.OrdinalIgnoreCase)) + continue; + if (++index >= arguments.Length || string.IsNullOrWhiteSpace(arguments[index])) + { + error = $"{BackendArgument} requires 'unity' or 'fmod-core'."; + return false; + } + + var value = arguments[index]; + if (string.Equals(value, "unity", StringComparison.OrdinalIgnoreCase) || + string.Equals(value, "unity-audio", StringComparison.OrdinalIgnoreCase)) + { + backend = SongAudioBackendKind.Unity; + continue; + } + if (string.Equals(value, "fmod", StringComparison.OrdinalIgnoreCase) || + string.Equals(value, "fmod-core", StringComparison.OrdinalIgnoreCase)) + { + backend = SongAudioBackendKind.FmodCore; + continue; + } + + error = $"Unsupported audio backend '{value}'. Expected 'unity' or 'fmod-core'."; + return false; + } + + return true; + } + } + + /// + /// One FMOD Core system shared by every prepared song. Its public clock removes time spent + /// paused, while scheduling continues to use the unmodified native DSP sample clock. + /// + internal sealed class FmodCoreAudioContext : IAudioClock, IDisposable + { + private const int MaximumVirtualChannels = 512; + private FmodSystem _system; + private FmodChannelGroup _masterGroup; + private readonly FMOD.SYSTEM_CALLBACK _outputCallback; + private readonly int _outputSampleRate; + private readonly bool _noSoundNrt; + private AudioOutputDeviceInfo[] _outputDevices = { AudioOutputDeviceInfo.SystemDefault }; + private int _deviceReinitializeCount; + private int _reportedDeviceReinitializeCount; + private int _outputUnderrunCount; + private int _reportedOutputUnderrunCount; + private ulong _accumulatedPausedSamples; + private ulong _pauseStartedRawClock; + private double _pausedLogicalTime; + private bool _clockPaused; + private bool _disposed; + + public FmodCoreAudioContext( + uint dspBufferLength = 0, + bool noSoundNrt = false, + string preferredOutputDeviceId = "") + { + _noSoundNrt = noSoundNrt; + RequestedOutputDeviceId = preferredOutputDeviceId?.Trim() ?? string.Empty; + try + { + Check(FMOD.Factory.System_Create(out _system), "create Core system"); + if (noSoundNrt) + Check(_system.setOutput(FMOD.OUTPUTTYPE.NOSOUND_NRT), "select NRT test output"); + Check(_system.setSoftwareChannels(MaximumVirtualChannels), "configure virtual channels"); + if (dspBufferLength > 0) + Check(_system.setDSPBufferSize(dspBufferLength, 4), "configure DSP buffer"); + if (!noSoundNrt && !string.IsNullOrWhiteSpace(RequestedOutputDeviceId)) + SelectPreferredOutputDevice(RequestedOutputDeviceId); + Check(_system.init(MaximumVirtualChannels, FMOD.INITFLAGS.NORMAL, IntPtr.Zero), "initialize Core system"); + _outputCallback = OnOutputEvent; + Check( + _system.setCallback( + _outputCallback, + FMOD.SYSTEM_CALLBACK_TYPE.DEVICEREINITIALIZE | + FMOD.SYSTEM_CALLBACK_TYPE.OUTPUTUNDERRUN), + "register output-device diagnostics"); + Check(_system.getVersion(out var runtimeVersion), "query runtime version"); + if (runtimeVersion != FMOD.VERSION.number) + { + throw new InvalidOperationException( + $"FMOD header/runtime version mismatch: header 0x{FMOD.VERSION.number:X8}, " + + $"runtime 0x{runtimeVersion:X8}."); + } + Check( + _system.getSoftwareFormat(out _outputSampleRate, out _, out _), + "query output format"); + if (_outputSampleRate <= 0) + throw new InvalidOperationException("FMOD reported an invalid output sample rate."); + Check(_system.getMasterChannelGroup(out _masterGroup), "query master channel group"); + RefreshOutputDevices(); + CaptureSelectedOutputDevice(); + if (DeviceSelectionFallback) + { + Debug.LogWarning( + $"[ProjectQuint Audio] Saved output device '{RequestedOutputDeviceId}' is unavailable; " + + "using the macOS system default for this session."); + } + Debug.Log($"[ProjectQuint Audio] {DescribeOutputDevice()}."); + } + catch + { + Dispose(); + throw; + } + } + + public string ClockId => "FMOD_DSP"; + public int OutputSampleRate => _outputSampleRate; + internal string RequestedOutputDeviceId { get; } + internal string SelectedOutputDeviceId { get; private set; } = string.Empty; + internal string ActiveOutputDeviceId { get; private set; } = string.Empty; + internal string ActiveOutputDeviceName { get; private set; } = string.Empty; + internal bool DeviceSelectionFallback { get; private set; } + internal IReadOnlyList OutputDevices => _outputDevices; + internal AudioOutputConfigurationSnapshot OutputConfiguration => CaptureOutputConfiguration(); + public double CurrentTime + { + get + { + ThrowIfDisposed(); + if (_clockPaused) + return _pausedLogicalTime; + return LogicalSeconds(GetRawDspClock(), _accumulatedPausedSamples, _outputSampleRate); + } + } + + internal FmodSystem System + { + get + { + ThrowIfDisposed(); + return _system; + } + } + + internal FmodChannelGroup MasterGroup + { + get + { + ThrowIfDisposed(); + return _masterGroup; + } + } + + internal ulong ScheduleAfter(double leadSeconds) + { + ThrowIfDisposed(); + var leadSamples = (ulong)Math.Ceiling(Math.Max(0.0, leadSeconds) * _outputSampleRate); + return checked(GetRawDspClock() + leadSamples); + } + + internal double LogicalTimeForRawClock(ulong rawClock) => + _clockPaused + ? _pausedLogicalTime + : LogicalSeconds(rawClock, _accumulatedPausedSamples, _outputSampleRate); + + internal ulong RawClockForLogicalTime(double logicalTime) + { + ThrowIfDisposed(); + var activeSamples = (ulong)Math.Ceiling(Math.Max(0.0, logicalTime) * _outputSampleRate); + return checked(activeSamples + _accumulatedPausedSamples); + } + + internal void PauseClock() + { + ThrowIfDisposed(); + if (_clockPaused) + return; + _pauseStartedRawClock = GetRawDspClock(); + _pausedLogicalTime = LogicalSeconds( + _pauseStartedRawClock, + _accumulatedPausedSamples, + _outputSampleRate); + _clockPaused = true; + } + + internal void ResumeClock() + { + ThrowIfDisposed(); + if (!_clockPaused) + return; + var resumedAt = GetRawDspClock(); + if (resumedAt >= _pauseStartedRawClock) + _accumulatedPausedSamples = checked( + _accumulatedPausedSamples + resumedAt - _pauseStartedRawClock); + _clockPaused = false; + } + + internal void Update() + { + Check(_system.update(), "update Core system"); + ReportOutputEvents(); + } + + internal void SetMasterVolume(float normalizedVolume) => + Check(_masterGroup.setVolume(Mathf.Clamp01(normalizedVolume)), "set master volume"); + + internal FmodSound CreateFileStream(string path) + { + ThrowIfDisposed(); + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("FMOD stream path is empty.", nameof(path)); + if (!global::System.IO.File.Exists(path)) + throw new global::System.IO.FileNotFoundException("FMOD stream file was not found.", path); + var mode = FMOD.MODE.CREATESTREAM | + FMOD.MODE.NONBLOCKING | + FMOD.MODE.LOOP_OFF | + FMOD.MODE._2D | + FMOD.MODE.ACCURATETIME; + Check(_system.createSound(path, mode, out var sound), $"open nonblocking stream '{path}'"); + return sound; + } + + internal FMOD.OPENSTATE GetOpenState(FmodSound sound, out uint bufferedPercent) + { + ThrowIfDisposed(); + if (!sound.hasHandle()) + throw new InvalidOperationException("FMOD stream sound is not prepared."); + var result = sound.getOpenState(out var state, out bufferedPercent, out _, out _); + if (result != FMOD.RESULT.OK && result != FMOD.RESULT.ERR_NOTREADY) + Check(result, "query nonblocking stream state"); + return state; + } + + internal FmodSound CreateFileSample( + string path, + out long decodedPcmBytes, + out double durationSeconds) + { + ThrowIfDisposed(); + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("FMOD sample path is empty.", nameof(path)); + if (!global::System.IO.File.Exists(path)) + throw new global::System.IO.FileNotFoundException("FMOD sample file was not found.", path); + + var mode = FMOD.MODE.CREATESAMPLE | + FMOD.MODE.LOOP_OFF | + FMOD.MODE._2D | + FMOD.MODE.ACCURATETIME; + Check(_system.createSound(path, mode, out var sound), $"create sample '{path}'"); + try + { + Check(sound.getLength(out var pcmBytes, FMOD.TIMEUNIT.PCMBYTES), "query sample PCM size"); + Check(sound.getLength(out var durationMilliseconds, FMOD.TIMEUNIT.MS), "query sample duration"); + decodedPcmBytes = pcmBytes; + durationSeconds = Math.Max(0.01, durationMilliseconds / 1000.0); + return sound; + } + catch + { + sound.release(); + sound.clearHandle(); + throw; + } + } + + internal FmodChannel PlayScheduled(FmodSound sound, double leadSeconds, out ulong startRawClock) + { + ThrowIfDisposed(); + if (!sound.hasHandle()) + throw new InvalidOperationException("FMOD sound is not prepared."); + Check(_system.playSound(sound, _masterGroup, true, out var channel), "allocate playback channel"); + try + { + startRawClock = ScheduleAfter(leadSeconds); + Check(channel.setDelay(startRawClock, 0, false), "schedule playback channel"); + Check(channel.setPaused(false), "start playback channel"); + return channel; + } + catch + { + channel.stop(); + channel.clearHandle(); + throw; + } + } + + internal FmodChannel PlayScheduledAt(FmodSound sound, ulong startRawClock) + { + ThrowIfDisposed(); + if (!sound.hasHandle()) + throw new InvalidOperationException("FMOD sound is not prepared."); + Check(_system.playSound(sound, _masterGroup, true, out var channel), "allocate scheduled channel"); + try + { + Check(channel.setDelay(startRawClock, 0, false), "schedule playback channel"); + Check(channel.setPaused(false), "start scheduled playback channel"); + return channel; + } + catch + { + channel.stop(); + channel.clearHandle(); + throw; + } + } + + internal FmodChannel PlayImmediate(FmodSound sound) + { + ThrowIfDisposed(); + if (!sound.hasHandle()) + throw new InvalidOperationException("FMOD sound is not prepared."); + Check(_system.playSound(sound, _masterGroup, false, out var channel), "play reactive keysound"); + return channel; + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + if (_system.hasHandle()) + { + _system.setCallback(null, 0); + _system.close(); + _system.release(); + _system.clearHandle(); + } + _masterGroup.clearHandle(); + } + + internal static double LogicalSeconds( + ulong rawClock, + ulong pausedSamples, + int sampleRate) + { + if (sampleRate <= 0) + throw new ArgumentOutOfRangeException(nameof(sampleRate)); + var activeSamples = rawClock >= pausedSamples ? rawClock - pausedSamples : 0; + return activeSamples / (double)sampleRate; + } + + internal static void Check(FMOD.RESULT result, string operation) + { + if (result == FMOD.RESULT.OK) + return; + throw new InvalidOperationException( + $"FMOD could not {operation}: {result} ({FMOD.Error.String(result)})"); + } + + private ulong GetRawDspClock() + { + Check(_masterGroup.getDSPClock(out var dspClock, out _), "read DSP clock"); + return dspClock; + } + + private FMOD.RESULT OnOutputEvent( + IntPtr system, + FMOD.SYSTEM_CALLBACK_TYPE type, + IntPtr commandData1, + IntPtr commandData2, + IntPtr userData) + { + // OUTPUTUNDERRUN may arrive on the mixer thread. Keep this callback lock-free and + // defer Unity logging and native driver queries to the next main-thread Update. + if ((type & FMOD.SYSTEM_CALLBACK_TYPE.DEVICEREINITIALIZE) != 0) + Interlocked.Increment(ref _deviceReinitializeCount); + if ((type & FMOD.SYSTEM_CALLBACK_TYPE.OUTPUTUNDERRUN) != 0) + Interlocked.Increment(ref _outputUnderrunCount); + return FMOD.RESULT.OK; + } + + private void ReportOutputEvents() + { + var reinitializations = Volatile.Read(ref _deviceReinitializeCount); + if (reinitializations != _reportedDeviceReinitializeCount) + { + var delta = reinitializations - _reportedDeviceReinitializeCount; + _reportedDeviceReinitializeCount = reinitializations; + RefreshOutputDevices(); + CaptureSelectedOutputDevice(); + Debug.LogWarning( + $"[ProjectQuint Audio] FMOD output device reinitialized {delta} time(s); " + + $"automatic device switching remains enabled. {DescribeOutputDevice()}."); + } + + var underruns = Volatile.Read(ref _outputUnderrunCount); + if (underruns != _reportedOutputUnderrunCount) + { + var delta = underruns - _reportedOutputUnderrunCount; + _reportedOutputUnderrunCount = underruns; + Debug.LogWarning( + $"[ProjectQuint Audio] FMOD output underrun detected {delta} time(s), " + + $"{underruns} total. Increase DSP BUFFER if this repeats."); + } + } + + internal void RefreshOutputDevices() + { + ThrowIfDisposed(); + if (_noSoundNrt) + { + _outputDevices = new[] { AudioOutputDeviceInfo.SystemDefault }; + return; + } + + Check(_system.getNumDrivers(out var driverCount), "enumerate output devices"); + var devices = new List(Math.Max(1, driverCount + 1)) + { + AudioOutputDeviceInfo.SystemDefault, + }; + for (var driverIndex = 0; driverIndex < driverCount; driverIndex++) + { + var result = _system.getDriverInfo( + driverIndex, + out var name, + 512, + out Guid guid, + out var sampleRate, + out _, + out var channels); + if (result != FMOD.RESULT.OK) + { + Debug.LogWarning( + $"[ProjectQuint Audio] Could not inspect output device {driverIndex}: " + + $"{result} ({FMOD.Error.String(result)})."); + continue; + } + + devices.Add(new AudioOutputDeviceInfo( + guid.ToString("D"), + name, + sampleRate, + channels)); + } + _outputDevices = devices.ToArray(); + } + + private void SelectPreferredOutputDevice(string preferredOutputDeviceId) + { + Check(_system.getNumDrivers(out var driverCount), "enumerate output devices before initialization"); + for (var driverIndex = 0; driverIndex < driverCount; driverIndex++) + { + var result = _system.getDriverInfo( + driverIndex, + out Guid guid, + out _, + out _, + out _); + if (result != FMOD.RESULT.OK || + !string.Equals( + guid.ToString("D"), + preferredOutputDeviceId, + StringComparison.OrdinalIgnoreCase)) + continue; + + Check(_system.setDriver(driverIndex), $"select output device {preferredOutputDeviceId}"); + SelectedOutputDeviceId = guid.ToString("D"); + return; + } + + DeviceSelectionFallback = true; + SelectedOutputDeviceId = string.Empty; + } + + private void CaptureSelectedOutputDevice() + { + if (_noSoundNrt) + { + SelectedOutputDeviceId = string.Empty; + ActiveOutputDeviceId = string.Empty; + ActiveOutputDeviceName = "NO SOUND"; + return; + } + + if (_system.getDriver(out var driverIndex) != FMOD.RESULT.OK || + _system.getDriverInfo( + driverIndex, + out var activeName, + 512, + out Guid guid, + out _, + out _, + out _) != FMOD.RESULT.OK) + { + SelectedOutputDeviceId = string.Empty; + ActiveOutputDeviceId = string.Empty; + ActiveOutputDeviceName = string.Empty; + DeviceSelectionFallback = true; + return; + } + + ActiveOutputDeviceId = guid.ToString("D"); + ActiveOutputDeviceName = string.IsNullOrWhiteSpace(activeName) + ? "UNKNOWN DEVICE" + : activeName.Trim(); + SelectedOutputDeviceId = + DeviceSelectionFallback || string.IsNullOrWhiteSpace(RequestedOutputDeviceId) + ? string.Empty + : ActiveOutputDeviceId; + DeviceSelectionFallback = !string.Equals( + ActiveOutputDeviceId, + RequestedOutputDeviceId, + StringComparison.OrdinalIgnoreCase) && + !string.IsNullOrWhiteSpace(RequestedOutputDeviceId); + } + + private string DescribeOutputDevice() + { + var snapshot = CaptureOutputConfiguration(); + return $"FMOD {snapshot.OutputType} output '{snapshot.DeviceName}', " + + $"mix {snapshot.SampleRate:N0} Hz, DSP {snapshot.BufferLength} x {snapshot.BufferCount}"; + } + + private AudioOutputConfigurationSnapshot CaptureOutputConfiguration() + { + Check(_system.getOutput(out var outputType), "query output type"); + Check(_system.getDSPBufferSize(out var bufferLength, out var bufferCount), "query FMOD DSP buffer"); + var driverName = "default device"; + if (_system.getDriver(out var driverIndex) == FMOD.RESULT.OK && + _system.getDriverInfo( + driverIndex, + out var detectedName, + 256, + out _, + out _, + out _, + out _) == FMOD.RESULT.OK) + { + if (!string.IsNullOrWhiteSpace(detectedName)) + driverName = detectedName; + } + + return new AudioOutputConfigurationSnapshot( + FmodCoreSongAudioBackendFactory.FmodBackendId, + outputType.ToString(), + driverName, + checked((int)bufferLength), + bufferCount, + _outputSampleRate, + ActiveOutputDeviceId); + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(FmodCoreAudioContext)); + } + } + + public sealed class FmodCoreSongAudioBackendFactory : ISongAudioBackendFactory, IDisposable + { + public const string FmodBackendId = "FMOD_CORE"; + private readonly FmodCoreAudioContext _context; + private bool _disposed; + + public FmodCoreSongAudioBackendFactory( + uint dspBufferLength = 0, + string preferredOutputDeviceId = "") + : this(dspBufferLength, false, preferredOutputDeviceId) + { + } + + internal FmodCoreSongAudioBackendFactory(uint dspBufferLength, bool noSoundNrt) => + _context = new FmodCoreAudioContext(dspBufferLength, noSoundNrt); + + private FmodCoreSongAudioBackendFactory( + uint dspBufferLength, + bool noSoundNrt, + string preferredOutputDeviceId) => + _context = new FmodCoreAudioContext( + dspBufferLength, + noSoundNrt, + preferredOutputDeviceId); + + public string BackendId => FmodBackendId; + public IAudioClock Clock => _context; + public AudioOutputConfigurationSnapshot OutputConfiguration => _context.OutputConfiguration; + public IReadOnlyList OutputDevices => _context.OutputDevices; + public string SelectedOutputDeviceId => _context.SelectedOutputDeviceId; + public string ActiveOutputDeviceId => _context.ActiveOutputDeviceId; + public string ActiveOutputDeviceName => _context.ActiveOutputDeviceName; + public bool DeviceSelectionFallback => _context.DeviceSelectionFallback; + + internal FmodAudioSyncMetronome CreateAudioSyncMetronome(float volume) => + new FmodAudioSyncMetronome(_context, volume); + + internal void UpdateAudioSystem() => _context.Update(); + + public void RefreshOutputDevices() + { + if (_disposed) + throw new ObjectDisposedException(nameof(FmodCoreSongAudioBackendFactory)); + _context.RefreshOutputDevices(); + } + + public ISongAudioBackend Create(GameObject resourceOwner) + { + if (_disposed) + throw new ObjectDisposedException(nameof(FmodCoreSongAudioBackendFactory)); + if (resourceOwner == null) + throw new ArgumentNullException(nameof(resourceOwner)); + return new FmodCoreSongAudioBackend(_context); + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + _context.Dispose(); + } + } + + internal sealed class FmodCoreSongAudioBackend : ISongAudioBackend + { + private readonly FmodCoreAudioContext _context; + private readonly AudioAssetStore _assets = new AudioAssetStore(); + private readonly PremixedSongRenderer _premix; + private readonly FmodReactiveSoundBank _reactiveBank; + private readonly FmodReactivePlaybackEngine _reactivePlayback; + private PlayableChart _chart; + private FmodSound _sound; + private FmodChannel _channel; + private long _premixedStreamBytes; + private string _loadError = string.Empty; + private string _fmodError = string.Empty; + private bool _paused; + private bool _loadCompleted; + private bool _reactiveLoaded; + private bool _activeReactivePlayback; + private bool _disposed; + private bool _runtimeErrorReported; + + public FmodCoreSongAudioBackend( + FmodCoreAudioContext context, + PremixedSongCache premixCache = null) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + _premix = new PremixedSongRenderer(premixCache); + _reactiveBank = new FmodReactiveSoundBank(_context); + _reactivePlayback = new FmodReactivePlaybackEngine(_context); + } + + public string BackendId => FmodCoreSongAudioBackendFactory.FmodBackendId; + public IAudioClock Clock => _context; + public double StartDspTime { get; private set; } + public int LoadedClipCount => _reactiveLoaded + ? _reactiveBank.LoadedSoundIdCount + : _sound.hasHandle() ? 1 : 0; + public int MissingClipCount => _reactiveLoaded + ? _reactiveBank.MissingSoundCount + : _assets.MissingClipCount; + public int DecodeFailedClipCount => _reactiveLoaded + ? _reactiveBank.DecodeFailedSoundCount + : _assets.DecodeFailedClipCount; + public int SilentPlaceholderCount => _reactiveLoaded + ? _reactiveBank.SilentPlaceholderCount + : _assets.SilentPlaceholderCount; + public int ScheduledSourceCount => _activeReactivePlayback + ? _reactivePlayback.ActiveVoiceCount + : _channel.hasHandle() ? 1 : 0; + public int PendingScheduledEventCount => _activeReactivePlayback + ? _reactivePlayback.PendingEventCount + : 0; + public int DroppedLateEventCount => _activeReactivePlayback + ? _reactivePlayback.DroppedLateEventCount + : 0; + public bool SmoothMixReady => _sound.hasHandle() && string.IsNullOrEmpty(_fmodError); + public string SmoothMixError => string.IsNullOrWhiteSpace(_fmodError) ? _premix.Error : _fmodError; + public bool SmoothMixCacheHit => _premix.CacheHit; + public string SmoothMixCacheStatus => _premix.CacheStatus; + public string LoadError => _loadError; + public long EstimatedDecodedPcmBytes => _reactiveLoaded + ? _reactiveBank.EstimatedDecodedPcmBytes + : _assets.EstimatedDecodedPcmBytes; + // CREATESTREAM keeps the decoded song out of managed memory. This value intentionally + // reports zero resident premixed PCM; encoded file size is exposed separately in logs. + public long EstimatedPremixedPcmBytes => 0; + public long EncodedPremixedStreamBytes => _premixedStreamBytes; + public long EstimatedTotalPcmBytes => checked(EstimatedDecodedPcmBytes + EstimatedPremixedPcmBytes); + + public IEnumerator Load( + PlayableChart chart, + Action progress, + Func cancellationRequested = null, + bool reactiveKeySoundsEnabled = true) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + ThrowIfDisposed(); + ResetLoadedResources(); + _chart = chart; + if (reactiveKeySoundsEnabled) + { + yield return _reactiveBank.Load(chart, progress, cancellationRequested); + if (cancellationRequested?.Invoke() == true) + yield break; + if (!string.IsNullOrWhiteSpace(_reactiveBank.Error)) + { + _loadError = _reactiveBank.Error; + yield break; + } + _reactiveLoaded = true; + _loadCompleted = true; + progress?.Invoke( + _reactiveBank.LoadedSoundIdCount, + _reactiveBank.LoadedSoundIdCount, + "FMOD Core native keysound bank ready"); + yield break; + } + + _assets.PrepareForSequentialMix(); + _loadCompleted = true; + progress?.Invoke(0, 0, "FMOD Core sequential smooth mix selected"); + } + + public void PrepareReactivePlayback(double leadSeconds = 0.35) + { + ThrowIfDisposed(); + EnsureChartReady(); + if (!_reactiveLoaded) + throw new InvalidOperationException("FMOD reactive keysounds were not loaded for this song."); + _reactivePlayback.Prepare(_chart, _reactiveBank); + } + + public IEnumerator PrepareSmoothPlaybackMix( + Action progress, + Func cancellationRequested = null) + { + ThrowIfDisposed(); + EnsureChartReady(); + yield return _premix.Prepare( + _chart, + _assets, + progress, + cancellationRequested, + PremixedSongOutput.FmodStreamFile, + _context.OutputSampleRate); + if (!_premix.IsReady || cancellationRequested?.Invoke() == true) + yield break; + + var streamEntry = _premix.TakeStreamEntry(); + if (!streamEntry.IsValid) + { + _fmodError = "Smooth playback FLAC stream was not produced."; + yield break; + } + if (!TryCreatePremixStream(streamEntry.Path)) + yield break; + + while (true) + { + if (cancellationRequested?.Invoke() == true) + { + ReleaseSound(); + yield break; + } + if (!TryUpdatePremixOpenState(out var state, out var bufferedPercent)) + yield break; + progress?.Invoke((int)bufferedPercent, 100, "Opening FMOD FLAC stream"); + if (state == FMOD.OPENSTATE.READY) + break; + if (state == FMOD.OPENSTATE.ERROR) + { + ReleaseSound(); + _fmodError = "FMOD could not open the FLAC premix stream."; + yield break; + } + yield return null; + } + _premixedStreamBytes = streamEntry.EncodedBytes; + progress?.Invoke(100, 100, "FMOD Core FLAC stream ready"); + Debug.Log( + $"[ProjectQuint Audio] FMOD CREATESTREAM | NONBLOCKING ready: " + + $"{streamEntry.FrameCount:N0} frames @ {streamEntry.SampleRate:N0} Hz, " + + $"{streamEntry.EncodedBytes / (1024d * 1024d):0.0} MiB FLAC, " + + $"cache {_premix.CacheStatus}; managed premixed PCM: 0 bytes"); + streamEntry = default; + } + + private bool TryCreatePremixStream(string path) + { + try + { + _sound = _context.CreateFileStream(path); + return true; + } + catch (Exception exception) + { + ReleaseSound(); + _fmodError = exception.Message; + return false; + } + } + + private bool TryUpdatePremixOpenState(out FMOD.OPENSTATE state, out uint bufferedPercent) + { + state = FMOD.OPENSTATE.ERROR; + bufferedPercent = 0; + try + { + _context.Update(); + state = _context.GetOpenState(_sound, out bufferedPercent); + return true; + } + catch (Exception exception) + { + ReleaseSound(); + _fmodError = exception.Message; + return false; + } + } + + public void BeginPlayback(bool reactiveKeySoundsEnabled, double leadSeconds = 0.35) + { + ThrowIfDisposed(); + EnsureChartReady(); + if (reactiveKeySoundsEnabled) + { + if (!_reactiveLoaded) + throw new InvalidOperationException("FMOD reactive keysounds were not loaded for this song."); + StopChannel(); + _context.Update(); + _reactivePlayback.Begin(leadSeconds); + _activeReactivePlayback = true; + StartDspTime = _reactivePlayback.StartDspTime; + return; + } + if (!_sound.hasHandle()) + throw new InvalidOperationException( + string.IsNullOrWhiteSpace(_fmodError) + ? "FMOD smooth playback sound is not prepared." + : _fmodError); + + StopChannel(); + _context.Update(); + _channel = _context.PlayScheduled(_sound, leadSeconds, out var startRawClock); + _activeReactivePlayback = false; + StartDspTime = _context.LogicalTimeForRawClock(startRawClock); + } + + public void Update() + { + if (_disposed) + return; + try + { + _context.Update(); + if (_activeReactivePlayback) + _reactivePlayback.Update(); + } + catch (Exception exception) + { + _fmodError = exception.Message; + if (_runtimeErrorReported) + return; + _runtimeErrorReported = true; + Debug.LogError($"[ProjectQuint Audio] FMOD Core update failed: {_fmodError}"); + } + } + + public void PlayKeySound(string soundId, int lane) + { + if (_activeReactivePlayback) + _reactivePlayback.PlayKeySound(soundId, lane); + } + + public void SetMasterVolume(float normalizedVolume) + { + ThrowIfDisposed(); + _context.SetMasterVolume(normalizedVolume); + } + + public void Pause() + { + ThrowIfDisposed(); + if (_paused) + return; + if (_activeReactivePlayback) + _reactivePlayback.Pause(); + else if (_channel.hasHandle()) + FmodCoreAudioContext.Check(_channel.setPaused(true), "pause playback channel"); + _context.PauseClock(); + _paused = true; + } + + public void Resume() + { + ThrowIfDisposed(); + if (!_paused) + return; + _context.ResumeClock(); + if (_activeReactivePlayback) + _reactivePlayback.Resume(); + else if (_channel.hasHandle()) + FmodCoreAudioContext.Check(_channel.setPaused(false), "resume playback channel"); + _paused = false; + } + + public void Stop() + { + if (_disposed) + return; + if (_paused) + { + _context.ResumeClock(); + _paused = false; + } + _reactivePlayback.StopPlayback(); + _activeReactivePlayback = false; + StopChannel(); + StartDspTime = 0; + } + + public void Dispose() + { + if (_disposed) + return; + Stop(); + ReleaseSound(); + _reactivePlayback.Clear(); + _reactiveBank.Dispose(); + _premix.Dispose(); + _assets.Dispose(); + _chart = null; + _disposed = true; + } + + private void ResetLoadedResources() + { + Stop(); + ReleaseSound(); + _reactivePlayback.Clear(); + _reactiveBank.Clear(); + _premix.Clear(); + _assets.Clear(); + _chart = null; + _premixedStreamBytes = 0; + _loadError = string.Empty; + _fmodError = string.Empty; + _runtimeErrorReported = false; + _loadCompleted = false; + _reactiveLoaded = false; + _activeReactivePlayback = false; + } + + private void StopChannel() + { + if (!_channel.hasHandle()) + return; + _channel.stop(); + _channel.clearHandle(); + } + + private void ReleaseSound() + { + if (!_sound.hasHandle()) + return; + _sound.release(); + _sound.clearHandle(); + _premixedStreamBytes = 0; + } + + private void EnsureChartReady() + { + if (_chart == null || !_loadCompleted) + { + throw new InvalidOperationException( + string.IsNullOrWhiteSpace(_loadError) + ? "FMOD audio assets are not ready." + : _loadError); + } + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(FmodCoreSongAudioBackend)); + } + + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreAudioBackend.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreAudioBackend.cs.meta new file mode 100644 index 0000000..8877ceb --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreAudioBackend.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: de5e1984f3a2a43ab89ab705fff95575 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreReactivePlayback.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreReactivePlayback.cs new file mode 100644 index 0000000..05c9457 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreReactivePlayback.cs @@ -0,0 +1,434 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ProjectQuint.Core.Charts; +using UnityEngine; +using FmodChannel = FMOD.Channel; +using FmodSound = FMOD.Sound; + +namespace ProjectQuint.Audio +{ + internal readonly struct FmodReactiveSoundAsset + { + public FmodReactiveSoundAsset(FmodSound sound, long decodedPcmBytes, double durationSeconds) + { + Sound = sound; + DecodedPcmBytes = decodedPcmBytes; + DurationSeconds = Math.Max(0.01, durationSeconds); + } + + public FmodSound Sound { get; } + public long DecodedPcmBytes { get; } + public double DurationSeconds { get; } + } + + /// + /// Per-song native sample bank for reactive mode. FMOD decodes source files directly, so + /// keysound ON does not create a second Unity AudioClip graph before native playback. + /// + internal sealed class FmodReactiveSoundBank : IDisposable + { + private readonly FmodCoreAudioContext _context; + private readonly Dictionary _soundsById = + new Dictionary(StringComparer.Ordinal); + private readonly Dictionary _soundsByPath = + new Dictionary(StringComparer.OrdinalIgnoreCase); + private bool _disposed; + + public FmodReactiveSoundBank(FmodCoreAudioContext context) => + _context = context ?? throw new ArgumentNullException(nameof(context)); + + public int LoadedSoundIdCount => _soundsById.Count; + public int MissingSoundCount { get; private set; } + public int DecodeFailedSoundCount { get; private set; } + public int SilentPlaceholderCount { get; private set; } + public long EstimatedDecodedPcmBytes { get; private set; } + public string Error { get; private set; } = string.Empty; + public bool IsReady { get; private set; } + + public IEnumerator Load( + PlayableChart chart, + Action progress, + Func cancellationRequested = null) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + ThrowIfDisposed(); + Clear(); + + var soundIds = new HashSet(StringComparer.Ordinal); + foreach (var note in chart.Notes) + if (!string.IsNullOrWhiteSpace(note.SoundId)) soundIds.Add(note.SoundId); + foreach (var item in chart.BackgroundAudioEvents) + if (!string.IsNullOrWhiteSpace(item.SoundId)) soundIds.Add(item.SoundId); + foreach (var item in chart.LaneSoundEvents) + if (!string.IsNullOrWhiteSpace(item.SoundId)) soundIds.Add(item.SoundId); + var orderedIds = soundIds.OrderBy(value => value, StringComparer.Ordinal).ToList(); + + for (var index = 0; index < orderedIds.Count; index++) + { + if (cancellationRequested?.Invoke() == true) + { + ReleaseSounds(); + yield break; + } + + var soundId = orderedIds[index]; + progress?.Invoke(index, orderedIds.Count, soundId); + if (!chart.AudioResources.TryGetValue(soundId, out var declaredPath)) + { + MissingSoundCount++; + continue; + } + + var resolvedPath = AudioAssetStore.ResolveReferencedAudioPath( + chart.Metadata.SourcePath, + declaredPath); + if (string.IsNullOrWhiteSpace(resolvedPath)) + { + MissingSoundCount++; + continue; + } + + resolvedPath = Path.GetFullPath(resolvedPath); + if (!AudioAssetStore.TryClassifyWaveResource( + resolvedPath, + soundId, + out var resourceStatus, + out var resourceError)) + { + DecodeFailedSoundCount++; + Debug.LogWarning( + $"[ProjectQuint Audio] FMOD skipped keysound {soundId} '{resolvedPath}': " + + resourceError); + continue; + } + if (resourceStatus == AudioResourceLoadStatus.SilentPlaceholder) + { + SilentPlaceholderCount++; + continue; + } + + if (_soundsByPath.TryGetValue(resolvedPath, out var sharedAsset)) + { + _soundsById[soundId] = sharedAsset; + continue; + } + + FmodSound sound = default; + try + { + sound = _context.CreateFileSample( + resolvedPath, + out var decodedPcmBytes, + out var durationSeconds); + if (decodedPcmBytes > AudioAssetStore.DefaultDecodedPcmBudgetBytes - EstimatedDecodedPcmBytes) + { + sound.release(); + sound.clearHandle(); + Error = + $"Decoded FMOD keysounds exceed the per-song audio budget " + + $"({FormatBytes(AudioAssetStore.DefaultDecodedPcmBudgetBytes)}). Last asset: {soundId}."; + ReleaseSounds(); + yield break; + } + + var asset = new FmodReactiveSoundAsset(sound, decodedPcmBytes, durationSeconds); + _soundsByPath[resolvedPath] = asset; + _soundsById[soundId] = asset; + EstimatedDecodedPcmBytes = checked(EstimatedDecodedPcmBytes + decodedPcmBytes); + } + catch (Exception exception) + { + if (sound.hasHandle()) + sound.release(); + DecodeFailedSoundCount++; + Debug.LogWarning( + $"[ProjectQuint Audio] FMOD skipped keysound {soundId} '{resolvedPath}': " + + exception.Message); + } + + _context.Update(); + yield return null; + } + + if (cancellationRequested?.Invoke() == true) + { + ReleaseSounds(); + yield break; + } + + if (orderedIds.Count > 0 && + _soundsById.Count == 0 && + MissingSoundCount + DecodeFailedSoundCount > 0) + { + Error = + $"No playable FMOD keysounds could be loaded " + + $"({MissingSoundCount} missing, {DecodeFailedSoundCount} decode failed)."; + ReleaseSounds(); + yield break; + } + + IsReady = true; + progress?.Invoke(orderedIds.Count, orderedIds.Count, string.Empty); + } + + public bool TryGet(string soundId, out FmodReactiveSoundAsset asset) => + _soundsById.TryGetValue(soundId ?? string.Empty, out asset) && asset.Sound.hasHandle(); + + public void Clear() + { + ThrowIfDisposed(); + ReleaseSounds(); + MissingSoundCount = 0; + DecodeFailedSoundCount = 0; + SilentPlaceholderCount = 0; + EstimatedDecodedPcmBytes = 0; + Error = string.Empty; + IsReady = false; + } + + public void Dispose() + { + if (_disposed) + return; + ReleaseSounds(); + _disposed = true; + } + + private void ReleaseSounds() + { + foreach (var asset in _soundsByPath.Values) + if (asset.Sound.hasHandle()) asset.Sound.release(); + _soundsByPath.Clear(); + _soundsById.Clear(); + EstimatedDecodedPcmBytes = 0; + IsReady = false; + } + + private static string FormatBytes(long bytes) => + bytes >= 1024L * 1024L + ? $"{bytes / (1024d * 1024d):0.#} MiB" + : $"{bytes / 1024d:0.#} KiB"; + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(FmodReactiveSoundBank)); + } + } + + /// + /// Schedules BGM objects against the FMOD DSP clock while note keysounds remain immediate. + /// Only a short look-ahead is allocated, and future reservations are rebuilt after Pause. + /// + internal sealed class FmodReactivePlaybackEngine + { + internal const double SchedulingHorizonSeconds = 1.0; + internal const double LateEventDropThresholdSeconds = 0.100; + private const double MinimumScheduleLeadSeconds = 0.005; + + private readonly FmodCoreAudioContext _context; + private readonly List _backgroundEvents = new List(); + private readonly List _voices = new List(); + private FmodReactiveSoundBank _bank; + private int _nextBackgroundEvent; + private bool _prepared; + private bool _running; + private bool _paused; + + public FmodReactivePlaybackEngine(FmodCoreAudioContext context) => + _context = context ?? throw new ArgumentNullException(nameof(context)); + + public double StartDspTime { get; private set; } + public int ActiveVoiceCount => _voices.Count; + public int PendingEventCount => Math.Max(0, _backgroundEvents.Count - _nextBackgroundEvent); + public int DroppedLateEventCount { get; private set; } + + public void Prepare(PlayableChart chart, FmodReactiveSoundBank bank) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + if (bank == null || !bank.IsReady) + throw new InvalidOperationException("FMOD reactive sound bank is not ready."); + + Clear(); + _bank = bank; + _backgroundEvents.AddRange(AudioPlaybackTimeline.Build(chart, reactiveKeySoundsEnabled: true)); + _prepared = true; + } + + public void Begin(double leadSeconds) + { + if (!_prepared || _bank == null || !_bank.IsReady) + throw new InvalidOperationException("FMOD reactive playback was not prepared."); + + StopVoices(); + _nextBackgroundEvent = 0; + DroppedLateEventCount = 0; + _paused = false; + _running = true; + var startRawClock = _context.ScheduleAfter(leadSeconds); + StartDspTime = _context.LogicalTimeForRawClock(startRawClock); + ScheduleBackgroundEvents(); + } + + public void Update() + { + PruneFinishedVoices(); + if (_running && !_paused) + ScheduleBackgroundEvents(); + } + + public void PlayKeySound(string soundId, int lane) + { + if (!_running || _paused || lane < 0 || lane >= 8 || string.IsNullOrWhiteSpace(soundId) || + _bank == null || !_bank.TryGet(soundId, out var asset)) + return; + + var channel = _context.PlayImmediate(asset.Sound); + _voices.Add(new Voice(channel, reactive: true, eventIndex: -1, scheduledSongTime: double.NaN)); + } + + public void Pause() + { + if (!_running || _paused) + return; + + var songTime = _context.CurrentTime - StartDspTime; + var rewindIndex = _nextBackgroundEvent; + for (var index = _voices.Count - 1; index >= 0; index--) + { + var voice = _voices[index]; + if (!voice.Reactive && voice.ScheduledSongTime > songTime) + { + StopVoice(voice.Channel); + rewindIndex = Math.Min(rewindIndex, voice.EventIndex); + _voices.RemoveAt(index); + continue; + } + if (!TrySetPaused(voice.Channel, true)) + _voices.RemoveAt(index); + } + _nextBackgroundEvent = rewindIndex; + _paused = true; + } + + public void Resume() + { + if (!_running || !_paused) + return; + for (var index = _voices.Count - 1; index >= 0; index--) + if (!TrySetPaused(_voices[index].Channel, false)) _voices.RemoveAt(index); + _paused = false; + ScheduleBackgroundEvents(); + } + + public void StopPlayback() + { + StopVoices(); + _nextBackgroundEvent = 0; + DroppedLateEventCount = 0; + StartDspTime = 0; + _running = false; + _paused = false; + } + + public void Clear() + { + StopPlayback(); + _backgroundEvents.Clear(); + _bank = null; + _prepared = false; + } + + private void ScheduleBackgroundEvents() + { + var currentLogicalTime = _context.CurrentTime; + var currentSongTime = currentLogicalTime - StartDspTime; + var horizonSongTime = currentSongTime + SchedulingHorizonSeconds; + while (_nextBackgroundEvent < _backgroundEvents.Count) + { + var eventIndex = _nextBackgroundEvent; + var item = _backgroundEvents[eventIndex]; + if (item.Time > horizonSongTime) + break; + _nextBackgroundEvent++; + + if (!_bank.TryGet(item.SoundId, out var asset)) + continue; + var scheduledLogicalTime = StartDspTime + item.Time; + if (scheduledLogicalTime < currentLogicalTime - LateEventDropThresholdSeconds) + { + DroppedLateEventCount++; + continue; + } + + var rawClock = scheduledLogicalTime <= currentLogicalTime + MinimumScheduleLeadSeconds + ? _context.ScheduleAfter(MinimumScheduleLeadSeconds) + : _context.RawClockForLogicalTime(scheduledLogicalTime); + var channel = _context.PlayScheduledAt(asset.Sound, rawClock); + _voices.Add(new Voice(channel, reactive: false, eventIndex, item.Time)); + } + } + + private void PruneFinishedVoices() + { + for (var index = _voices.Count - 1; index >= 0; index--) + { + var result = _voices[index].Channel.isPlaying(out var playing); + if (result == FMOD.RESULT.OK && playing) + continue; + if (result != FMOD.RESULT.OK && !IsExpiredChannel(result)) + FmodCoreAudioContext.Check(result, "query reactive playback channel"); + _voices.RemoveAt(index); + } + } + + private void StopVoices() + { + foreach (var voice in _voices) + StopVoice(voice.Channel); + _voices.Clear(); + } + + private static void StopVoice(FmodChannel channel) + { + var result = channel.stop(); + if (result != FMOD.RESULT.OK && !IsExpiredChannel(result)) + FmodCoreAudioContext.Check(result, "stop reactive playback channel"); + } + + private static bool TrySetPaused(FmodChannel channel, bool paused) + { + var result = channel.setPaused(paused); + if (result == FMOD.RESULT.OK) + return true; + if (IsExpiredChannel(result)) + return false; + FmodCoreAudioContext.Check(result, paused ? "pause reactive playback channel" : "resume reactive playback channel"); + return false; + } + + private static bool IsExpiredChannel(FMOD.RESULT result) => + result == FMOD.RESULT.ERR_INVALID_HANDLE || result == FMOD.RESULT.ERR_CHANNEL_STOLEN; + + private readonly struct Voice + { + public Voice(FmodChannel channel, bool reactive, int eventIndex, double scheduledSongTime) + { + Channel = channel; + Reactive = reactive; + EventIndex = eventIndex; + ScheduledSongTime = scheduledSongTime; + } + + public FmodChannel Channel { get; } + public bool Reactive { get; } + public int EventIndex { get; } + public double ScheduledSongTime { get; } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreReactivePlayback.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreReactivePlayback.cs.meta new file mode 100644 index 0000000..0285fd1 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/FmodCoreReactivePlayback.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ad2e4562a96c24a5abd4d789faebe347 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongCache.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongCache.cs new file mode 100644 index 0000000..86c4dfa --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongCache.cs @@ -0,0 +1,721 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using ProjectQuint.Core.Charts; +using ProjectQuint.Persistence; + +namespace ProjectQuint.Audio +{ + internal enum PremixedSongCacheTier + { + Automatic, + Pinned, + } + + internal readonly struct PremixedSongCacheEntry + { + public PremixedSongCacheEntry( + string path, + int sampleRate, + int channels, + long frameCount, + long encodedBytes, + PremixedSongCacheTier tier) + { + Path = path ?? string.Empty; + SampleRate = sampleRate; + Channels = channels; + FrameCount = frameCount; + EncodedBytes = Math.Max(0, encodedBytes); + Tier = tier; + } + + public string Path { get; } + public int SampleRate { get; } + public int Channels { get; } + public long FrameCount { get; } + public long EncodedBytes { get; } + public PremixedSongCacheTier Tier { get; } + public bool IsValid => !string.IsNullOrWhiteSpace(Path) && File.Exists(Path) && + SampleRate > 0 && Channels > 0 && FrameCount > 0; + } + + internal readonly struct PremixedSongCacheSnapshot + { + public PremixedSongCacheSnapshot( + int automaticEntryCount, + long automaticBytes, + long automaticMaximumBytes, + int pinnedEntryCount, + long pinnedBytes) + { + AutomaticEntryCount = Math.Max(0, automaticEntryCount); + AutomaticBytes = Math.Max(0, automaticBytes); + AutomaticMaximumBytes = Math.Max(1, automaticMaximumBytes); + PinnedEntryCount = Math.Max(0, pinnedEntryCount); + PinnedBytes = Math.Max(0, pinnedBytes); + } + + public int AutomaticEntryCount { get; } + public long AutomaticBytes { get; } + public long AutomaticMaximumBytes { get; } + public int PinnedEntryCount { get; } + public long PinnedBytes { get; } + public int EntryCount => AutomaticEntryCount + PinnedEntryCount; + public long TotalBytes => AutomaticBytes + PinnedBytes; + public long MaximumBytes => AutomaticMaximumBytes; + public string Label => + $"AUTO {AutomaticEntryCount:N0} {FormatBytes(AutomaticBytes)} / {FormatBytes(AutomaticMaximumBytes)}" + + $" // PINNED {PinnedEntryCount:N0} {FormatBytes(PinnedBytes)}"; + + internal static string FormatBytes(long bytes) => bytes >= 1024L * 1024L * 1024L + ? $"{bytes / (1024d * 1024d * 1024d):0.00} GiB" + : $"{bytes / (1024d * 1024d):0.0} MiB"; + } + + internal readonly struct PremixedSongCacheReadResult + { + public PremixedSongCacheReadResult(bool hit, PremixedSongCacheEntry entry, string status) + { + Hit = hit; + Entry = entry; + Status = status ?? string.Empty; + } + + public bool Hit { get; } + public PremixedSongCacheEntry Entry { get; } + public string Status { get; } + } + + /// + /// Content-addressed FLAC cache for the keysound-off timeline. Automatic objects are governed + /// by a 4 GiB LRU; approved pack objects are stored in an unbounded pinned tier. Both tiers use + /// the same content key so identical arrangements can hit across unrelated BMS packs. + /// + internal sealed class PremixedSongCache + { + private const string AudioExtension = ".flac"; + private const string MetadataExtension = ".pqmeta"; + private const string LegacyExtension = ".pqmix"; + private const string MetadataMagic = "PQFLAC02"; + private const int SchemaVersion = 2; + private const int MixAlgorithmVersion = 3; + private const int HashBytes = 32; + private const int IoBufferBytes = 1024 * 1024; + private const string AfconvertPath = "/usr/bin/afconvert"; + + public const long DefaultMaximumBytes = 4L * 1024L * 1024L * 1024L; + + private static readonly Lazy SharedInstance = + new Lazy(() => new PremixedSongCache( + PersistencePaths.PremixCache, + DefaultMaximumBytes, + PersistencePaths.PinnedPremixCache)); + private static readonly object FingerprintGate = new object(); + private static readonly Dictionary Fingerprints = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + private readonly string _automaticRootPath; + private readonly string _pinnedRootPath; + private readonly long _maximumBytes; + private bool _legacyCleaned; + + public PremixedSongCache( + string rootPath, + long maximumBytes = DefaultMaximumBytes, + string pinnedRootPath = null) + { + if (string.IsNullOrWhiteSpace(rootPath)) + throw new ArgumentException("A premix cache directory is required.", nameof(rootPath)); + if (maximumBytes <= 0) + throw new ArgumentOutOfRangeException(nameof(maximumBytes)); + _automaticRootPath = Path.GetFullPath(rootPath); + _pinnedRootPath = Path.GetFullPath(string.IsNullOrWhiteSpace(pinnedRootPath) + ? rootPath + "-pinned" + : pinnedRootPath); + _maximumBytes = maximumBytes; + } + + public static PremixedSongCache Shared => SharedInstance.Value; + public string RootPath => _automaticRootPath; + public string PinnedRootPath => _pinnedRootPath; + public long MaximumBytes => _maximumBytes; + + public string BuildKey( + PlayableChart chart, + int outputSampleRate, + CancellationToken cancellationToken = default) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + if (outputSampleRate <= 0) + throw new ArgumentOutOfRangeException(nameof(outputSampleRate)); + + var identity = new StringBuilder(4096); + Append(identity, "schema", SchemaVersion.ToString(CultureInfo.InvariantCulture)); + Append(identity, "mixer", MixAlgorithmVersion.ToString(CultureInfo.InvariantCulture)); + Append(identity, "rate", outputSampleRate.ToString(CultureInfo.InvariantCulture)); + Append(identity, "channels", SmoothAudioMixer.OutputChannels.ToString(CultureInfo.InvariantCulture)); + Append(identity, "duration", chart.Duration.ToString("R", CultureInfo.InvariantCulture)); + + var contentHashes = new Dictionary(StringComparer.Ordinal); + var timeline = AudioPlaybackTimeline.Build(chart, reactiveKeySoundsEnabled: false); + foreach (var item in timeline) + { + cancellationToken.ThrowIfCancellationRequested(); + if (!contentHashes.TryGetValue(item.SoundId ?? string.Empty, out var contentHash)) + { + contentHash = ResolveAudioContentHash(chart, item.SoundId, cancellationToken); + contentHashes[item.SoundId ?? string.Empty] = contentHash; + } + Append(identity, "event", item.Time.ToString("R", CultureInfo.InvariantCulture)); + Append(identity, "audio", contentHash); + } + + return ComputeHexHash(Encoding.UTF8.GetBytes(identity.ToString())); + } + + public PremixedSongCacheReadResult TryRead( + string key, + int expectedSampleRate, + CancellationToken cancellationToken = default) + { + ValidateKey(key); + CleanupLegacyFilesOnce(); + var pinned = TryReadTier(key, expectedSampleRate, PremixedSongCacheTier.Pinned, cancellationToken); + if (pinned.Hit) + return pinned; + var automatic = TryReadTier(key, expectedSampleRate, PremixedSongCacheTier.Automatic, cancellationToken); + return automatic.Hit + ? automatic + : string.Equals(automatic.Status, "MISS", StringComparison.OrdinalIgnoreCase) + ? new PremixedSongCacheReadResult(false, default, "MISS") + : automatic; + } + + public string Write( + string key, + SmoothAudioMixResult mix, + CancellationToken cancellationToken = default) => + Write(key, mix, PremixedSongCacheTier.Automatic, cancellationToken).Status; + + public PremixedSongCacheWriteResult Write( + string key, + SmoothAudioMixResult mix, + PremixedSongCacheTier tier, + CancellationToken cancellationToken = default) + { + ValidateKey(key); + if (mix.Samples == null || mix.Samples.Length == 0 || mix.SampleRate <= 0 || mix.FrameCount <= 0) + return new PremixedSongCacheWriteResult(false, default, "STORE SKIPPED // EMPTY MIX"); + + CleanupLegacyFilesOnce(); + var root = RootFor(tier); + Directory.CreateDirectory(root); + var audioPath = EntryPath(key, tier); + var metadataPath = MetadataPath(key, tier); + var token = Guid.NewGuid().ToString("N"); + var wavePath = Path.Combine(root, $"{key}.tmp-{token}.wav"); + var flacPath = Path.Combine(root, $"{key}.tmp-{token}.flac"); + var metadataTemporaryPath = metadataPath + ".tmp-" + token; + try + { + WritePcm24Wave(wavePath, mix, cancellationToken); + EncodeFlac(wavePath, flacPath, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + var file = new FileInfo(flacPath); + if (file.Length <= 4 || !HasFlacSignature(flacPath)) + throw new InvalidDataException("FLAC ENCODER PRODUCED AN INVALID FILE"); + var encodedBytes = file.Length; + if (tier == PremixedSongCacheTier.Automatic && encodedBytes > _maximumBytes) + return new PremixedSongCacheWriteResult( + false, + default, + "STORE SKIPPED // TRACK EXCEEDS AUTOMATIC CACHE BUDGET"); + + var hash = ComputeFileHash(flacPath, cancellationToken); + WriteMetadata( + metadataTemporaryPath, + mix.SampleRate, + mix.Channels, + mix.FrameCount, + encodedBytes, + hash); + ReplaceFile(flacPath, audioPath); + ReplaceFile(metadataTemporaryPath, metadataPath); + Touch(audioPath, metadataPath); + if (tier == PremixedSongCacheTier.Automatic) + PruneToBudget(audioPath); + var entry = new PremixedSongCacheEntry( + audioPath, + mix.SampleRate, + mix.Channels, + mix.FrameCount, + encodedBytes, + tier); + return new PremixedSongCacheWriteResult( + true, + entry, + tier == PremixedSongCacheTier.Pinned ? "PINNED // STORED" : "MISS // STORED FLAC"); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception exception) + { + return new PremixedSongCacheWriteResult(false, default, $"STORE FAILED // {exception.Message}"); + } + finally + { + DeleteIfPresent(wavePath); + DeleteIfPresent(flacPath); + DeleteIfPresent(metadataTemporaryPath); + } + } + + public bool PromoteToPinned(string key, int expectedSampleRate, out string status) + { + ValidateKey(key); + var pinned = TryReadTier(key, expectedSampleRate, PremixedSongCacheTier.Pinned, default); + if (pinned.Hit) + { + status = "PINNED // HIT"; + return true; + } + var automatic = TryReadTier(key, expectedSampleRate, PremixedSongCacheTier.Automatic, default); + if (!automatic.Hit) + { + status = "PINNED // SOURCE MISS"; + return false; + } + + try + { + Directory.CreateDirectory(_pinnedRootPath); + File.Copy(automatic.Entry.Path, EntryPath(key, PremixedSongCacheTier.Pinned), true); + File.Copy( + MetadataPath(key, PremixedSongCacheTier.Automatic), + MetadataPath(key, PremixedSongCacheTier.Pinned), + true); + status = "PINNED // PROMOTED"; + return true; + } + catch (Exception exception) + { + status = $"PIN FAILED // {exception.Message}"; + return false; + } + } + + public PremixedSongCacheSnapshot GetSnapshot() + { + CleanupLegacyFilesOnce(); + var automatic = EnumerateEntries(PremixedSongCacheTier.Automatic).ToArray(); + var pinned = EnumerateEntries(PremixedSongCacheTier.Pinned).ToArray(); + return new PremixedSongCacheSnapshot( + automatic.Length, + automatic.Sum(file => file.Length), + _maximumBytes, + pinned.Length, + pinned.Sum(file => file.Length)); + } + + /// Clears only the bounded automatic tier. Approved pinned packs remain intact. + public void Clear() + { + if (!Directory.Exists(_automaticRootPath)) + return; + foreach (var path in Directory.EnumerateFiles(_automaticRootPath, "*", SearchOption.TopDirectoryOnly)) + DeleteIfPresent(path); + } + + private PremixedSongCacheReadResult TryReadTier( + string key, + int expectedSampleRate, + PremixedSongCacheTier tier, + CancellationToken cancellationToken) + { + var audioPath = EntryPath(key, tier); + var metadataPath = MetadataPath(key, tier); + if (!File.Exists(audioPath) || !File.Exists(metadataPath)) + return new PremixedSongCacheReadResult(false, default, "MISS"); + try + { + cancellationToken.ThrowIfCancellationRequested(); + var metadata = ReadMetadata(metadataPath); + var length = new FileInfo(audioPath).Length; + if (metadata.SampleRate != expectedSampleRate || + metadata.Channels != SmoothAudioMixer.OutputChannels || + metadata.FrameCount <= 0 || metadata.EncodedBytes != length || + !HasFlacSignature(audioPath)) + throw new InvalidDataException("INVALID FLAC CACHE METADATA"); + if (!FixedTimeEquals(metadata.Hash, ComputeFileHash(audioPath, cancellationToken))) + throw new InvalidDataException("FLAC CACHE HASH MISMATCH"); + + Touch(audioPath, metadataPath); + var entry = new PremixedSongCacheEntry( + audioPath, + metadata.SampleRate, + metadata.Channels, + metadata.FrameCount, + length, + tier); + return new PremixedSongCacheReadResult( + true, + entry, + tier == PremixedSongCacheTier.Pinned ? "PINNED HIT" : "AUTO HIT"); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception exception) + { + DeleteIfPresent(audioPath); + DeleteIfPresent(metadataPath); + return new PremixedSongCacheReadResult( + false, + default, + exception is InvalidDataException ? exception.Message : "INVALIDATED"); + } + } + + private void PruneToBudget(string protectedAudioPath) + { + var files = EnumerateEntries(PremixedSongCacheTier.Automatic) + .OrderBy(file => file.LastWriteTimeUtc) + .ThenBy(file => file.Name, StringComparer.OrdinalIgnoreCase) + .ToList(); + var total = files.Sum(file => file.Length); + foreach (var file in files) + { + if (total <= _maximumBytes) + break; + if (string.Equals(file.FullName, protectedAudioPath, StringComparison.OrdinalIgnoreCase)) + continue; + total -= file.Length; + DeleteIfPresent(file.FullName); + DeleteIfPresent(Path.ChangeExtension(file.FullName, MetadataExtension)); + } + } + + private IEnumerable EnumerateEntries(PremixedSongCacheTier tier) + { + var root = RootFor(tier); + if (!Directory.Exists(root)) + return Enumerable.Empty(); + return Directory.EnumerateFiles(root, "*" + AudioExtension, SearchOption.TopDirectoryOnly) + .Where(path => !Path.GetFileName(path).Contains(".tmp-")) + .Select(path => new FileInfo(path)); + } + + private string ResolveAudioContentHash( + PlayableChart chart, + string soundId, + CancellationToken cancellationToken) + { + chart.AudioResources.TryGetValue(soundId ?? string.Empty, out var declaredPath); + var path = AudioAssetStore.ResolveReferencedAudioPath(chart.Metadata?.SourcePath, declaredPath); + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + return "missing"; + var file = new FileInfo(path); + var cacheKey = file.FullName; + lock (FingerprintGate) + { + if (Fingerprints.TryGetValue(cacheKey, out var cached) && + cached.Length == file.Length && cached.LastWriteTicks == file.LastWriteTimeUtc.Ticks) + return cached.Hash; + } + + var hash = ComputeFileHash(path, cancellationToken); + var value = ToHex(hash); + lock (FingerprintGate) + Fingerprints[cacheKey] = new FileFingerprint(file.Length, file.LastWriteTimeUtc.Ticks, value); + return value; + } + + private void CleanupLegacyFilesOnce() + { + if (_legacyCleaned) + return; + _legacyCleaned = true; + if (!Directory.Exists(_automaticRootPath)) + return; + foreach (var path in Directory.EnumerateFiles(_automaticRootPath, "*", SearchOption.TopDirectoryOnly)) + { + var name = Path.GetFileName(path); + if (name.EndsWith(LegacyExtension, StringComparison.OrdinalIgnoreCase) || + name.Contains(LegacyExtension + ".tmp-") || + name.Contains(".tmp-") && + (name.EndsWith(".wav", StringComparison.OrdinalIgnoreCase) || + name.EndsWith(AudioExtension, StringComparison.OrdinalIgnoreCase))) + DeleteIfPresent(path); + } + } + + private static void WritePcm24Wave( + string path, + SmoothAudioMixResult mix, + CancellationToken cancellationToken) + { + // macOS afconvert emits an invalid 42-byte FLAC for sub-block streams. Padding only + // pathological sub-170 ms fixtures with silence keeps the production path native and + // leaves all real song timing unchanged. + var minimumSampleCount = checked(8192 * mix.Channels); + var encodedSampleCount = Math.Max(mix.Samples.Length, minimumSampleCount); + var dataBytes = checked((long)encodedSampleCount * 3L); + if (dataBytes > uint.MaxValue - 36) + throw new InvalidOperationException("Premix is too large for the WAV encoder."); + using var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None, IoBufferBytes); + using var writer = new BinaryWriter(stream, Encoding.ASCII, true); + writer.Write(Encoding.ASCII.GetBytes("RIFF")); + writer.Write(checked((uint)(36L + dataBytes))); + writer.Write(Encoding.ASCII.GetBytes("WAVEfmt ")); + writer.Write(16u); + writer.Write((ushort)1); + writer.Write((ushort)mix.Channels); + writer.Write((uint)mix.SampleRate); + writer.Write(checked((uint)(mix.SampleRate * mix.Channels * 3))); + writer.Write(checked((ushort)(mix.Channels * 3))); + writer.Write((ushort)24); + writer.Write(Encoding.ASCII.GetBytes("data")); + writer.Write((uint)dataBytes); + var bytes = new byte[IoBufferBytes - IoBufferBytes % 3]; + var byteIndex = 0; + for (var index = 0; index < encodedSampleCount; index++) + { + if ((index & 0x3fff) == 0) + cancellationToken.ThrowIfCancellationRequested(); + var sample = index < mix.Samples.Length + ? Math.Max(-1f, Math.Min(1f, mix.Samples[index])) + : 0f; + var pcm = sample >= 0f + ? (int)Math.Round(sample * 8388607f) + : (int)Math.Round(sample * 8388608f); + bytes[byteIndex++] = (byte)pcm; + bytes[byteIndex++] = (byte)(pcm >> 8); + bytes[byteIndex++] = (byte)(pcm >> 16); + if (byteIndex > bytes.Length - 3) + { + stream.Write(bytes, 0, byteIndex); + byteIndex = 0; + } + } + if (byteIndex > 0) + stream.Write(bytes, 0, byteIndex); + stream.Flush(true); + } + + private static void EncodeFlac(string wavePath, string flacPath, CancellationToken cancellationToken) + { + if (!File.Exists(AfconvertPath)) + throw new PlatformNotSupportedException("macOS afconvert is required to create the FLAC premix cache."); + var startInfo = new ProcessStartInfo + { + FileName = AfconvertPath, + Arguments = $"{Quote(wavePath)} -o {Quote(flacPath)} -f flac -d flac", + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true, + }; + using var process = Process.Start(startInfo) ?? + throw new InvalidOperationException("Could not start the FLAC encoder."); + using var registration = cancellationToken.Register(() => + { + try + { + if (!process.HasExited) + process.Kill(); + } + catch + { + // Cancellation cleanup is best effort. + } + }); + var error = process.StandardError.ReadToEnd().Trim(); + process.WaitForExit(); + cancellationToken.ThrowIfCancellationRequested(); + if (process.ExitCode != 0) + throw new InvalidOperationException( + string.IsNullOrWhiteSpace(error) ? "FLAC encoding failed." : error); + } + + private static void WriteMetadata( + string path, + int sampleRate, + int channels, + long frameCount, + long encodedBytes, + byte[] hash) + { + using var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None); + using var writer = new BinaryWriter(stream, Encoding.ASCII, true); + writer.Write(Encoding.ASCII.GetBytes(MetadataMagic)); + writer.Write(SchemaVersion); + writer.Write(sampleRate); + writer.Write(channels); + writer.Write(frameCount); + writer.Write(encodedBytes); + writer.Write(hash); + stream.Flush(true); + } + + private static FlacMetadata ReadMetadata(string path) + { + using var stream = File.OpenRead(path); + using var reader = new BinaryReader(stream, Encoding.ASCII, true); + var magic = Encoding.ASCII.GetString(reader.ReadBytes(8)); + var schema = reader.ReadInt32(); + var sampleRate = reader.ReadInt32(); + var channels = reader.ReadInt32(); + var frameCount = reader.ReadInt64(); + var encodedBytes = reader.ReadInt64(); + var hash = reader.ReadBytes(HashBytes); + if (magic != MetadataMagic || schema != SchemaVersion || hash.Length != HashBytes) + throw new InvalidDataException("INVALID FLAC CACHE SIDECAR"); + return new FlacMetadata(sampleRate, channels, frameCount, encodedBytes, hash); + } + + private string RootFor(PremixedSongCacheTier tier) => + tier == PremixedSongCacheTier.Pinned ? _pinnedRootPath : _automaticRootPath; + + private string EntryPath(string key, PremixedSongCacheTier tier) => + Path.Combine(RootFor(tier), key.ToLowerInvariant() + AudioExtension); + + private string MetadataPath(string key, PremixedSongCacheTier tier) => + Path.Combine(RootFor(tier), key.ToLowerInvariant() + MetadataExtension); + + private static void ValidateKey(string key) + { + if (string.IsNullOrWhiteSpace(key) || key.Length != 64 || + key.Any(character => !Uri.IsHexDigit(character))) + throw new ArgumentException("A 64-character hexadecimal premix cache key is required.", nameof(key)); + } + + private static bool HasFlacSignature(string path) + { + using var stream = File.OpenRead(path); + return stream.ReadByte() == 'f' && stream.ReadByte() == 'L' && + stream.ReadByte() == 'a' && stream.ReadByte() == 'C'; + } + + private static void Touch(string audioPath, string metadataPath) + { + var now = DateTime.UtcNow; + File.SetLastWriteTimeUtc(audioPath, now); + File.SetLastWriteTimeUtc(metadataPath, now); + } + + private static void ReplaceFile(string source, string destination) + { + DeleteIfPresent(destination); + File.Move(source, destination); + } + + private static byte[] ComputeFileHash(string path, CancellationToken cancellationToken) + { + using var stream = new FileStream( + path, FileMode.Open, FileAccess.Read, FileShare.Read, IoBufferBytes, FileOptions.SequentialScan); + using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); + var buffer = new byte[IoBufferBytes]; + int read; + while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + hash.AppendData(buffer, 0, read); + } + return hash.GetHashAndReset(); + } + + private static string ComputeHexHash(byte[] bytes) + { + using var sha = SHA256.Create(); + return ToHex(sha.ComputeHash(bytes)); + } + + private static void Append(StringBuilder builder, string name, string value) => + builder.Append(name).Append('=').Append(value ?? string.Empty).Append('\n'); + + private static string ToHex(byte[] bytes) => + string.Concat(bytes.Select(value => value.ToString("x2", CultureInfo.InvariantCulture))); + + private static bool FixedTimeEquals(byte[] left, byte[] right) + { + if (left == null || right == null || left.Length != right.Length) + return false; + var difference = 0; + for (var index = 0; index < left.Length; index++) + difference |= left[index] ^ right[index]; + return difference == 0; + } + + private static string Quote(string value) => "\"" + value.Replace("\"", "\\\"") + "\""; + + private static void DeleteIfPresent(string path) + { + try + { + if (File.Exists(path)) + File.Delete(path); + } + catch + { + // Cache cleanup must not replace playable source audio with a fatal error. + } + } + + private readonly struct FileFingerprint + { + public FileFingerprint(long length, long lastWriteTicks, string hash) + { + Length = length; + LastWriteTicks = lastWriteTicks; + Hash = hash; + } + + public long Length { get; } + public long LastWriteTicks { get; } + public string Hash { get; } + } + + private readonly struct FlacMetadata + { + public FlacMetadata(int sampleRate, int channels, long frameCount, long encodedBytes, byte[] hash) + { + SampleRate = sampleRate; + Channels = channels; + FrameCount = frameCount; + EncodedBytes = encodedBytes; + Hash = hash; + } + + public int SampleRate { get; } + public int Channels { get; } + public long FrameCount { get; } + public long EncodedBytes { get; } + public byte[] Hash { get; } + } + } + + internal readonly struct PremixedSongCacheWriteResult + { + public PremixedSongCacheWriteResult(bool success, PremixedSongCacheEntry entry, string status) + { + Success = success; + Entry = entry; + Status = status ?? string.Empty; + } + + public bool Success { get; } + public PremixedSongCacheEntry Entry { get; } + public string Status { get; } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongCache.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongCache.cs.meta new file mode 100644 index 0000000..30e71c5 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongCache.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 83f59debbe01642dabbe6a1d6b32907a \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongRenderer.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongRenderer.cs new file mode 100644 index 0000000..e90d285 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongRenderer.cs @@ -0,0 +1,441 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ProjectQuint.Core.Charts; +using UnityEngine; + +namespace ProjectQuint.Audio +{ + internal enum PremixedSongState + { + Empty, + Preparing, + Ready, + Cancelled, + Failed, + Disposed, + } + + internal enum PremixedSongOutput + { + UnityAudioClip, + FmodStreamFile, + } + + /// + /// Renders the shared audio timeline used by keysound-off mode. Unity playback requests an + /// AudioClip, while FMOD receives a content-addressed FLAC path and streams it from disk. + /// PCM exists only while a cache miss is being mixed and encoded on the loading screen. + /// + internal sealed class PremixedSongRenderer : IDisposable + { + private readonly PremixedSongCache _cache; + + public PremixedSongRenderer(PremixedSongCache cache = null) => + _cache = cache ?? PremixedSongCache.Shared; + + public PremixedSongState State { get; private set; } = PremixedSongState.Empty; + public AudioClip Clip { get; private set; } + private PremixedSongCacheEntry _streamEntry; + public bool IsReady => State == PremixedSongState.Ready && + (Clip != null || _streamEntry.IsValid); + public string Error { get; private set; } = string.Empty; + public bool CacheHit { get; private set; } + public string CacheStatus { get; private set; } = string.Empty; + public long EstimatedPcmBytes => Clip != null + ? AudioAssetStore.CalculateDecodedPcmBytes(Clip.samples, Clip.channels) + : 0; + public long StreamFileBytes => _streamEntry.EncodedBytes; + internal bool HasStreamFile => _streamEntry.IsValid; + + internal AudioClip TakeClip() + { + ThrowIfDisposed(); + var clip = Clip; + Clip = null; + return clip; + } + + internal PremixedSongCacheEntry TakeStreamEntry() + { + ThrowIfDisposed(); + var entry = _streamEntry; + _streamEntry = default; + return entry; + } + + public IEnumerator Prepare( + PlayableChart chart, + AudioAssetStore assets, + Action progress, + Func cancellationRequested = null, + PremixedSongOutput output = PremixedSongOutput.UnityAudioClip, + int outputSampleRateOverride = 0, + PremixedSongCacheTier cacheTier = PremixedSongCacheTier.Automatic) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + if (assets == null) + throw new ArgumentNullException(nameof(assets)); + ThrowIfDisposed(); + ClearOutput(); + Error = string.Empty; + CacheHit = false; + CacheStatus = string.Empty; + State = PremixedSongState.Preparing; + + var timeline = AudioPlaybackTimeline.Build(chart, reactiveKeySoundsEnabled: false); + var eventsBySoundId = timeline + .GroupBy(item => item.SoundId, StringComparer.Ordinal) + .ToDictionary( + group => group.Key, + group => (IReadOnlyList)group.ToList(), + StringComparer.Ordinal); + var orderedIds = eventsBySoundId.Keys.OrderBy( + item => item, + StringComparer.Ordinal).ToList(); + var outputSampleRate = outputSampleRateOverride > 0 + ? outputSampleRateOverride + : AudioSettings.outputSampleRate; + if (outputSampleRate <= 0) + { + Fail("The selected audio backend reported an invalid output sample rate."); + yield break; + } + + using var cancellation = new CancellationTokenSource(); + var cacheKey = string.Empty; + progress?.Invoke(0, orderedIds.Count + 1, "Fingerprinting smooth playback content"); + var keyTask = Task.Run( + () => _cache.BuildKey(chart, outputSampleRate, cancellation.Token), + cancellation.Token); + while (!keyTask.IsCompleted) + { + if (IsCancelled(cancellationRequested)) + cancellation.Cancel(); + yield return null; + } + if (keyTask.IsCanceled) + { + State = PremixedSongState.Cancelled; + yield break; + } + if (keyTask.IsFaulted) + { + CacheStatus = "KEY FAILED"; + Debug.LogWarning( + $"[ProjectQuint Audio] Premix cache key failed: " + + $"{keyTask.Exception?.GetBaseException().Message}"); + } + else + cacheKey = keyTask.Result; + + if (output == PremixedSongOutput.FmodStreamFile && !string.IsNullOrEmpty(cacheKey)) + { + progress?.Invoke(0, orderedIds.Count + 1, "Checking smooth playback cache"); + var readTask = Task.Run( + () => _cache.TryRead(cacheKey, outputSampleRate, cancellation.Token), + cancellation.Token); + while (!readTask.IsCompleted) + { + if (IsCancelled(cancellationRequested)) + cancellation.Cancel(); + yield return null; + } + if (readTask.IsCanceled) + { + State = PremixedSongState.Cancelled; + yield break; + } + if (!readTask.IsFaulted) + { + var cached = readTask.Result; + CacheStatus = cached.Status; + if (cached.Hit) + { + _streamEntry = cached.Entry; + CacheHit = true; + State = PremixedSongState.Ready; + progress?.Invoke(orderedIds.Count + 1, orderedIds.Count + 1, "Smooth playback cache hit"); + yield return null; + GC.Collect(); + yield return null; + yield break; + } + if (!string.Equals(cached.Status, "MISS", StringComparison.OrdinalIgnoreCase)) + Debug.LogWarning($"[ProjectQuint Audio] Premix cache {cached.Status}; rendering source audio again."); + } + else + { + CacheStatus = "READ FAILED"; + Debug.LogWarning( + $"[ProjectQuint Audio] Premix cache read failed: " + + $"{readTask.Exception?.GetBaseException().Message}"); + } + } + + SmoothAudioMixAccumulator accumulator; + try + { + accumulator = new SmoothAudioMixAccumulator(outputSampleRate, chart.Duration); + } + catch (Exception exception) + { + Fail($"Could not allocate the smooth playback mix: {exception.Message}"); + yield break; + } + + var mixedResourceCount = 0; + for (var index = 0; index < orderedIds.Count; index++) + { + if (IsCancelled(cancellationRequested)) + { + State = PremixedSongState.Cancelled; + yield break; + } + + var soundId = orderedIds[index]; + progress?.Invoke(index, orderedIds.Count + 1, $"Decoding and mixing {soundId}"); + var loadResult = new AudioResourceLoadResult( + AudioResourceLoadStatus.DecodeFailed, + detail: $"Keysound {soundId} did not return a load result."); + yield return assets.LoadTransientClip( + chart, + soundId, + result => loadResult = result, + cancellationRequested); + if (loadResult.Status == AudioResourceLoadStatus.Cancelled || + IsCancelled(cancellationRequested)) + { + AudioAssetStore.ReleaseTransientClip(loadResult.Clip); + State = PremixedSongState.Cancelled; + yield break; + } + if (loadResult.Status != AudioResourceLoadStatus.Loaded || + loadResult.Clip == null) + { + assets.RecordTransientOutcome( + soundId, + loadResult.Status, + loadResult.Detail); + continue; + } + + if (!AudioAssetStore.TryReadClipPcm( + loadResult.Clip, + soundId, + assets.DecodedPcmBudgetBytes, + out var pcm, + out var pcmFailureStatus, + out var pcmError)) + { + AudioAssetStore.ReleaseTransientClip(loadResult.Clip); + if (pcmFailureStatus == AudioResourceLoadStatus.TooLarge) + { + Fail(pcmError); + yield break; + } + assets.RecordTransientOutcome(soundId, pcmFailureStatus, pcmError); + continue; + } + AudioAssetStore.ReleaseTransientClip(loadResult.Clip); + + var soundEvents = eventsBySoundId[soundId]; + var mixTask = Task.Run( + () => accumulator.Mix(soundEvents, pcm, cancellation.Token), + cancellation.Token); + while (!mixTask.IsCompleted) + { + if (IsCancelled(cancellationRequested)) + cancellation.Cancel(); + yield return null; + } + pcm = null; + if (mixTask.IsCanceled) + { + State = PremixedSongState.Cancelled; + yield break; + } + if (mixTask.IsFaulted) + { + Fail(mixTask.Exception?.GetBaseException().Message ?? "Unknown incremental mix failure."); + yield break; + } + mixedResourceCount++; + yield return null; + } + + if (orderedIds.Count > 0 && + mixedResourceCount == 0 && + assets.MissingClipCount + assets.DecodeFailedClipCount > 0) + { + Fail( + $"No playable keysounds could be mixed " + + $"({assets.MissingClipCount} missing, " + + $"{assets.DecodeFailedClipCount} decode failed)."); + yield break; + } + + progress?.Invoke(orderedIds.Count, orderedIds.Count + 1, "Finalizing smooth playback track"); + var finalizeTask = Task.Run( + () => accumulator.Complete(cancellation.Token), + cancellation.Token); + while (!finalizeTask.IsCompleted) + { + if (IsCancelled(cancellationRequested)) + cancellation.Cancel(); + yield return null; + } + if (finalizeTask.IsCanceled) + { + State = PremixedSongState.Cancelled; + yield break; + } + if (finalizeTask.IsFaulted) + { + Fail(finalizeTask.Exception?.GetBaseException().Message ?? "Unknown offline mix failure."); + yield break; + } + + var mixResult = finalizeTask.Result; + if (output == PremixedSongOutput.UnityAudioClip && !TryCreateUnityOutput(chart, mixResult)) + yield break; + + if (!string.IsNullOrEmpty(cacheKey)) + { + progress?.Invoke(orderedIds.Count, orderedIds.Count + 1, "Encoding smooth playback FLAC cache"); + var writeTask = Task.Run( + () => _cache.Write(cacheKey, mixResult, cacheTier, cancellation.Token), + cancellation.Token); + while (!writeTask.IsCompleted) + { + if (IsCancelled(cancellationRequested)) + cancellation.Cancel(); + yield return null; + } + if (writeTask.IsCanceled) + { + ClearOutput(); + State = PremixedSongState.Cancelled; + yield break; + } + var write = writeTask.IsFaulted + ? new PremixedSongCacheWriteResult( + false, + default, + $"STORE FAILED // {writeTask.Exception?.GetBaseException().Message}") + : writeTask.Result; + CacheStatus = write.Status; + if (output == PremixedSongOutput.FmodStreamFile) + { + if (!write.Success || !write.Entry.IsValid) + { + Fail(CacheStatus); + yield break; + } + _streamEntry = write.Entry; + } + if (CacheStatus.StartsWith("STORE FAILED", StringComparison.OrdinalIgnoreCase)) + Debug.LogWarning($"[ProjectQuint Audio] Premix cache {CacheStatus}"); + } + else if (output == PremixedSongOutput.FmodStreamFile) + { + Fail("Could not build a content key for the FMOD FLAC stream."); + yield break; + } + + State = PremixedSongState.Ready; + progress?.Invoke(orderedIds.Count + 1, orderedIds.Count + 1, "Smooth playback ready"); + + // Drop all loading-only managed PCM references before gameplay begins. This + // collection is intentionally paid on the loading screen instead of a play frame. + mixResult = default; + finalizeTask = null; + accumulator = null; + eventsBySoundId.Clear(); + eventsBySoundId = null; + timeline = null; + yield return null; + GC.Collect(); + yield return null; + } + + public void Clear() + { + ThrowIfDisposed(); + ClearOutput(); + Error = string.Empty; + CacheHit = false; + CacheStatus = string.Empty; + State = PremixedSongState.Empty; + } + + public void Dispose() + { + if (State == PremixedSongState.Disposed) + return; + ClearOutput(); + State = PremixedSongState.Disposed; + } + + private void Fail(string error) + { + ClearOutput(); + Error = string.IsNullOrWhiteSpace(error) ? "Offline audio mix failed." : error; + State = PremixedSongState.Failed; + } + + private bool TryCreateUnityOutput(PlayableChart chart, SmoothAudioMixResult mix) + { + if (mix.Samples == null || mix.Samples.Length == 0 || mix.FrameCount <= 0) + { + Fail("Offline audio mix produced no PCM samples."); + return false; + } + var mixedClip = AudioClip.Create( + $"ProjectQuint Smooth Mix - {chart.Metadata.Title}", + mix.FrameCount, + mix.Channels, + mix.SampleRate, + false); + if (mixedClip == null || !mixedClip.SetData(mix.Samples, 0)) + { + DestroyUnityObject(mixedClip); + Fail("Unity could not create the smooth playback AudioClip."); + return false; + } + + Clip = mixedClip; + return true; + } + + private void ClearOutput() + { + DestroyUnityObject(Clip); + Clip = null; + _streamEntry = default; + } + + private void ThrowIfDisposed() + { + if (State == PremixedSongState.Disposed) + throw new ObjectDisposedException(nameof(PremixedSongRenderer)); + } + + private static bool IsCancelled(Func cancellationRequested) => + cancellationRequested?.Invoke() == true; + + private static void DestroyUnityObject(UnityEngine.Object target) + { + if (target == null) + return; + if (Application.isPlaying) + UnityEngine.Object.Destroy(target); + else + UnityEngine.Object.DestroyImmediate(target); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongRenderer.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongRenderer.cs.meta new file mode 100644 index 0000000..0c99e48 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/PremixedSongRenderer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 26a6a22f76eb24294a947cb7ba7cde5d \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/ProjectQuint.Media.Audio.asmdef b/Assets/ProjectQuint/Runtime/Unity/Audio/ProjectQuint.Media.Audio.asmdef new file mode 100644 index 0000000..b7b773f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/ProjectQuint.Media.Audio.asmdef @@ -0,0 +1,18 @@ +{ + "name": "ProjectQuint.Media.Audio", + "rootNamespace": "ProjectQuint.Audio", + "references": [ + "ProjectQuint.Core", + "ProjectQuint.Infrastructure", + "FMODUnity" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/ProjectQuint.Media.Audio.asmdef.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/ProjectQuint.Media.Audio.asmdef.meta new file mode 100644 index 0000000..d9cccee --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/ProjectQuint.Media.Audio.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 360789961504e497f8124c485249ac60 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/SmoothAudioMixer.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/SmoothAudioMixer.cs new file mode 100644 index 0000000..7db0889 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/SmoothAudioMixer.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace ProjectQuint.Audio +{ + internal sealed class PcmAudioData + { + public PcmAudioData(float[] samples, int channels, int sampleRate) + { + Samples = samples ?? throw new ArgumentNullException(nameof(samples)); + if (channels <= 0) + throw new ArgumentOutOfRangeException(nameof(channels)); + if (sampleRate <= 0) + throw new ArgumentOutOfRangeException(nameof(sampleRate)); + if (samples.Length % channels != 0) + throw new ArgumentException("The PCM sample count must be divisible by its channel count.", nameof(samples)); + + Channels = channels; + SampleRate = sampleRate; + } + + public float[] Samples { get; } + public int Channels { get; } + public int SampleRate { get; } + public int FrameCount => Samples.Length / Channels; + } + + internal readonly struct SmoothAudioMixResult + { + public SmoothAudioMixResult(float[] samples, int sampleRate) + { + Samples = samples ?? throw new ArgumentNullException(nameof(samples)); + SampleRate = sampleRate; + } + + public float[] Samples { get; } + public int SampleRate { get; } + public int Channels => SmoothAudioMixer.OutputChannels; + public int FrameCount => Samples.Length / Channels; + } + + internal static class SmoothAudioMixer + { + internal const int OutputChannels = 2; + internal const double MaximumDurationSeconds = 20.0 * 60.0; + private const float LimiterKnee = 0.90f; + + public static SmoothAudioMixResult Mix( + IReadOnlyList events, + IReadOnlyDictionary clips, + int outputSampleRate, + double minimumDurationSeconds, + CancellationToken cancellationToken = default) + { + if (events == null) + throw new ArgumentNullException(nameof(events)); + if (clips == null) + throw new ArgumentNullException(nameof(clips)); + if (outputSampleRate <= 0) + throw new ArgumentOutOfRangeException(nameof(outputSampleRate)); + if (minimumDurationSeconds < 0 || minimumDurationSeconds > MaximumDurationSeconds) + throw new ArgumentOutOfRangeException(nameof(minimumDurationSeconds)); + + var referencedIds = new List(); + var eventsBySoundId = new Dictionary>( + StringComparer.Ordinal); + for (var index = 0; index < events.Count; index++) + { + cancellationToken.ThrowIfCancellationRequested(); + var item = events[index]; + if (string.IsNullOrWhiteSpace(item.SoundId) || !clips.TryGetValue(item.SoundId, out var clip)) + continue; + + if (!eventsBySoundId.TryGetValue(item.SoundId, out var soundEvents)) + { + soundEvents = new List(); + eventsBySoundId[item.SoundId] = soundEvents; + referencedIds.Add(item.SoundId); + } + soundEvents.Add(item); + } + + var accumulator = new SmoothAudioMixAccumulator(outputSampleRate, minimumDurationSeconds); + for (var soundIndex = 0; soundIndex < referencedIds.Count; soundIndex++) + { + cancellationToken.ThrowIfCancellationRequested(); + var soundId = referencedIds[soundIndex]; + accumulator.Mix(eventsBySoundId[soundId], clips[soundId], cancellationToken); + } + return accumulator.Complete(cancellationToken); + } + + internal static float SoftLimit(float sample) + { + if (float.IsNaN(sample) || float.IsInfinity(sample)) + return 0f; + + var magnitude = Math.Abs(sample); + if (magnitude <= LimiterKnee) + return sample; + + var headroom = 1f - LimiterKnee; + var limitedMagnitude = LimiterKnee + + headroom * (float)Math.Tanh((magnitude - LimiterKnee) / headroom); + return Math.Sign(sample) * limitedMagnitude; + } + + internal static float[] ResampleToStereo( + PcmAudioData source, + int outputSampleRate, + CancellationToken cancellationToken) + { + if (source.FrameCount <= 0) + return Array.Empty(); + + var outputFrameCount64 = checked((long)Math.Ceiling( + source.FrameCount * (double)outputSampleRate / source.SampleRate)); + if (outputFrameCount64 > int.MaxValue / OutputChannels) + throw new InvalidOperationException("A keysound is too large to resample."); + + var outputFrameCount = (int)outputFrameCount64; + var output = new float[checked(outputFrameCount * OutputChannels)]; + var inputFramesPerOutputFrame = source.SampleRate / (double)outputSampleRate; + + for (var outputFrame = 0; outputFrame < outputFrameCount; outputFrame++) + { + if ((outputFrame & 0x3fff) == 0) + cancellationToken.ThrowIfCancellationRequested(); + + var sourcePosition = outputFrame * inputFramesPerOutputFrame; + var sourceFrame0 = Math.Min((int)sourcePosition, source.FrameCount - 1); + var sourceFrame1 = Math.Min(sourceFrame0 + 1, source.FrameCount - 1); + var interpolation = (float)(sourcePosition - sourceFrame0); + var outputSample = outputFrame * OutputChannels; + + output[outputSample] = InterpolateChannel(source, sourceFrame0, sourceFrame1, 0, interpolation); + output[outputSample + 1] = InterpolateChannel( + source, + sourceFrame0, + sourceFrame1, + source.Channels > 1 ? 1 : 0, + interpolation); + } + + return output; + } + + private static float InterpolateChannel( + PcmAudioData source, + int frame0, + int frame1, + int channel, + float interpolation) + { + var sample0 = source.Samples[frame0 * source.Channels + channel]; + var sample1 = source.Samples[frame1 * source.Channels + channel]; + return sample0 + (sample1 - sample0) * interpolation; + } + } + + /// + /// Keeps only the final song buffer and one transient resampled source. This allows + /// very large BMS keysound sets to be decoded and mixed sequentially instead of + /// retaining every decoded clip at the same time. + /// + internal sealed class SmoothAudioMixAccumulator + { + private readonly int _sampleRate; + private float[] _mixed; + private bool _completed; + + public SmoothAudioMixAccumulator(int outputSampleRate, double minimumDurationSeconds) + { + if (outputSampleRate <= 0) + throw new ArgumentOutOfRangeException(nameof(outputSampleRate)); + if (minimumDurationSeconds < 0 || minimumDurationSeconds > SmoothAudioMixer.MaximumDurationSeconds) + throw new ArgumentOutOfRangeException(nameof(minimumDurationSeconds)); + _sampleRate = outputSampleRate; + _mixed = new float[CalculateSampleCount(minimumDurationSeconds)]; + } + + public void Mix( + IReadOnlyList events, + PcmAudioData clip, + CancellationToken cancellationToken = default) + { + if (_completed) + throw new InvalidOperationException("The smooth mix is already complete."); + if (events == null) + throw new ArgumentNullException(nameof(events)); + if (clip == null) + return; + + var source = SmoothAudioMixer.ResampleToStereo(clip, _sampleRate, cancellationToken); + var sourceFrameCount = source.Length / SmoothAudioMixer.OutputChannels; + for (var eventIndex = 0; eventIndex < events.Count; eventIndex++) + { + if ((eventIndex & 31) == 0) + cancellationToken.ThrowIfCancellationRequested(); + + var destinationFrame = (long)Math.Round(events[eventIndex].Time * _sampleRate); + var sourceFrame = 0L; + if (destinationFrame < 0) + { + sourceFrame = -destinationFrame; + destinationFrame = 0; + } + var copyFrameCount = sourceFrameCount - sourceFrame; + if (copyFrameCount <= 0) + continue; + EnsureFrameCapacity(destinationFrame + copyFrameCount); + + var sourceSample = checked((int)(sourceFrame * SmoothAudioMixer.OutputChannels)); + var destinationSample = checked((int)(destinationFrame * SmoothAudioMixer.OutputChannels)); + var copySampleCount = checked((int)(copyFrameCount * SmoothAudioMixer.OutputChannels)); + for (var sampleIndex = 0; sampleIndex < copySampleCount; sampleIndex++) + _mixed[destinationSample + sampleIndex] += source[sourceSample + sampleIndex]; + } + } + + public SmoothAudioMixResult Complete(CancellationToken cancellationToken = default) + { + if (_completed) + throw new InvalidOperationException("The smooth mix is already complete."); + _completed = true; + for (var index = 0; index < _mixed.Length; index++) + { + if ((index & 0x3ffff) == 0) + cancellationToken.ThrowIfCancellationRequested(); + _mixed[index] = SmoothAudioMixer.SoftLimit(_mixed[index]); + } + return new SmoothAudioMixResult(_mixed, _sampleRate); + } + + private void EnsureFrameCapacity(long requiredFrameCount) + { + if (requiredFrameCount <= _mixed.LongLength / SmoothAudioMixer.OutputChannels) + return; + var maximumFrames = checked((long)Math.Ceiling( + SmoothAudioMixer.MaximumDurationSeconds * _sampleRate)); + if (requiredFrameCount > maximumFrames || + requiredFrameCount > int.MaxValue / SmoothAudioMixer.OutputChannels) + { + throw new InvalidOperationException( + $"Smooth playback mix exceeds the {SmoothAudioMixer.MaximumDurationSeconds / 60:0}-minute safety limit."); + } + Array.Resize( + ref _mixed, + checked((int)requiredFrameCount * SmoothAudioMixer.OutputChannels)); + } + + private int CalculateSampleCount(double durationSeconds) + { + var frameCount = Math.Max(1L, checked((long)Math.Ceiling(durationSeconds * _sampleRate))); + if (frameCount > int.MaxValue / SmoothAudioMixer.OutputChannels) + throw new InvalidOperationException("Smooth playback mix is too large for a Unity AudioClip."); + return checked((int)frameCount * SmoothAudioMixer.OutputChannels); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/SmoothAudioMixer.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/SmoothAudioMixer.cs.meta new file mode 100644 index 0000000..5c6a1da --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/SmoothAudioMixer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c4271fd5fdd16467ebe22fe5ded1e031 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/SongAudioPlaybackEngine.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/SongAudioPlaybackEngine.cs new file mode 100644 index 0000000..1c6d2bf --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/SongAudioPlaybackEngine.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Charts; +using Unity.Profiling; +using UnityEngine; + +namespace ProjectQuint.Audio +{ + /// + /// Owns the Unity AudioSources and DSP reservations for one prepared song. It never + /// decodes or mixes audio and therefore performs no PCM work during gameplay. + /// + internal sealed class SongAudioPlaybackEngine : IDisposable + { + internal const double SchedulingHorizon = 1.0; + internal const double LateEventDropThresholdSeconds = 0.100; + internal const int ScheduledSourceHeadroom = 8; + private const int KeySoundPriority = 32; + private const int ScheduledSoundPriority = 96; + private static readonly ProfilerMarker ScheduleAudioMarker = + new ProfilerMarker("ProjectQuint.Audio.Schedule"); + + private readonly GameObject _owner; + private readonly IAudioClock _clock; + private readonly List _scheduledSlots = new List(); + private readonly DspReservationPool _scheduledReservations = new DspReservationPool(); + private readonly List _scheduledEvents = new List(); + private readonly AudioSource[] _laneSources = new AudioSource[8]; + private readonly AudioSource _premixSource; + private PlayableChart _chart; + private AudioAssetStore _assets; + private int _nextScheduledEvent; + private bool _usingPremix; + private bool _disposed; + + public SongAudioPlaybackEngine(GameObject owner, IAudioClock clock) + { + _owner = owner ?? throw new ArgumentNullException(nameof(owner)); + _clock = clock ?? throw new ArgumentNullException(nameof(clock)); + for (var lane = 0; lane < _laneSources.Length; lane++) + _laneSources[lane] = CreateSource(KeySoundPriority); + _premixSource = CreateSource(ScheduledSoundPriority); + } + + public double StartDspTime { get; private set; } + public int ScheduledSourceCount => _usingPremix ? 1 : _scheduledSlots.Count; + public int PendingScheduledEventCount => Math.Max(0, _scheduledEvents.Count - _nextScheduledEvent); + public int DroppedLateEventCount { get; private set; } + + public void PrepareReactivePlayback(PlayableChart chart, AudioAssetStore assets, double leadSeconds = 0.35) + { + ThrowIfDisposed(); + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + if (assets == null) + throw new ArgumentNullException(nameof(assets)); + + Stop(); + _chart = chart; + _assets = assets; + _scheduledEvents.AddRange(AudioPlaybackTimeline.Build(chart, reactiveKeySoundsEnabled: true)); + PrewarmScheduledSources(assets, leadSeconds); + } + + public void BeginPlayback( + PlayableChart chart, + AudioAssetStore assets, + AudioClip premixedClip, + bool reactiveKeySoundsEnabled, + double leadSeconds = 0.35) + { + ThrowIfDisposed(); + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + if (assets == null) + throw new ArgumentNullException(nameof(assets)); + + var alreadyPreparedReactive = reactiveKeySoundsEnabled && + ReferenceEquals(_chart, chart) && + ReferenceEquals(_assets, assets) && + _scheduledEvents.Count > 0; + if (!alreadyPreparedReactive) + Stop(); + else + { + foreach (var source in _laneSources) source.Stop(); + _premixSource.Stop(); + foreach (var slot in _scheduledSlots) slot.Source.Stop(); + _scheduledReservations.Reset(); + _nextScheduledEvent = 0; + StartDspTime = 0; + _usingPremix = false; + } + + DroppedLateEventCount = 0; + _chart = chart; + _assets = assets; + StartDspTime = _clock.CurrentTime + leadSeconds; + + if (!reactiveKeySoundsEnabled) + { + if (premixedClip == null) + throw new InvalidOperationException("Smooth playback was requested before its single-track mix was prepared."); + _scheduledEvents.Clear(); + _usingPremix = true; + _premixSource.clip = premixedClip; + _premixSource.PlayScheduled(StartDspTime); + return; + } + + if (!alreadyPreparedReactive) + { + _scheduledEvents.AddRange(AudioPlaybackTimeline.Build(chart, reactiveKeySoundsEnabled: true)); + PrewarmScheduledSources(assets, leadSeconds); + } + ScheduleAudio(_clock.CurrentTime); + } + + public void Update() + { + if (!_usingPremix && _chart != null && _assets != null && StartDspTime > 0) + ScheduleAudio(_clock.CurrentTime); + } + + public void PlayKeySound(string soundId, int lane) + { + if (lane < 0 || lane >= _laneSources.Length || string.IsNullOrWhiteSpace(soundId) || _assets == null) + return; + if (_assets.TryGetClip(soundId, out var clip)) + _laneSources[lane].PlayOneShot(clip); + } + + public void Stop() + { + if (_disposed) + return; + foreach (var source in _laneSources) source.Stop(); + _premixSource.Stop(); + _premixSource.clip = null; + foreach (var slot in _scheduledSlots) + { + slot.Source.Stop(); + slot.Source.clip = null; + } + _scheduledReservations.Reset(); + _scheduledEvents.Clear(); + _nextScheduledEvent = 0; + StartDspTime = 0; + _usingPremix = false; + _chart = null; + _assets = null; + } + + public void Dispose() + { + if (_disposed) + return; + Stop(); + _disposed = true; + foreach (var source in _laneSources) + DestroyUnityObject(source); + DestroyUnityObject(_premixSource); + foreach (var slot in _scheduledSlots) + DestroyUnityObject(slot.Source); + _scheduledSlots.Clear(); + } + + internal static bool ShouldDropLateScheduledEvent(double scheduledDspTime, double currentDspTime) => + scheduledDspTime < currentDspTime - LateEventDropThresholdSeconds; + + private void ScheduleAudio(double currentDspTime) + { + using var profilerScope = ScheduleAudioMarker.Auto(); + var horizon = currentDspTime + SchedulingHorizon; + while (_nextScheduledEvent < _scheduledEvents.Count) + { + var item = _scheduledEvents[_nextScheduledEvent]; + var scheduledTime = StartDspTime + item.Time; + if (scheduledTime > horizon) + break; + + _nextScheduledEvent++; + if (!_assets.TryGetClip(item.SoundId, out var clip)) + continue; + if (ShouldDropLateScheduledEvent(scheduledTime, currentDspTime)) + { + DroppedLateEventCount++; + continue; + } + + scheduledTime = Math.Max(scheduledTime, currentDspTime + 0.005); + var slotIndex = _scheduledReservations.Reserve( + currentDspTime, + scheduledTime, + Math.Max(clip.length, 0.01f)); + if (slotIndex == _scheduledSlots.Count) + _scheduledSlots.Add(new AudioSlot(CreateSource(ScheduledSoundPriority))); + + var slot = _scheduledSlots[slotIndex]; + slot.Source.clip = clip; + slot.Source.PlayScheduled(scheduledTime); + } + } + + private void PrewarmScheduledSources(AudioAssetStore assets, double leadSeconds) + { + var requiredSlotCount = ScheduledAudioSourcePlanner.CalculateRequiredSlotCount( + _scheduledEvents, + assets.GetClipDurations(), + SchedulingHorizon, + Math.Max(leadSeconds, 0)); + if (requiredSlotCount > 0) + requiredSlotCount += ScheduledSourceHeadroom; + while (_scheduledSlots.Count < requiredSlotCount) + _scheduledSlots.Add(new AudioSlot(CreateSource(ScheduledSoundPriority))); + } + + private AudioSource CreateSource(int priority) + { + var source = _owner.AddComponent(); + source.playOnAwake = false; + source.spatialBlend = 0; + source.priority = priority; + source.bypassEffects = true; + source.bypassListenerEffects = true; + source.bypassReverbZones = true; + return source; + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(SongAudioPlaybackEngine)); + } + + private static void DestroyUnityObject(UnityEngine.Object target) + { + if (target == null) + return; + if (Application.isPlaying) + UnityEngine.Object.Destroy(target); + else + UnityEngine.Object.DestroyImmediate(target); + } + + private sealed class AudioSlot + { + public AudioSlot(AudioSource source) + { + Source = source; + } + + public AudioSource Source { get; } + } + } + + internal readonly struct ScheduledAudioEvent + { + public ScheduledAudioEvent(double time, string soundId) + { + Time = time; + SoundId = soundId; + } + + public double Time { get; } + public string SoundId { get; } + } + + internal static class AudioPlaybackTimeline + { + public static List Build(PlayableChart chart, bool reactiveKeySoundsEnabled) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + + var events = new List( + chart.BackgroundAudioEvents.Count + (reactiveKeySoundsEnabled ? 0 : chart.Notes.Count)); + foreach (var backgroundSound in chart.BackgroundAudioEvents) + { + if (!string.IsNullOrWhiteSpace(backgroundSound.SoundId)) + events.Add(new ScheduledAudioEvent(backgroundSound.Time, backgroundSound.SoundId)); + } + + if (!reactiveKeySoundsEnabled) + { + foreach (var note in chart.Notes) + { + if (!string.IsNullOrWhiteSpace(note.SoundId)) + events.Add(new ScheduledAudioEvent(note.StartTime, note.SoundId)); + } + } + + events.Sort((left, right) => left.Time.CompareTo(right.Time)); + return events; + } + } + + internal sealed class DspReservationPool + { + internal const double RecycleGuardSeconds = 0.02; + private readonly List _reservedUntil = new List(); + + public int Reserve(double currentDspTime, double scheduledDspTime, double durationSeconds) + { + var slotIndex = -1; + for (var index = 0; index < _reservedUntil.Count; index++) + { + if (_reservedUntil[index] > currentDspTime) + continue; + slotIndex = index; + break; + } + if (slotIndex < 0) + { + slotIndex = _reservedUntil.Count; + _reservedUntil.Add(0); + } + + _reservedUntil[slotIndex] = scheduledDspTime + durationSeconds + RecycleGuardSeconds; + return slotIndex; + } + + public void Reset() => _reservedUntil.Clear(); + } + + internal static class ScheduledAudioSourcePlanner + { + public static int CalculateRequiredSlotCount( + IReadOnlyList events, + IReadOnlyDictionary clipDurations, + double schedulingHorizon, + double playbackLeadSeconds) + { + if (events == null) + throw new ArgumentNullException(nameof(events)); + if (clipDurations == null) + throw new ArgumentNullException(nameof(clipDurations)); + + var reservedUntil = new List(); + for (var eventIndex = 0; eventIndex < events.Count; eventIndex++) + { + var item = events[eventIndex]; + if (!clipDurations.TryGetValue(item.SoundId, out var durationSeconds)) + continue; + + var schedulerSongTime = Math.Max(-playbackLeadSeconds, item.Time - schedulingHorizon); + var slotIndex = -1; + for (var index = 0; index < reservedUntil.Count; index++) + { + if (reservedUntil[index] > schedulerSongTime) + continue; + slotIndex = index; + break; + } + if (slotIndex < 0) + { + slotIndex = reservedUntil.Count; + reservedUntil.Add(0); + } + + reservedUntil[slotIndex] = item.Time + Math.Max(durationSeconds, 0.01) + + DspReservationPool.RecycleGuardSeconds; + } + + return reservedUntil.Count; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/SongAudioPlaybackEngine.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/SongAudioPlaybackEngine.cs.meta new file mode 100644 index 0000000..0147c9c --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/SongAudioPlaybackEngine.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e035d78e984f745748ac545806648d50 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/WaveAudioDecoder.cs b/Assets/ProjectQuint/Runtime/Unity/Audio/WaveAudioDecoder.cs new file mode 100644 index 0000000..00a3d9f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/WaveAudioDecoder.cs @@ -0,0 +1,447 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace ProjectQuint.Audio +{ + internal enum WaveContainerStatus + { + AudioData, + SilentPlaceholder, + Invalid, + } + + internal readonly struct WaveContainerInspection + { + public WaveContainerInspection(WaveContainerStatus status, string detail) + { + Status = status; + Detail = detail ?? string.Empty; + } + + public WaveContainerStatus Status { get; } + public string Detail { get; } + } + + public sealed class DecodedWaveAudio + { + public DecodedWaveAudio(float[] samples, int channels, int sampleRate) + { + Samples = samples; + Channels = channels; + SampleRate = sampleRate; + } + + public float[] Samples { get; } + public int Channels { get; } + public int SampleRate { get; } + public int FrameCount => Samples.Length / Channels; + } + + public static class WaveAudioDecoder + { + private const ushort MicrosoftAdpcmFormat = 0x0002; + private const uint RiffId = 0x46464952; + private const uint WaveId = 0x45564157; + private const uint FormatId = 0x20746D66; + private const uint DataId = 0x61746164; + + private static readonly int[] AdaptationTable = + { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230, + }; + + public static bool IsMicrosoftAdpcm(string filePath) + { + try + { + using var stream = File.OpenRead(filePath); + using var reader = new BinaryReader(stream); + if (stream.Length < 12 || reader.ReadUInt32() != RiffId) + return false; + stream.Position = 8; + if (reader.ReadUInt32() != WaveId) + return false; + + while (stream.Position + 8 <= stream.Length) + { + var chunkId = reader.ReadUInt32(); + var chunkSize = reader.ReadUInt32(); + var chunkStart = stream.Position; + if (chunkId == FormatId && chunkSize >= 2) + return reader.ReadUInt16() == MicrosoftAdpcmFormat; + + var nextChunk = chunkStart + chunkSize + (chunkSize & 1); + if (nextChunk <= chunkStart || nextChunk > stream.Length) + return false; + stream.Position = nextChunk; + } + } + catch (Exception) + { + return false; + } + + return false; + } + + /// + /// Validates the RIFF/WAVE container without decoding its payload. A zero-length data + /// chunk is a legitimate BMS silent placeholder only when the surrounding RIFF and fmt + /// chunks are structurally valid. Truncated or malformed WAV files remain decode errors. + /// + internal static WaveContainerInspection InspectContainer(string filePath) + { + try + { + using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var reader = new BinaryReader(stream); + if (stream.Length < 12) + return InvalidContainer("The WAV file is truncated."); + if (reader.ReadUInt32() != RiffId) + return InvalidContainer("The file is not a RIFF stream."); + + var riffSize = reader.ReadUInt32(); + if (riffSize < 4) + return InvalidContainer("The RIFF size is invalid."); + var riffEnd = checked(8L + riffSize); + if (riffEnd > stream.Length) + return InvalidContainer("The RIFF payload is truncated."); + if (reader.ReadUInt32() != WaveId) + return InvalidContainer("The RIFF stream is not WAVE audio."); + + var formatFound = false; + var dataFound = false; + var hasAudioData = false; + while (stream.Position < riffEnd) + { + if (riffEnd - stream.Position < 8) + return InvalidContainer("The WAV chunk header is truncated."); + + var chunkId = reader.ReadUInt32(); + var chunkSize = reader.ReadUInt32(); + var chunkStart = stream.Position; + var chunkEnd = checked(chunkStart + chunkSize); + if (chunkEnd > stream.Length) + return InvalidContainer("A WAV chunk extends beyond the physical file."); + + if (chunkId == FormatId) + { + if (chunkSize < 16) + return InvalidContainer("The WAV fmt chunk is truncated."); + + var formatTag = reader.ReadUInt16(); + var channels = reader.ReadUInt16(); + var sampleRate = reader.ReadUInt32(); + var byteRate = reader.ReadUInt32(); + var blockAlign = reader.ReadUInt16(); + var bitsPerSample = reader.ReadUInt16(); + if (formatTag == 0 || channels == 0 || sampleRate == 0 || + byteRate == 0 || blockAlign == 0 || bitsPerSample == 0) + { + return InvalidContainer("The WAV fmt chunk contains invalid audio parameters."); + } + formatFound = true; + } + else if (chunkId == DataId) + { + dataFound = true; + hasAudioData |= chunkSize > 0; + } + + var paddedEnd = checked(chunkEnd + (chunkSize & 1)); + if (paddedEnd > stream.Length) + return InvalidContainer("The WAV chunk padding is truncated."); + stream.Position = paddedEnd; + } + + if (!formatFound) + return InvalidContainer("The WAV fmt chunk is missing."); + if (!dataFound) + return InvalidContainer("The WAV data chunk is missing."); + return hasAudioData + ? new WaveContainerInspection(WaveContainerStatus.AudioData, string.Empty) + : new WaveContainerInspection( + WaveContainerStatus.SilentPlaceholder, + "The WAV contains a valid zero-length data chunk."); + } + catch (Exception exception) + { + return InvalidContainer(exception.Message); + } + } + + public static bool TryDecodeFile(string filePath, out DecodedWaveAudio audio, out string error) + { + try + { + return TryDecode(File.ReadAllBytes(filePath), out audio, out error); + } + catch (Exception exception) + { + audio = null; + error = exception.Message; + return false; + } + } + + public static bool TryDecode(byte[] bytes, out DecodedWaveAudio audio, out string error) + { + audio = null; + error = string.Empty; + try + { + if (bytes == null || bytes.Length < 12) + return Fail("The WAV file is truncated.", out error); + + using var stream = new MemoryStream(bytes, false); + using var reader = new BinaryReader(stream); + if (reader.ReadUInt32() != RiffId || reader.ReadUInt32() + 8 > stream.Length || reader.ReadUInt32() != WaveId) + return Fail("The file is not a valid RIFF/WAVE stream.", out error); + + WaveFormat format = null; + var dataOffset = -1; + var dataLength = 0; + while (stream.Position + 8 <= stream.Length) + { + var chunkId = reader.ReadUInt32(); + var declaredSize = reader.ReadUInt32(); + var chunkStart = stream.Position; + var availableSize = Math.Min((long)declaredSize, stream.Length - chunkStart); + + if (chunkId == FormatId) + { + if (!TryReadFormat(reader, availableSize, out format, out error)) + return false; + } + else if (chunkId == DataId) + { + dataOffset = checked((int)chunkStart); + dataLength = checked((int)availableSize); + } + + var nextChunk = chunkStart + declaredSize + (declaredSize & 1); + if (nextChunk <= chunkStart || nextChunk > stream.Length) + break; + stream.Position = nextChunk; + } + + if (format == null) + return Fail("The WAV fmt chunk is missing.", out error); + if (dataOffset < 0 || dataLength == 0) + return Fail("The WAV data chunk is missing or empty.", out error); + if (format.FormatTag != MicrosoftAdpcmFormat) + return Fail($"WAV format 0x{format.FormatTag:X4} is not Microsoft ADPCM.", out error); + if (format.Channels < 1 || format.Channels > 2) + return Fail($"Microsoft ADPCM with {format.Channels} channels is not supported.", out error); + if (format.BlockAlign <= format.Channels * 7 || format.SamplesPerBlock < 2) + return Fail("The Microsoft ADPCM block layout is invalid.", out error); + + var samples = DecodeMicrosoftAdpcm(bytes, dataOffset, dataLength, format); + if (samples.Count == 0) + return Fail("The Microsoft ADPCM stream contains no decodable samples.", out error); + + audio = new DecodedWaveAudio(samples.ToArray(), format.Channels, format.SampleRate); + return true; + } + catch (Exception exception) + { + audio = null; + error = exception.Message; + return false; + } + } + + private static bool TryReadFormat( + BinaryReader reader, + long chunkSize, + out WaveFormat format, + out string error) + { + format = null; + error = string.Empty; + if (chunkSize < 22) + return Fail("The Microsoft ADPCM fmt chunk is truncated.", out error); + + var start = reader.BaseStream.Position; + var formatTag = reader.ReadUInt16(); + var channels = reader.ReadUInt16(); + var sampleRate = checked((int)reader.ReadUInt32()); + reader.ReadUInt32(); + var blockAlign = reader.ReadUInt16(); + reader.ReadUInt16(); + reader.ReadUInt16(); + var samplesPerBlock = reader.ReadUInt16(); + var coefficientCount = reader.ReadUInt16(); + + if (coefficientCount == 0 || start + 22L + coefficientCount * 4L > start + chunkSize) + return Fail("The Microsoft ADPCM coefficient table is invalid.", out error); + + var coefficient1 = new short[coefficientCount]; + var coefficient2 = new short[coefficientCount]; + for (var index = 0; index < coefficientCount; index++) + { + coefficient1[index] = reader.ReadInt16(); + coefficient2[index] = reader.ReadInt16(); + } + + format = new WaveFormat( + formatTag, + channels, + sampleRate, + blockAlign, + samplesPerBlock, + coefficient1, + coefficient2); + return true; + } + + private static List DecodeMicrosoftAdpcm( + byte[] bytes, + int dataOffset, + int dataLength, + WaveFormat format) + { + var estimatedFrames = (dataLength / format.BlockAlign + 1) * format.SamplesPerBlock; + var samples = new List(Math.Max(estimatedFrames * format.Channels, 0)); + var end = dataOffset + dataLength; + for (var blockOffset = dataOffset; blockOffset < end; blockOffset += format.BlockAlign) + { + var blockLength = Math.Min(format.BlockAlign, end - blockOffset); + var headerLength = format.Channels * 7; + if (blockLength < headerLength) + break; + + var states = new ChannelState[format.Channels]; + var cursor = blockOffset; + for (var channel = 0; channel < format.Channels; channel++) + states[channel].Predictor = bytes[cursor++]; + for (var channel = 0; channel < format.Channels; channel++) + { + states[channel].Delta = ReadInt16(bytes, cursor); + cursor += 2; + } + for (var channel = 0; channel < format.Channels; channel++) + { + states[channel].Sample1 = ReadInt16(bytes, cursor); + cursor += 2; + } + for (var channel = 0; channel < format.Channels; channel++) + { + states[channel].Sample2 = ReadInt16(bytes, cursor); + cursor += 2; + if (states[channel].Predictor >= format.Coefficient1.Length) + throw new InvalidDataException("A Microsoft ADPCM block uses an unknown predictor."); + states[channel].Delta = Math.Max(states[channel].Delta, 16); + } + + AppendInitialFrames(samples, states); + var decodedFrames = 2; + var blockEnd = blockOffset + blockLength; + if (format.Channels == 1) + { + while (cursor < blockEnd && decodedFrames < format.SamplesPerBlock) + { + var packed = bytes[cursor++]; + AppendDecodedSample(samples, ref states[0], packed >> 4, format); + decodedFrames++; + if (decodedFrames >= format.SamplesPerBlock) + break; + AppendDecodedSample(samples, ref states[0], packed & 0x0F, format); + decodedFrames++; + } + } + else + { + while (cursor < blockEnd && decodedFrames < format.SamplesPerBlock) + { + var packed = bytes[cursor++]; + AppendDecodedSample(samples, ref states[0], packed >> 4, format); + AppendDecodedSample(samples, ref states[1], packed & 0x0F, format); + decodedFrames++; + } + } + } + + return samples; + } + + private static void AppendInitialFrames(List samples, ChannelState[] states) + { + foreach (var state in states) + samples.Add(ToFloat(state.Sample2)); + foreach (var state in states) + samples.Add(ToFloat(state.Sample1)); + } + + private static void AppendDecodedSample( + List samples, + ref ChannelState state, + int nibble, + WaveFormat format) + { + var signedNibble = nibble >= 8 ? nibble - 16 : nibble; + var predicted = (state.Sample1 * format.Coefficient1[state.Predictor] + + state.Sample2 * format.Coefficient2[state.Predictor]) / 256; + predicted += signedNibble * state.Delta; + predicted = Math.Max(short.MinValue, Math.Min(short.MaxValue, predicted)); + + state.Sample2 = state.Sample1; + state.Sample1 = predicted; + state.Delta = Math.Max(16, AdaptationTable[nibble] * state.Delta / 256); + samples.Add(ToFloat(predicted)); + } + + private static short ReadInt16(byte[] bytes, int offset) => + unchecked((short)(bytes[offset] | bytes[offset + 1] << 8)); + + private static float ToFloat(int sample) => sample / 32768f; + + private static bool Fail(string message, out string error) + { + error = message; + return false; + } + + private static WaveContainerInspection InvalidContainer(string detail) => + new WaveContainerInspection(WaveContainerStatus.Invalid, detail); + + private sealed class WaveFormat + { + public WaveFormat( + ushort formatTag, + int channels, + int sampleRate, + int blockAlign, + int samplesPerBlock, + short[] coefficient1, + short[] coefficient2) + { + FormatTag = formatTag; + Channels = channels; + SampleRate = sampleRate; + BlockAlign = blockAlign; + SamplesPerBlock = samplesPerBlock; + Coefficient1 = coefficient1; + Coefficient2 = coefficient2; + } + + public ushort FormatTag { get; } + public int Channels { get; } + public int SampleRate { get; } + public int BlockAlign { get; } + public int SamplesPerBlock { get; } + public short[] Coefficient1 { get; } + public short[] Coefficient2 { get; } + } + + private struct ChannelState + { + public int Predictor; + public int Delta; + public int Sample1; + public int Sample2; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Audio/WaveAudioDecoder.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Audio/WaveAudioDecoder.cs.meta new file mode 100644 index 0000000..0cd462e --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Audio/WaveAudioDecoder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1234f513bc93d48daaef279c67e00820 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap.meta b/Assets/ProjectQuint/Runtime/Unity/Bootstrap.meta new file mode 100644 index 0000000..7882528 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8ac866a5c17ce403e9b745726534f447 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/AssemblyInfo.cs b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/AssemblyInfo.cs new file mode 100644 index 0000000..2ab96b6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ProjectQuint.Tests.EditMode")] diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/AssemblyInfo.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/AssemblyInfo.cs.meta new file mode 100644 index 0000000..38c895b --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/AssemblyInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: db766f0a4143249338cae181a3130071 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/PinnedPackPremixCacheService.cs b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/PinnedPackPremixCacheService.cs new file mode 100644 index 0000000..3febfae --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/PinnedPackPremixCacheService.cs @@ -0,0 +1,508 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ProjectQuint.Audio; +using ProjectQuint.Core.Charts; +using ProjectQuint.Features.Loading; +using ProjectQuint.Library; +using ProjectQuint.Settings; +using UnityEngine; + +namespace ProjectQuint.Bootstrap +{ + internal readonly struct PinnedPackCacheProgress + { + public PinnedPackCacheProgress(int current, int total, string detail) + { + Current = Math.Max(0, current); + Total = Math.Max(0, total); + Detail = detail ?? string.Empty; + } + + public int Current { get; } + public int Total { get; } + public string Detail { get; } + } + + internal sealed class PinnedPackCachePlan + { + public BmsPackDefinition Pack { get; set; } + public List Items { get; set; } = new List(); + public int SourceChartCount { get; set; } + public int ExistingPinnedCount { get; set; } + public int AutomaticPromotionCount { get; set; } + public int RenderCount { get; set; } + public long EstimatedAdditionalBytes { get; set; } + public long AvailableBytes { get; set; } + public TimeSpan EstimatedDuration { get; set; } + public int OutputSampleRate { get; set; } + + public string Summary => + $"{SourceChartCount:N0} CHARTS // {Items.Count:N0} UNIQUE MIXES\n" + + $"{ExistingPinnedCount:N0} PINNED HITS // {AutomaticPromotionCount:N0} AUTO HITS // {RenderCount:N0} TO ENCODE\n" + + $"ESTIMATED ADDITIONAL {PremixedSongCacheSnapshot.FormatBytes(EstimatedAdditionalBytes)}\n" + + $"FREE SPACE {PremixedSongCacheSnapshot.FormatBytes(AvailableBytes)} // " + + $"ESTIMATED TIME {FormatDuration(EstimatedDuration)}"; + + private static string FormatDuration(TimeSpan value) => value.TotalHours >= 1 + ? $"{(int)value.TotalHours}:{value.Minutes:00}:{value.Seconds:00}" + : $"{(int)value.TotalMinutes}:{value.Seconds:00}"; + } + + internal sealed class PinnedPackCacheItem + { + public SongLibraryEntry Entry { get; set; } + public string Key { get; set; } = string.Empty; + public double DurationSeconds { get; set; } + public PremixedSongCacheTier ExistingTier { get; set; } = PremixedSongCacheTier.Automatic; + public bool Exists { get; set; } + public long ExistingBytes { get; set; } + } + + internal readonly struct PinnedPackCacheStatus + { + public PinnedPackCacheStatus( + BmsPackDefinition pack, + bool isPinned, + int objectCount, + int sharedObjectCount, + int missingObjectCount, + long referencedBytes, + long exclusiveBytes, + DateTime createdUtc) + { + Pack = pack; + IsPinned = isPinned; + ObjectCount = Math.Max(0, objectCount); + SharedObjectCount = Math.Max(0, sharedObjectCount); + MissingObjectCount = Math.Max(0, missingObjectCount); + ReferencedBytes = Math.Max(0, referencedBytes); + ExclusiveBytes = Math.Max(0, exclusiveBytes); + CreatedUtc = createdUtc; + } + + public BmsPackDefinition Pack { get; } + public bool IsPinned { get; } + public int ObjectCount { get; } + public int SharedObjectCount { get; } + public int MissingObjectCount { get; } + public long ReferencedBytes { get; } + public long ExclusiveBytes { get; } + public DateTime CreatedUtc { get; } + } + + internal readonly struct PinnedPackCacheDeleteResult + { + public PinnedPackCacheDeleteResult(bool success, int deletedObjects, long freedBytes, string status) + { + Success = success; + DeletedObjects = Math.Max(0, deletedObjects); + FreedBytes = Math.Max(0, freedBytes); + Status = status ?? string.Empty; + } + + public bool Success { get; } + public int DeletedObjects { get; } + public long FreedBytes { get; } + public string Status { get; } + } + + /// + /// Plans and creates an explicitly approved, unbounded pack cache. Objects remain globally + /// content-addressed; the pack manifest only records ownership, so identical charts across + /// packs share the same pinned FLAC instead of being encoded repeatedly. + /// + internal sealed class PinnedPackPremixCacheService + { + private const double EstimatedFlacRatioForPcm24 = 0.55; + private const double EstimatedRenderTimePerAudioSecond = 0.20; + private readonly PremixedSongCache _cache; + private readonly string _manifestRoot; + + public PinnedPackPremixCacheService(PremixedSongCache cache = null) + { + _cache = cache ?? PremixedSongCache.Shared; + _manifestRoot = Path.Combine(_cache.PinnedRootPath, "manifests"); + } + + public Task AnalyzeAsync( + BmsPackDefinition pack, + IReadOnlyList entries, + int outputSampleRate, + CancellationToken cancellationToken) => Task.Run(() => + { + if (pack == null) + throw new ArgumentNullException(nameof(pack)); + if (entries == null) + throw new ArgumentNullException(nameof(entries)); + if (outputSampleRate <= 0) + throw new ArgumentOutOfRangeException(nameof(outputSampleRate)); + + var unique = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var entry in entries) + { + cancellationToken.ThrowIfCancellationRequested(); + var prepared = SongLoadCoordinator.ParseAndTransform(entry, LaneModifier.Off, 0); + var key = _cache.BuildKey(prepared.Chart, outputSampleRate, cancellationToken); + if (unique.ContainsKey(key)) + continue; + var read = _cache.TryRead(key, outputSampleRate, cancellationToken); + unique[key] = new PinnedPackCacheItem + { + Entry = entry, + Key = key, + DurationSeconds = Math.Max(0.01, prepared.Chart.Duration), + Exists = read.Hit, + ExistingTier = read.Hit ? read.Entry.Tier : PremixedSongCacheTier.Automatic, + ExistingBytes = read.Hit ? read.Entry.EncodedBytes : 0, + }; + } + + var items = unique.Values.ToList(); + var additionalBytes = items.Sum(item => item.Exists + ? item.ExistingTier == PremixedSongCacheTier.Automatic ? item.ExistingBytes : 0 + : EstimateFlacBytes(item.DurationSeconds, outputSampleRate)); + var renderSeconds = items.Where(item => !item.Exists).Sum(item => + 0.35 + item.DurationSeconds * EstimatedRenderTimePerAudioSecond); + return new PinnedPackCachePlan + { + Pack = pack, + Items = items, + SourceChartCount = entries.Count, + ExistingPinnedCount = items.Count(item => item.Exists && + item.ExistingTier == PremixedSongCacheTier.Pinned), + AutomaticPromotionCount = items.Count(item => item.Exists && + item.ExistingTier == PremixedSongCacheTier.Automatic), + RenderCount = items.Count(item => !item.Exists), + EstimatedAdditionalBytes = additionalBytes, + AvailableBytes = GetAvailableBytes(_cache.PinnedRootPath), + EstimatedDuration = TimeSpan.FromSeconds(Math.Max(1.0, renderSeconds)), + OutputSampleRate = outputSampleRate, + }; + }, cancellationToken); + + public IReadOnlyList GetPackStatuses( + IReadOnlyList packs) + { + var manifests = LoadManifests(); + var referenceCounts = BuildReferenceCounts(manifests); + var result = new List(packs?.Count ?? 0); + foreach (var pack in packs ?? Array.Empty()) + { + var manifest = manifests.FirstOrDefault(item => + string.Equals(item.PackId, pack.Id, StringComparison.OrdinalIgnoreCase)); + if (manifest == null) + { + result.Add(new PinnedPackCacheStatus(pack, false, 0, 0, 0, 0, 0, default)); + continue; + } + + long referencedBytes = 0; + long exclusiveBytes = 0; + var sharedCount = 0; + var missingCount = 0; + var keys = manifest.Keys + .Where(IsValidKey) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + foreach (var key in keys) + { + var audioPath = PinnedAudioPath(key); + if (!File.Exists(audioPath)) + { + missingCount++; + continue; + } + var bytes = new FileInfo(audioPath).Length; + referencedBytes += bytes; + if (referenceCounts.TryGetValue(key, out var references) && references > 1) + sharedCount++; + else + exclusiveBytes += bytes; + } + var created = manifest.CreatedUtcTicks > 0 + ? new DateTime(manifest.CreatedUtcTicks, DateTimeKind.Utc) + : default; + result.Add(new PinnedPackCacheStatus( + pack, + true, + keys.Length, + sharedCount, + missingCount, + referencedBytes, + exclusiveBytes, + created)); + } + return result; + } + + public PinnedPackCacheDeleteResult DeletePackCache(string packId) + { + if (string.IsNullOrWhiteSpace(packId)) + return new PinnedPackCacheDeleteResult(false, 0, 0, "PACK ID IS EMPTY"); + try + { + var manifests = LoadManifests(); + var target = manifests.FirstOrDefault(item => + string.Equals(item.PackId, packId, StringComparison.OrdinalIgnoreCase)); + if (target == null) + return new PinnedPackCacheDeleteResult(false, 0, 0, "PACK CACHE NOT FOUND"); + DeleteIfPresent(target.SourcePath); + var collection = CollectOrphanedObjects(); + return new PinnedPackCacheDeleteResult( + true, + collection.DeletedObjects, + collection.FreedBytes, + collection.Status.StartsWith("ORPHAN CLEANUP DEFERRED", StringComparison.OrdinalIgnoreCase) + ? $"PACK CACHE REMOVED // {collection.Status}" + : $"PACK CACHE REMOVED // {collection.DeletedObjects:N0} ORPHANS // " + + $"{PremixedSongCacheSnapshot.FormatBytes(collection.FreedBytes)} FREED"); + } + catch (Exception exception) + { + return new PinnedPackCacheDeleteResult(false, 0, 0, $"DELETE FAILED // {exception.Message}"); + } + } + + public IEnumerator Build( + PinnedPackCachePlan plan, + Action progress, + Func cancellationRequested, + Action completed) + { + if (plan?.Pack == null) + throw new ArgumentNullException(nameof(plan)); + var completedKeys = new List(plan.Items.Count); + var hadManifest = HasManifest(plan.Pack.Id); + for (var index = 0; index < plan.Items.Count; index++) + { + if (cancellationRequested?.Invoke() == true) + { + if (!hadManifest && completedKeys.Count > 0) + WriteManifest(plan.Pack, completedKeys); + completed?.Invoke("PINNED PACK CACHE CANCELLED"); + yield break; + } + + var item = plan.Items[index]; + progress?.Invoke(new PinnedPackCacheProgress( + index, + plan.Items.Count, + $"{item.Entry.DisplayTitle} // CHECKING CONTENT CACHE")); + var current = _cache.TryRead(item.Key, plan.OutputSampleRate); + if (current.Hit && current.Entry.Tier == PremixedSongCacheTier.Pinned) + { + completedKeys.Add(item.Key); + continue; + } + if (current.Hit && _cache.PromoteToPinned(item.Key, plan.OutputSampleRate, out _)) + { + completedKeys.Add(item.Key); + continue; + } + + SongLoadCoordinator.PreparedChart prepared; + try + { + prepared = SongLoadCoordinator.ParseAndTransform(item.Entry, LaneModifier.Off, 0); + } + catch (Exception exception) + { + Debug.LogWarning($"[ProjectQuint Audio] Pinned cache skipped {item.Entry.ChartPath}: {exception.Message}"); + continue; + } + + using var assets = new AudioAssetStore(); + using var renderer = new PremixedSongRenderer(_cache); + yield return renderer.Prepare( + prepared.Chart, + assets, + (currentItem, totalItems, detail) => progress?.Invoke(new PinnedPackCacheProgress( + index, + plan.Items.Count, + $"{item.Entry.DisplayTitle} // {detail} ({currentItem}/{totalItems})")), + cancellationRequested, + PremixedSongOutput.FmodStreamFile, + plan.OutputSampleRate, + PremixedSongCacheTier.Pinned); + if (renderer.State == PremixedSongState.Cancelled) + { + if (!hadManifest && completedKeys.Count > 0) + WriteManifest(plan.Pack, completedKeys); + completed?.Invoke("PINNED PACK CACHE CANCELLED"); + yield break; + } + if (!renderer.IsReady) + { + Debug.LogWarning( + $"[ProjectQuint Audio] Pinned cache skipped {item.Entry.ChartPath}: {renderer.Error}"); + continue; + } + completedKeys.Add(item.Key); + } + + WriteManifest(plan.Pack, completedKeys); + CollectOrphanedObjects(); + progress?.Invoke(new PinnedPackCacheProgress(plan.Items.Count, plan.Items.Count, "PACK CACHE READY")); + completed?.Invoke( + $"PINNED {completedKeys.Count:N0}/{plan.Items.Count:N0} UNIQUE MIXES // " + + $"{plan.Pack.DisplayName}"); + } + + private static long EstimateFlacBytes(double durationSeconds, int outputSampleRate) => + checked((long)Math.Ceiling( + Math.Max(0.01, durationSeconds) * outputSampleRate * + SmoothAudioMixer.OutputChannels * 3d * EstimatedFlacRatioForPcm24)); + + private static long GetAvailableBytes(string targetPath) + { + try + { + var root = Path.GetPathRoot(Path.GetFullPath(targetPath)); + return string.IsNullOrWhiteSpace(root) ? 0 : new DriveInfo(root).AvailableFreeSpace; + } + catch + { + return 0; + } + } + + private bool HasManifest(string packId) => LoadManifests().Any(item => + string.Equals(item.PackId, packId, StringComparison.OrdinalIgnoreCase)); + + private void WriteManifest(BmsPackDefinition pack, List keys) + { + Directory.CreateDirectory(_manifestRoot); + var document = new PinnedPackCacheManifest + { + PackId = pack.Id ?? string.Empty, + PackName = pack.DisplayName ?? string.Empty, + CreatedUtcTicks = DateTime.UtcNow.Ticks, + Keys = keys.Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(key => key).ToList(), + }; + var path = Path.Combine( + _manifestRoot, + SanitizeFileName(document.PackId) + ".json"); + var temporaryPath = path + ".tmp"; + File.WriteAllText(temporaryPath, JsonUtility.ToJson(document, true)); + if (File.Exists(path)) + File.Delete(path); + File.Move(temporaryPath, path); + } + + private static string SanitizeFileName(string value) + { + var builder = new System.Text.StringBuilder(); + foreach (var character in value ?? string.Empty) + builder.Append(char.IsLetterOrDigit(character) || character == '-' || character == '_' + ? character + : '_'); + return builder.Length == 0 ? "pack" : builder.ToString(); + } + + private List LoadManifests() => LoadManifests(out _); + + private List LoadManifests(out bool invalidManifestDetected) + { + invalidManifestDetected = false; + var result = new List(); + if (!Directory.Exists(_manifestRoot)) + return result; + foreach (var path in Directory.EnumerateFiles( + _manifestRoot, + "*.json", + SearchOption.TopDirectoryOnly)) + { + try + { + var document = JsonUtility.FromJson(File.ReadAllText(path)); + if (document == null || document.SchemaVersion != 1 || + string.IsNullOrWhiteSpace(document.PackId) || document.Keys == null) + { + invalidManifestDetected = true; + continue; + } + document.SourcePath = path; + result.Add(document); + } + catch (Exception exception) + { + invalidManifestDetected = true; + Debug.LogWarning($"[ProjectQuint Audio] Ignored invalid pack cache manifest '{path}': {exception.Message}"); + } + } + return result; + } + + private static Dictionary BuildReferenceCounts( + IEnumerable manifests) + { + var counts = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var manifest in manifests) + foreach (var key in manifest.Keys.Where(IsValidKey).Distinct(StringComparer.OrdinalIgnoreCase)) + counts[key] = counts.TryGetValue(key, out var count) ? count + 1 : 1; + return counts; + } + + private PinnedPackCacheDeleteResult CollectOrphanedObjects() + { + var manifests = LoadManifests(out var invalidManifestDetected); + if (invalidManifestDetected) + return new PinnedPackCacheDeleteResult( + true, + 0, + 0, + "ORPHAN CLEANUP DEFERRED // INVALID MANIFEST REQUIRES REBUILD"); + var referenced = new HashSet( + manifests.SelectMany(manifest => manifest.Keys).Where(IsValidKey), + StringComparer.OrdinalIgnoreCase); + if (!Directory.Exists(_cache.PinnedRootPath)) + return new PinnedPackCacheDeleteResult(true, 0, 0, "NO ORPHANS"); + var deleted = 0; + long freed = 0; + foreach (var audioPath in Directory.EnumerateFiles( + _cache.PinnedRootPath, + "*.flac", + SearchOption.TopDirectoryOnly)) + { + var key = Path.GetFileNameWithoutExtension(audioPath); + if (referenced.Contains(key)) + continue; + var bytes = new FileInfo(audioPath).Length; + File.Delete(audioPath); + DeleteIfPresent(Path.ChangeExtension(audioPath, ".pqmeta")); + deleted++; + freed += bytes; + } + return new PinnedPackCacheDeleteResult(true, deleted, freed, "ORPHAN COLLECTION COMPLETE"); + } + + private string PinnedAudioPath(string key) => + Path.Combine(_cache.PinnedRootPath, key.ToLowerInvariant() + ".flac"); + + private static bool IsValidKey(string key) => !string.IsNullOrWhiteSpace(key) && + key.Length == 64 && + key.All(Uri.IsHexDigit); + + private static void DeleteIfPresent(string path) + { + if (!string.IsNullOrWhiteSpace(path) && File.Exists(path)) + File.Delete(path); + } + + [Serializable] + private sealed class PinnedPackCacheManifest + { + public int SchemaVersion = 1; + public string PackId = string.Empty; + public string PackName = string.Empty; + public long CreatedUtcTicks; + public List Keys = new List(); + [NonSerialized] public string SourcePath = string.Empty; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/PinnedPackPremixCacheService.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/PinnedPackPremixCacheService.cs.meta new file mode 100644 index 0000000..bf1b7a6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/PinnedPackPremixCacheService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d334e055c8b5349daa533ef3a1eb3d96 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuint.Bootstrap.asmdef b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuint.Bootstrap.asmdef new file mode 100644 index 0000000..c5c0817 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuint.Bootstrap.asmdef @@ -0,0 +1,23 @@ +{ + "name": "ProjectQuint.Bootstrap", + "rootNamespace": "ProjectQuint.Bootstrap", + "references": [ + "ProjectQuint.Application", + "ProjectQuint.Core", + "ProjectQuint.Infrastructure", + "ProjectQuint.Media.Audio", + "ProjectQuint.Media.Images", + "ProjectQuint.Media.Video", + "ProjectQuint.Presentation", + "Unity.InputSystem" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuint.Bootstrap.asmdef.meta b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuint.Bootstrap.asmdef.meta new file mode 100644 index 0000000..9618654 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuint.Bootstrap.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 178f6069f8ef347608b519f555a40a11 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintApp.cs b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintApp.cs new file mode 100644 index 0000000..fef8199 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintApp.cs @@ -0,0 +1,7251 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ProjectQuint.Features.Course; +using ProjectQuint.Features.Flow; +using ProjectQuint.Features.Gameplay; +using ProjectQuint.Features.Library; +using ProjectQuint.Features.Loading; +using ProjectQuint.Features.Results; +using ProjectQuint.Features.Settings; +using ProjectQuint.Audio; +using ProjectQuint.Core.Bms; +using ProjectQuint.Core.Charts; +using ProjectQuint.Core.Course; +using ProjectQuint.Core.Gauge; +using ProjectQuint.Core.Judgement; +using ProjectQuint.Core.Progression; +using ProjectQuint.Core.Release; +using ProjectQuint.Library; +using ProjectQuint.Persistence; +using ProjectQuint.Infrastructure.Images; +using ProjectQuint.Settings; +using ProjectQuint.UI; +using ProjectQuint.Video; +using UnityEngine; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.LowLevel; +using Unity.Profiling; + +namespace ProjectQuint.Bootstrap +{ + public sealed class ProjectQuintApp : MonoBehaviour + { + private static readonly int[] QuickOptionsVisualOrder = + { + 0, 12, 2, 3, 4, 5, + 1, 6, 7, 8, + 9, 10, 11, + }; + private static readonly int[] PackKeyModeChoices = { 0, 4, 5, 6, 7 }; + private const double LibraryViewStateSaveDelaySeconds = 0.75; + private static readonly ProfilerMarker GameplayUpdateMarker = + new ProfilerMarker("ProjectQuint.Gameplay.Update"); + private static readonly ProfilerMarker JudgementUpdateMarker = + new ProfilerMarker("ProjectQuint.Gameplay.Judgement"); + private static readonly ProfilerMarker BgaUpdateMarker = + new ProfilerMarker("ProjectQuint.Gameplay.BGA"); + private static readonly ProfilerMarker GameplayRenderMarker = + new ProfilerMarker("ProjectQuint.Gameplay.Render"); + + private static bool s_Created; + private readonly string[] _laneKeyLabels = new string[GameSettings.MaximumKeyCount]; + private readonly GameplayNoteWindow _gameplayNoteWindow = new GameplayNoteWindow(); + private readonly GameplayFramePacingMonitor _framePacingMonitor = new GameplayFramePacingMonitor(); + private readonly UnityFrameTimingMonitor _unityFrameTimingMonitor = new UnityFrameTimingMonitor(); + private readonly GameplayWorkloadMonitor _workloadMonitor = new GameplayWorkloadMonitor(); + private readonly HighRateInputMonitor _inputMonitor = new HighRateInputMonitor(); + private readonly DirectionalKeyRepeat _optionsValueKeyRepeat = new DirectionalKeyRepeat(); + private bool _continuousOptionsDirty; + private MacNativeKeyboardPoller _nativeInputPoller; + private string _inputPollingStatus = string.Empty; + private StartupBrandingPresenter _startupBrandingPresenter; + private HomeScreenPresenter _homeScreenPresenter; + private UpdateNoticePresenter _updateNoticePresenter; + private ProjectQuintSparkleUpdater _sparkleUpdater; + private PackMusicScreenPresenter _packMusicScreenPresenter; + private OptionsProfileCoursePresenter _optionsProfileCoursePresenter; + private SessionScreensPresenter _sessionScreensPresenter; + private FpsCounterPresenter _fpsCounterPresenter; + private GameplayPlayfieldPresenter _gameplayPlayfieldPresenter; + private GameplayHudPresenter _gameplayHudPresenter; + private PreparedSong _preparedSong; + private ISongAudioBackend _audio; + private ISongAudioBackendFactory _audioBackendFactory; + private SongAudioBackendKind _audioBackendKind = SongAudioBackendKind.FmodCore; + private IAudioClock _audioClock; + private AudioSyncCalibrationSession _audioSyncCalibration; + private InputAction _audioSyncTapAction; + private string _audioSyncCalibrationError = string.Empty; + private string _audioBackendInitializationError = string.Empty; + private AudioDspBufferController _audioDspBufferController; + private bool _audioBackendResetting; + private string _audioDspStatus = string.Empty; + private string _premixCacheStatus = string.Empty; + private bool _premixCacheClearing; + private PinnedPackPremixCacheService _pinnedPackCacheService; + private CancellationTokenSource _pinnedPackCacheCancellation; + private PinnedPackCachePlan _pendingPinnedPackCachePlan; + private string _pinnedPackCacheStatus = "ESTIMATE DISK SPACE AND TIME BEFORE APPROVAL"; + private bool _pinnedPackCacheConfirmationPending; + private bool _pinnedPackCacheWorking; + private bool _packCacheManagerOpen; + private int _packCacheSelection; + private IReadOnlyList _packCacheStatuses = + Array.Empty(); + private IReadOnlyList _packCacheUiEntries = + Array.Empty(); + private BmsBgaSystem _bga; + private GameplaySessionController _gameplaySession; + private SongLibraryScanner _scanner; + private CancellationTokenSource _scanCancellation; + private CancellationTokenSource _packImportCancellation; + private BmsPackRegistry _packRegistry; + private BmsDifficultyTableRegistry _difficultyTableRegistry; + private BmsPackDefinition _activePack; + private bool _packKeyModeModalOpen; + private int _packKeyModeModalSelection; + private string _pendingPackKeyModeId = string.Empty; + private bool _packRemovalConfirmationPending; + private bool _packRemovalInProgress; + private string _pendingPackRemovalId = string.Empty; + private string _pendingPackRemovalName = string.Empty; + private BmsDifficultyTableDefinition _activeDifficultyTable; + private int _tableSetupSelection; + private string _tableSetupPackId = string.Empty; + private string _tableUrlDraft = string.Empty; + private string _tableSetupStatus = string.Empty; + private bool _tableUrlFocusRequested; + private bool _tableUrlReleaseRequested; + private bool _tableUrlFocused; + private InputAction[] _laneActions; + private GameSettings _settings; + private GameSettingsRepository _settingsRepository; + private FavoriteChartRepository _favoriteRepository; + private LibraryController _libraryController; + private SongLoadCoordinator _songLoadCoordinator; + private AppFlowController _flow; + private CourseSessionController _courseSession; + private ResultSessionController _resultSession; + private PlayerProgressionRepository _progressionRepository; + private PlayResultCommitCoordinator _playCommitCoordinator; + private ProfileArchiveService _profileArchiveService; + private ProfileImageStore _profileImageStore; + private Texture2D _profileImageTexture; + private string _profileImageStatus = string.Empty; + private string _profileTransferStatus = "PROFILE ARCHIVE READY"; + private string _updateStatus = "NOT CHECKED"; + private ProjectQuintUpdateManifest _availableUpdate; + private bool _automaticUpdateCheckStarted; + private bool _updateCheckRunning; + private bool _updateOfferPending; + private bool _updateModalOpen; + private bool _updateDismissedThisRun; + private int _updateModalSelection; + private bool _profileImportConfirmationPending; + private ProfileProgressSnapshot _profileSnapshot; + private RatingSummary _homeRatingSummary; + private RatingSummary _profileRatingSummary; + private IReadOnlyList _ratingContexts = Array.Empty(); + private int _profileRatingContextIndex; + private IReadOnlyList _profileRatingThumbnailSongs = + Array.Empty(); + private int _profileRatingThumbnailSongsRevision; + private long _profileRatingThumbnailSourceSignature = long.MinValue; + private ProfileCollectionSnapshot _profileCollection = ProfileCollectionSnapshot.Empty; + private ProfileCourseSnapshot _profileCourses = ProfileCourseSnapshot.Empty; + private bool _profileModalOpen; + private int _profileModalTab; + private int _profileBadgeSelection; + private int _profileCollectionColumn; + private readonly int[] _profileCollectionSelections = new int[3]; + private string _libraryRoot = string.Empty; + private string _libraryLoadError = string.Empty; + private bool _libraryReady; + private bool _libraryScanRunning; + private bool _openLibraryWhenScanCompletes; + private bool _libraryViewStateDirty; + private double _libraryViewStateSaveDeadline; + private SongLibraryEntry _loadingEntry; + private SongLibraryEntry _playingEntry; + private AeryCourseIndex _courseIndex; + private AeryTableIndex _aeryCatalog; + private IChartCatalog _activeCatalog; + private SearchAliasIndex _searchAliases = SearchAliasIndex.Empty; + private int _aeryMatchedCharts; + private int _catalogMatchedCharts; + private int _libraryScanGeneration; + private Texture2D _libraryThumbnail; + private Texture2D _stageTexture; + private string _thumbnailChartPath = string.Empty; + private int _thumbnailLoadVersion; + + private Texture CurrentArtworkTexture => + _libraryThumbnail != null + ? _libraryThumbnail + : _stageTexture != null + ? _stageTexture + : DefaultJacketProvider.Texture; + private double _startupAttributionEnteredRealtime; + private double _teamDeniaBrandingEnteredRealtime; + private int _songLoadGeneration; + private int _fpsSampleFrames; + private float _fpsSampleElapsed; + private float _displayedFps; + private int _effectiveTargetFrameRate = GameSettingsDefaults.FrameLimit == FrameLimitMode.Unlimited + ? -1 + : GameSettings.TargetFrameRate(GameSettingsDefaults.FrameLimit); + private double _appliedDisplayRefreshRate = double.NaN; + private double _nextDisplayRefreshCheckRealtime; + private bool _autoPlayEnabled; + private bool _autoScratchEnabled; + private bool _gameplayDiagnosticsEnabled; + + // Compatibility accessors keep this first extraction behavior-neutral. Feature controllers + // can receive GameSettings directly as they are split out in later refactoring stages. + private Key[] _laneKeys => _settings.LaneKeys; + private Key _centerAuxKey => _settings.CenterAuxKey; + private bool _reactiveKeySoundsEnabled { get => _settings.ReactiveKeySoundsEnabled; set => _settings.ReactiveKeySoundsEnabled = value; } + private float _noteSpeed { get => _settings.NoteSpeed; set => _settings.NoteSpeed = value; } + private GameplayGearPosition _gearPosition { get => _settings.GearPosition; set => _settings.GearPosition = value; } + private LaneModifier _laneModifier { get => _settings.LaneModifier; set => _settings.LaneModifier = value; } + private GaugeMode _gaugeType { get => _settings.GaugeType; set => _settings.GaugeType = value; } + private bool _gaugeFallbackEnabled + { + get => _settings.GaugeFallbackEnabled; + set => _settings.GaugeFallbackEnabled = value; + } + private GameplaySkin _gameplaySkin { get => _settings.GameplaySkin; set => _settings.GameplaySkin = value; } + private GameplayNoteSkin _noteSkin { get => _settings.NoteSkin; set => _settings.NoteSkin = value; } + private LongNoteVisualStyle _longNoteStyle { get => _settings.LongNoteStyle; set => _settings.LongNoteStyle = value; } + private FrameLimitMode _frameLimitMode { get => _settings.FrameLimitMode; set => _settings.FrameLimitMode = value; } + private DisplayResolutionMode _displayResolutionMode { get => _settings.DisplayResolutionMode; set => _settings.DisplayResolutionMode = value; } + private WindowMode _windowMode { get => _settings.WindowMode; set => _settings.WindowMode = value; } + private int _judgementOffsetMilliseconds { get => _settings.JudgementOffsetMilliseconds; set => _settings.JudgementOffsetMilliseconds = value; } + private int _audioOutputLatencyMilliseconds { get => _settings.AudioOutputLatencyMilliseconds; set => _settings.AudioOutputLatencyMilliseconds = value; } + private int _masterVolumePercent { get => _settings.MasterVolumePercent; set => _settings.MasterVolumePercent = value; } + private AudioDspBufferProfile _audioDspBufferProfile + { + get => _settings.AudioDspBufferProfile; + set => _settings.AudioDspBufferProfile = value; + } + private AudioOutputDeviceInfo CurrentAudioOutputDevice + { + get + { + if (string.IsNullOrWhiteSpace(_settings?.AudioOutputDeviceId) || + _audioBackendFactory is not FmodCoreSongAudioBackendFactory fmodFactory) + return AudioOutputDeviceInfo.SystemDefault; + + foreach (var device in fmodFactory.OutputDevices) + { + if (string.Equals( + device.PersistentId, + _settings.AudioOutputDeviceId, + StringComparison.OrdinalIgnoreCase)) + return device; + } + return AudioOutputDeviceInfo.SystemDefault; + } + } + private bool _showFpsCounter { get => _settings.ShowFpsCounter; set => _settings.ShowFpsCounter = value; } + private InputPollingRate _inputPollingRate + { + get => _settings.InputPollingRate; + set => _settings.InputPollingRate = value; + } + private BgaPlaybackMode _bgaPlaybackMode + { + get => _settings.BgaPlayback; + set => _settings.BgaPlayback = value; + } + private bool DynamicBgaEnabled => _bgaPlaybackMode == BgaPlaybackMode.Full; + private bool StageBackgroundEnabled => _bgaPlaybackMode != BgaPlaybackMode.Black; + private bool IsAudioSystemResetting => + _audioBackendResetting || _audioDspBufferController?.IsResetting == true; + private BgaDisplayMode _bgaDisplayMode { get => _settings.BgaDisplayMode; set => _settings.BgaDisplayMode = value; } + private bool _vSyncEnabled { get => _settings.VSyncEnabled; set => _settings.VSyncEnabled = value; } + private bool _laneSeparatorsEnabled { get => _settings.LaneSeparatorsEnabled; set => _settings.LaneSeparatorsEnabled = value; } + private bool _timingMeterEnabled { get => _settings.TimingMeterEnabled; set => _settings.TimingMeterEnabled = value; } + private int _backgroundDimPercent { get => _settings.BackgroundDimPercent; set => _settings.BackgroundDimPercent = value; } + private AppMode _mode { get => _flow.Mode; set => _flow.TransitionTo(value); } + private string _status { get => _flow.Status; set => _flow.Status = value; } + private string _errorTitle => _flow.ErrorTitle; + private string _errorDetail => _flow.ErrorDetail; + private string _loadingTitle => _flow.LoadingTitle; + private string _loadingDetail => _flow.LoadingDetail; + private int _loadingCurrent => _flow.LoadingCurrent; + private int _loadingTotal => _flow.LoadingTotal; + private double _loadingEnteredRealtime => _flow.LoadingEnteredRealtime; + private int _homeSelection { get => _flow.HomeSelection; set => _flow.HomeSelection = value; } + private int _packSelection { get => _flow.PackSelection; set => _flow.PackSelection = value; } + private OptionsCategory _optionsCategory { get => _flow.OptionsCategory; set => _flow.OptionsCategory = value; } + private int _optionsSelectedRow { get => _flow.OptionsSelectedRow; set => _flow.OptionsSelectedRow = value; } + private int _optionsInputKeyCount { get => _flow.OptionsInputKeyCount; set => _flow.OptionsInputKeyCount = value; } + private int _quickOptionsSelectedRow { get => _flow.QuickOptionsSelectedRow; set => _flow.QuickOptionsSelectedRow = value; } + private int _pauseSelection { get => _flow.PauseSelection; set => _flow.PauseSelection = value; } + private int _gameOverSelection { get => _flow.GameOverSelection; set => _flow.GameOverSelection = value; } + private int _rebindLane { get => _flow.RebindLane; set => _flow.RebindLane = value; } + private AeryCourseDefinition _activeCourse => _courseSession.ActiveCourse; + private CourseProgress _courseProgress => _courseSession.Progress; + private AeryCourseTrophy _courseTrophy => _courseSession.Trophy; + private double _courseGaugeValue => _courseSession.GaugeValue; + private int _courseSelectedIndex => _courseSession.SelectedIndex; + private int _courseStageIndex => _courseSession.StageIndex; + private int _courseResultSelection { get => _courseSession.ResultSelection; set => _courseSession.ResultSelection = value; } + private AppMode _optionsReturnMode = AppMode.Home; + private bool _coursePassed => _courseSession.Passed; + private bool _courseBadgeEarned => _courseSession.CertificationBadgeEarned; + private string _courseStatus { get => _courseSession.Status; set => _courseSession.Status = value; } + private bool _songCleared => _resultSession.SongCleared; + private SongPlayRecord _resultPreviousRecord => _resultSession.PreviousRecord; + private double _resultEnteredRealtime => _resultSession.EnteredRealtime; + private int _resultsSelection { get => _resultSession.Selection; set => _resultSession.Selection = value; } + private PlayableChart _chart => _gameplaySession?.Chart; + private RhythmJudgementEngine _judgement => _gameplaySession?.Judgement; + private PlayGauge _gauge => _gameplaySession?.Gauge; + private bool[] _laneHeld => _gameplaySession?.LaneHeld; + private double[] _laneFlashTimes => _gameplaySession?.LaneFlashTimes; + private double _frameSongTime => _gameplaySession?.FrameSongTime ?? 0; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void Create() + { + if (s_Created) + return; + + s_Created = true; + var gameObject = new GameObject("ProjectQuint App"); + DontDestroyOnLoad(gameObject); + gameObject.AddComponent(); + } + + private void Awake() + { + Application.runInBackground = true; + AudioListener.pause = false; + Debug.Log( + $"[ProjectQuint] Starting {ProjectQuintReleaseVersion.DisplayVersionWithBuild}."); + _pinnedPackCacheService = new PinnedPackPremixCacheService(); + _flow = new AppFlowController(); + _courseSession = new CourseSessionController(); + _resultSession = new ResultSessionController(); + _startupBrandingPresenter = new StartupBrandingPresenter(gameObject); + _homeScreenPresenter = new HomeScreenPresenter( + gameObject, + HandleHomePresenterAction); + _updateNoticePresenter = new UpdateNoticePresenter( + gameObject, + HandleUpdateNoticeAction); + _sparkleUpdater = new ProjectQuintSparkleUpdater(); + if (_sparkleUpdater.TryInitialize(out var sparkleError)) + Debug.Log("[ProjectQuint] Sparkle automatic updater is ready."); + else if (!Application.isEditor) + Debug.LogWarning( + $"[ProjectQuint] Sparkle unavailable; manual download fallback remains active: " + + sparkleError); + _packMusicScreenPresenter = new PackMusicScreenPresenter( + gameObject, + HandlePackSelectUi, + HandleDifficultyTableUi, + HandleLibraryUi); + _optionsProfileCoursePresenter = new OptionsProfileCoursePresenter( + gameObject, + HandleOptionsUi, + HandleProfileModalUi, + HandleCourseSelectUi); + _sessionScreensPresenter = new SessionScreensPresenter( + gameObject, + new SessionScreensCallbacks( + HandleRetainedLoadingCancel, + selection => _quickOptionsSelectedRow = + SessionScreensPresenter.NormalizeSelection( + selection, + ProjectQuintPresentation.QuickOptionRowCount), + HandleQuickOptionsUi, + selection => _pauseSelection = + SessionScreensPresenter.NormalizeSelection(selection, 3), + ActivatePauseAction, + selection => _gameOverSelection = + SessionScreensPresenter.NormalizeSelection(selection, 2), + ActivateGameOverAction, + selection => _resultsSelection = + SessionScreensPresenter.NormalizeSelection(selection, 2), + ActivateResultsAction, + selection => _courseResultSelection = + SessionScreensPresenter.NormalizeSelection(selection, 2), + ActivateCourseResultAction, + HandleRetainedErrorRetry, + () => _mode = AppMode.Home)); + _fpsCounterPresenter = new FpsCounterPresenter(gameObject); + _gameplayPlayfieldPresenter = new GameplayPlayfieldPresenter(gameObject); + _gameplayHudPresenter = new GameplayHudPresenter(gameObject); + ProductIdentityStorageMigrationResult productStorageMigration; + try + { + productStorageMigration = ProductIdentityStorageMigrator.MigrateLegacyDefaultCompany(); + } + catch (Exception exception) + { + _flow.ShowError( + "DATA MIGRATION FAILED", + "ProjectQuint could not move the existing DefaultCompany data into Team Denia storage.\n\n" + + exception.Message); + Debug.LogException(exception); + return; + } + if (productStorageMigration.Status == ProductIdentityStorageMigrationStatus.DestinationConflict) + { + _flow.ShowError( + "DATA MIGRATION NEEDS ATTENTION", + "Both the legacy and Team Denia data locations contain files. Nothing was overwritten.\n\n" + + $"LEGACY: {productStorageMigration.SourcePath}\n" + + $"CURRENT: {productStorageMigration.DestinationPath}"); + Debug.LogError( + "[ProjectQuint Persistence] Product identity migration stopped because both roots contain data. " + + $"Legacy='{productStorageMigration.SourcePath}', Current='{productStorageMigration.DestinationPath}'."); + return; + } + if (productStorageMigration.Status == ProductIdentityStorageMigrationStatus.Migrated) + { + Debug.Log( + "[ProjectQuint Persistence] Moved the complete legacy product data root from " + + $"'{productStorageMigration.SourcePath}' to '{productStorageMigration.DestinationPath}'."); + } + var profileStorageMigration = ProfileStorageMigrator.MigrateDefaultProfile(); + if (profileStorageMigration.CopiedFileCount > 0) + { + Debug.Log( + $"[ProjectQuint Persistence] Migrated {profileStorageMigration.CopiedFileCount} " + + $"legacy profile files into '{PersistencePaths.DefaultProfileRoot}'. " + + "The original global files were preserved."); + } + _progressionRepository = PlayerProgressionRepository.Shared; + _profileArchiveService = new ProfileArchiveService(); + _profileImageStore = new ProfileImageStore(); + LoadProfileImage(); + _playCommitCoordinator = new PlayResultCommitCoordinator( + progression: _progressionRepository); + RefreshProfileSummary(); + _gameplaySession = new GameplaySessionController(); + _gameplaySession.Judged += OnJudged; + _gameplaySession.AutoPlayNotePressed += OnAutoPlayNotePressed; + _settingsRepository = new GameSettingsRepository(); + _favoriteRepository = new FavoriteChartRepository(); + _settings = _settingsRepository.Load(); + _nativeInputPoller = new MacNativeKeyboardPoller(); + ApplyInputPollingRate(_inputPollingRate, startup: true); + _audioDspBufferController = new AudioDspBufferController(_audioDspBufferProfile); + ApplyAudioDspBufferProfile(_audioDspBufferProfile, startup: true); + ConfigureAudioBackend(Environment.GetCommandLineArgs()); + RestoreActiveDeviceAudioCalibration(); + RefreshPremixCacheStatus(); + _packRegistry = new BmsPackRegistry(); + _packRegistry.MigrateLegacyFolder(_settings.BmsLibraryRoot); + _activePack = _packRegistry.ActivePack; + _difficultyTableRegistry = new BmsDifficultyTableRegistry(); + var migratedTableCount = _difficultyTableRegistry.MigrateLegacyPackTables( + _packRegistry.Packs, + _packRegistry.ActivePackId); + var promotedPackAssignmentCount = + _packRegistry.PromoteLegacyDifficultyTableAssignments(_difficultyTableRegistry); + var migratedGlobalAssignmentCount = 0; + if (_activePack != null && + string.IsNullOrWhiteSpace(_activePack.DifficultyTableId) && + _difficultyTableRegistry.ActiveTable != null) + { + _packRegistry.AssignDifficultyTable( + _activePack.Id, + _difficultyTableRegistry.ActiveTable.Id); + migratedGlobalAssignmentCount = 1; + } + // ActiveTableId was the schema-v1 global selection. Pack-scoped assignments are + // now authoritative, so clear the legacy singleton after its one-time promotion. + _difficultyTableRegistry.ClearSelection(); + var legacyProviderMap = _difficultyTableRegistry.BuildLegacyProviderMap(); + var migratedSongProviderCount = SongRecordRepository.Shared + .MigrateDifficultyTableProviders(legacyProviderMap); + var migratedCourseProviderCount = CourseRecordRepository.Shared + .MigrateDifficultyTableProviders(legacyProviderMap); + var clearedLegacyTableCount = _packRegistry.ClearLegacyDifficultyTableUrls(); + _activeDifficultyTable = ResolvePackDifficultyTable(_activePack); + if (migratedSongProviderCount > 0 || migratedCourseProviderCount > 0) + RefreshProfileSummary(); + if (migratedTableCount > 0 || promotedPackAssignmentCount > 0 || + migratedGlobalAssignmentCount > 0 || clearedLegacyTableCount > 0 || + migratedSongProviderCount > 0 || migratedCourseProviderCount > 0) + { + Debug.Log( + $"[ProjectQuint Library] Promoted {migratedTableCount:N0} legacy pack table URLs " + + $"into the global registry, assigned {promotedPackAssignmentCount + migratedGlobalAssignmentCount:N0} " + + $"pack table references, migrated {migratedSongProviderCount:N0} song records and " + + $"{migratedCourseProviderCount:N0} course records, then cleared " + + $"{clearedLegacyTableCount:N0} pack links."); + } + _libraryController = new LibraryController(); + PersistSettings(); + ApplyMasterVolume(); + ApplyFrameLimit(); +#if UNITY_STANDALONE_OSX && !UNITY_EDITOR + ApplyResolution(); + StartCoroutine(ConfirmStandaloneDisplaySettings()); +#endif + RefreshLaneKeyLabels(); + _startupAttributionEnteredRealtime = Time.realtimeSinceStartupAsDouble; + _mode = AppMode.StartupAttribution; + StartCoroutine(CompleteStartupAttribution()); + _status = !string.IsNullOrWhiteSpace(_audioBackendInitializationError) + ? $"AUDIO BACKEND UNAVAILABLE // {_audioBackendInitializationError}" + : _packRegistry.Packs.Count == 0 + ? "NO BMS PACKS // OPEN PLAY TO ADD ONE" + : $"{_packRegistry.Packs.Count:N0} BMS PACKS REGISTERED"; + + if (!StandaloneProfileOptions.TryParse( + Environment.GetCommandLineArgs(), + out var profileOptions, + out var profileError)) + { + Debug.LogError($"[ProjectQuint Profile Automation] {profileError}"); + Application.Quit(64); + } + else if (profileOptions.Enabled) + { + _gameplayDiagnosticsEnabled = true; + StartCoroutine(RunStandaloneProfile(profileOptions)); + } + } + + private IEnumerator CompleteStartupAttribution() + { + while (!StartupAttributionTimeline.IsComplete( + Time.realtimeSinceStartupAsDouble - _startupAttributionEnteredRealtime)) + { + yield return null; + } + + if (_mode != AppMode.StartupAttribution) + yield break; + + _teamDeniaBrandingEnteredRealtime = Time.realtimeSinceStartupAsDouble; + _mode = AppMode.TeamDeniaBranding; + while (!TeamDeniaBrandingTimeline.IsComplete( + Time.realtimeSinceStartupAsDouble - _teamDeniaBrandingEnteredRealtime)) + { + yield return null; + } + + if (_mode == AppMode.TeamDeniaBranding) + _mode = AppMode.Home; + } + + private IEnumerator RunStandaloneProfile(StandaloneProfileOptions options) + { + while (_mode == AppMode.StartupAttribution || _mode == AppMode.TeamDeniaBranding) + yield return null; + + string chartPath; + try + { + chartPath = Path.GetFullPath(options.ChartPath); + } + catch (Exception exception) + { + Debug.LogError($"[ProjectQuint Profile Automation] Invalid chart path: {exception.Message}"); + Application.Quit(66); + yield break; + } + + if (!File.Exists(chartPath)) + { + Debug.LogError($"[ProjectQuint Profile Automation] Chart not found: {chartPath}"); + Application.Quit(66); + yield break; + } + + var chartDirectory = Path.GetDirectoryName(chartPath); + var scanner = new SongLibraryScanner(packId: "standalone-profile"); + var scanTask = Task.Run(() => scanner.Scan(chartDirectory, CancellationToken.None)); + while (!scanTask.IsCompleted) + yield return null; + + if (scanTask.IsFaulted) + { + Debug.LogError( + $"[ProjectQuint Profile Automation] Chart scan failed: " + + $"{scanTask.Exception?.GetBaseException().Message}"); + Application.Quit(70); + yield break; + } + + var entry = scanTask.Result.FirstOrDefault(candidate => string.Equals( + Path.GetFullPath(candidate.ChartPath), + chartPath, + StringComparison.OrdinalIgnoreCase)); + if (entry == null) + { + Debug.LogError($"[ProjectQuint Profile Automation] Chart was not indexed: {chartPath}"); + Application.Quit(65); + yield break; + } + + _courseSession.ClearRun(); + _mode = AppMode.Library; + var originalGaugeType = _gaugeType; + var originalVSyncEnabled = _vSyncEnabled; + var originalBgaPlaybackMode = _bgaPlaybackMode; + var originalWindowMode = _windowMode; + _gaugeType = GaugeMode.Normal; + if (options.VSyncEnabled.HasValue) + _vSyncEnabled = options.VSyncEnabled.Value; + if (options.BgaPlayback.HasValue) + _bgaPlaybackMode = options.BgaPlayback.Value; + if (options.WindowMode.HasValue) + _windowMode = options.WindowMode.Value; + ApplyFrameLimit(); + ApplyResolution(); + yield return null; + Debug.Log( + $"[ProjectQuint Profile Automation] Loading '{entry.DisplayTitle}' for " + + $"{options.DurationSeconds:0.###} seconds with Normal gauge, " + + $"VSync {(_vSyncEnabled ? "ON" : "OFF")}, BGA {_bgaPlaybackMode}, " + + $"window {_windowMode} from {chartPath}"); + yield return LoadSong(entry); + if (_mode != AppMode.Gameplay) + { + _gaugeType = originalGaugeType; + _vSyncEnabled = originalVSyncEnabled; + _bgaPlaybackMode = originalBgaPlaybackMode; + _windowMode = originalWindowMode; + ApplyFrameLimit(); + ApplyResolution(); + Debug.LogError( + $"[ProjectQuint Profile Automation] Gameplay did not start; final mode {_mode}."); + Application.Quit(70); + yield break; + } + + var startedAt = Time.realtimeSinceStartupAsDouble; + while (_mode == AppMode.Gameplay && + Time.realtimeSinceStartupAsDouble - startedAt < options.DurationSeconds) + yield return null; + + var elapsed = Time.realtimeSinceStartupAsDouble - startedAt; + Debug.Log( + $"[ProjectQuint Profile Automation] Completed {elapsed:0.000} seconds; " + + $"song time {_frameSongTime:0.000}s, final mode {_mode}. Exiting without saving a play record."); + DisableLaneInput(); + _audio?.Stop(); + _bga?.Stop(); + _gaugeType = originalGaugeType; + _vSyncEnabled = originalVSyncEnabled; + _bgaPlaybackMode = originalBgaPlaybackMode; + _windowMode = originalWindowMode; + ApplyFrameLimit(); + ApplyResolution(); + yield return null; + Application.Quit(0); + } + +#if UNITY_EDITOR + /// + /// Opens the real song-select presentation with an indexed pack for deterministic visual QA. + /// This helper is excluded from standalone builds and never changes the user's pack registry. + /// + public void EditorOpenChartFeaturePreview( + string libraryRoot, + string cachePath, + string preferredChartFileName) + { + var scanner = new SongLibraryScanner(cachePath); + var songs = scanner.Scan(libraryRoot, CancellationToken.None); + _favoriteRepository.Apply(songs); + foreach (var song in songs) + { + song.ClearLamp = SongClearLampStore.Load(song); + song.BestRecord = SongPlayRecordStore.Load(song); + } + _libraryController.ReplaceSongs(songs); + var selectedIndex = songs.FindIndex(song => string.Equals( + Path.GetFileName(song.ChartPath), + preferredChartFileName, + StringComparison.OrdinalIgnoreCase)); + // Music Select reserves visual row zero for RANDOM SELECT. + _libraryController.State.SetSelectedIndex(Math.Max(0, selectedIndex + 1)); + _libraryRoot = libraryRoot; + _scanner = scanner; + _libraryReady = true; + _libraryScanRunning = false; + _status = $"VISUAL QA // {scanner.CacheHits:N0} CACHED // {scanner.Failed:N0} FAILED"; + _mode = AppMode.Library; + RefreshLibraryThumbnail(); + } + + public void EditorMoveLibrarySelection(int delta) + { + if (_mode != AppMode.Library || + _libraryController?.State == null || + !_libraryController.State.MoveSelection(delta)) + return; + RefreshLibraryThumbnail(); + } + + public void EditorOpenLibraryControlPreview(bool filter) + { + if (_mode != AppMode.Library || _libraryController?.State == null) + return; + _libraryController.State.CloseSortPanel(); + _libraryController.State.CloseLevelFilterPanel(); + if (filter) + _libraryController.State.ToggleLevelFilterPanel(); + else + _libraryController.State.ToggleSortPanel(); + } + + public void EditorOpenQuickOptionsPreview() + { + _flow.BeginQuickOptions(); + _flow.QuickOptionsSelectedRow = 2; + } + + public void EditorOpenAudioOptionsPreview() + { + _audioDspBufferController?.RefreshSnapshot(); + RefreshAudioOutputDevices(); + RefreshPremixCacheStatus(); + _flow.BeginOptions(); + _optionsCategory = OptionsCategory.Audio; + _optionsSelectedRow = 1; + } + + public void EditorOpenOptionsPreview(OptionsCategory category, int keyCount = 0) + { + _flow.BeginOptions(); + if (keyCount > 0) + { + _optionsInputKeyCount = GameSettings.NormalizeInputKeyCount(keyCount); + RefreshLaneKeyLabels(); + } + _optionsCategory = ProjectQuintPresentation.CycleOptionsCategory(category, 0); + _optionsSelectedRow = 0; + } + + public void EditorOpenAudioSyncCalibrationPreview() + { + EditorOpenOptionsPreview(OptionsCategory.Latency); + _optionsSelectedRow = 3; + OpenAudioSyncCalibration(); + } + + public void EditorStartAudioSyncCalibrationPreview() + { + EditorOpenAudioSyncCalibrationPreview(); + StartAudioSyncCalibration(); + } + + public void EditorOpenGameplayOptionsPreview(int keyCount = 0) + { + EditorOpenOptionsPreview(OptionsCategory.Gameplay); + if (keyCount > 0) + _optionsInputKeyCount = GameSettings.NormalizeInputKeyCount(keyCount); + } + + public void EditorPressOptionsPreviewLane(int lane) + { + _optionsProfileCoursePresenter?.PressOptionsPreviewLane(lane); + } + + /// + /// Opens the real retained Results presenter with an editor-only deterministic + /// judgement fixture. It performs no persistence and is excluded from players. + /// + public void EditorOpenResultsPreview() + { + var chart = new PlayableChart + { + Metadata = new PlayableChartMetadata + { + Title = "Apocalypse Nemesis -Meteor Noir-", + Artist = "kanone", + DifficultyLabel = "4UE20", + InitialTempo = 256, + MinimumTempo = 64, + MaximumTempo = 256, + KeyCount = 4, + }, + }; + for (var index = 0; index < 180; index++) + { + chart.Notes.Add(new PlayableNote + { + Id = index + 1, + Lane = index % 4, + StartTime = 1.0 + index * 0.08, + }); + } + + _gameplaySession.LoadChart(chart); + _gameplaySession.Start( + new GameplaySessionStartRequest( + GaugeMode.Normal, + restoredGaugeValue: 100.0), + new GameplayClockSample( + currentDspTime: 0.0, + startDspTime: 0.0, + currentRealtime: 0.0, + audioOutputLatencyMilliseconds: 0)); + var judgement = _gameplaySession.Judgement; + for (var index = 0; index < chart.Notes.Count; index++) + { + var note = chart.Notes[index]; + if (index % 41 == 0) + continue; + var offset = index % 31 == 0 + ? -0.080 + : index % 23 == 0 + ? -0.060 + : index % 17 == 0 + ? -0.040 + : index % 29 == 0 + ? 0.075 + : index % 13 == 0 + ? 0.052 + : index % 7 == 0 + ? 0.031 + : index % 2 == 0 ? -0.012 : 0.006; + judgement.Press(note.Lane, note.StartTime + offset); + } + judgement.Update(chart.Notes[chart.Notes.Count - 1].StartTime + 1.0); + + var previousRecord = new SongPlayRecord + { + HasRecord = true, + BestScore = 940_152, + BestAccuracyPercent = 94.20, + BestMaxCombo = 800, + HasBestScoreJudgementBreakdown = true, + BestScoreKoolCount = 142, + BestScoreCoolCount = 24, + BestScoreGoodCount = 9, + BestScoreMissCount = 5, + BestScorePurePerfectCount = 128, + PlayCount = 9, + }; + var previousProfile = new ProfileProgressSnapshot( + "PLAYER", 12_540, 24, 100, "4ue", "4UE", 4, false); + var currentProfile = new ProfileProgressSnapshot( + "PLAYER", 12_720, 25, 280, "4ue", "4UE", 4, false); + var profileUpdate = new ProfileProgressUpdate( + true, + 180, + previousProfile, + currentProfile, + firstPlay: false, + firstNormalClear: false, + firstHardClear: false, + firstFullCombo: false, + firstAllPure: false); + var previousRating = new RatingSummary("4ue", "4UE", 4, false, 14.485, 29); + var currentRating = new RatingSummary("4ue", "4UE", 4, false, 15.054, 30); + var ratingUpdate = new RatingUpdate(true, 17.061, previousRating, currentRating); + + _libraryThumbnail = null; + _stageTexture = null; + _profileSnapshot = currentProfile; + _resultSession.BeginPlayback(); + _resultSession.SetCommitOutcome(new PlayCommitOutcome(profileUpdate, ratingUpdate)); + _resultSession.BeginResults( + previousRecord, + songCleared: true, + Time.realtimeSinceStartupAsDouble - 2.0); + _mode = AppMode.Results; + } + + /// + /// Opens retained surfaces directly for deterministic visual QA. This changes + /// only the editor Play Mode session and never persists profile or pack state. + /// + public void EditorOpenVisualQaSurface(string surface) + { + var key = (surface ?? string.Empty).Trim().ToLowerInvariant(); + var selectedSong = _libraryController?.State?.SelectedSong; + _updateModalOpen = false; + _updateOfferPending = false; + _profileModalOpen = false; + _packCacheManagerOpen = false; + _pinnedPackCacheConfirmationPending = false; + _packRemovalConfirmationPending = false; + CloseAudioSyncCalibration(); + _libraryController?.State?.CloseSortPanel(); + _libraryController?.State?.CloseLevelFilterPanel(); + switch (key) + { + case "startup-attribution": + _startupAttributionEnteredRealtime = + Time.realtimeSinceStartupAsDouble - StartupAttributionTimeline.FadeInSeconds; + _mode = AppMode.StartupAttribution; + break; + case "team-denia-branding": + _teamDeniaBrandingEnteredRealtime = + Time.realtimeSinceStartupAsDouble - TeamDeniaBrandingTimeline.FadeInSeconds; + _mode = AppMode.TeamDeniaBranding; + break; + case "update": + _availableUpdate = new ProjectQuintUpdateManifest + { + schemaVersion = 1, + channel = ProjectQuintReleaseVersion.ReleaseChannel, + available = true, + version = "0.1.0-alpha.3", + buildNumber = 3, + publishedAt = DateTimeOffset.UtcNow.ToString("O"), + downloadUrl = + "https://projectquint.duckdns.org/downloads/0.1.0-alpha.3/" + + "ProjectQuint-0.1.0-alpha.3-macOS-universal.dmg", + notesUrl = "/changelog/", + sha256 = new string('a', 64), + sizeBytes = 78_000_000, + }; + _updateModalSelection = 0; + _updateOfferPending = false; + _updateModalOpen = true; + _mode = AppMode.Home; + break; + case "pack": + _packSelection = ResolveActivePackSelection(); + _mode = AppMode.PackSelect; + break; + case "pack-remove": + _packSelection = ResolveActivePackSelection(); + _mode = AppMode.PackSelect; + RequestPackRemoval(); + break; + case "tables": + _packSelection = ResolveActivePackSelection(); + _mode = AppMode.PackSelect; + OpenDifficultyTableSetup(); + break; + case "course": + ConfigureEditorCourseVisualQa(selectedSong); + OpenCourseSelect(); + break; + case "profile": + case "profile-overview": + _mode = AppMode.Library; + OpenProfileModal(0); + break; + case "profile-rating": + _mode = AppMode.Library; + OpenProfileModal(1); + break; + case "pack-cache": + EditorOpenOptionsPreview(OptionsCategory.Data); + OpenPackCacheManager(); + break; + case "pack-cache-confirm": + EditorOpenOptionsPreview(OptionsCategory.Data); + OpenPackCacheManager(); + _pinnedPackCacheConfirmationPending = true; + break; + case "audio-sync": + EditorOpenAudioSyncCalibrationPreview(); + break; + case "loading": + _loadingEntry = selectedSong; + _flow.BeginLoading( + "DECODING AUDIO", + "MIXING KEY-SOUND BANK", + Math.Max(0.0, Time.realtimeSinceStartupAsDouble - 1.0)); + _flow.ReportLoading("MIXING KEY-SOUND BANK", 386, 1234); + break; + case "pause": + _playingEntry = selectedSong; + _flow.BeginPause(); + break; + case "game-over": + _flow.BeginGameOver(); + break; + case "course-results": + ConfigureEditorCourseResultsVisualQa(selectedSong); + _mode = AppMode.CourseResults; + break; + case "error": + _flow.ShowError( + "AUDIO MIX FAILED", + "The selected chart contains an audio stream that could not be prepared."); + break; + default: + throw new ArgumentOutOfRangeException( + nameof(surface), + surface, + "Unknown editor visual QA surface."); + } + } + + private void ConfigureEditorCourseVisualQa(SongLibraryEntry selectedSong) + { + if (selectedSong == null) + return; + + var bundledCourseHeader = Resources.Load("ProjectQuint/Data/AeryHeader"); + var preview = AeryCourseIndex.Parse( + "aery", + "AERY", + bundledCourseHeader != null ? bundledCourseHeader.text : string.Empty); + if (preview?.Courses == null || preview.Courses.Count == 0) + return; + + var previewSongs = (_libraryController?.State?.AllSongs ?? + Array.Empty()) + .Where(song => song != null) + .GroupBy( + song => song.DisplayTitle ?? string.Empty, + StringComparer.OrdinalIgnoreCase) + .Select(group => group.First()) + .Take(12) + .ToArray(); + if (previewSongs.Length == 0) + previewSongs = new[] { selectedSong }; + + for (var index = 0; index < preview.Courses.Count; index++) + { + var course = preview.Courses[index]; + course.Stages = Enumerable.Range(0, course.StageCount) + .Select(stage => previewSongs[(index + stage) % previewSongs.Length]) + .ToArray(); + course.ResolvedKeyCount = 5; + course.ResolvedHasScratch = + course.Stages.Any(stage => stage?.Metadata?.HasScratch == true); + course.StageCompatibilityError = string.Empty; + course.Record = index switch + { + 3 => new AeryCourseRecord + { + HasRecord = true, + BestStageReached = 3, + BestScore = 2_845_320, + BestExScoreRate = 76.84, + BestMissRate = 4.12, + PlayCount = 2, + }, + 5 => new AeryCourseRecord + { + HasRecord = true, + Cleared = true, + BestStageReached = course.StageCount, + BestScore = 3_721_480, + BestExScoreRate = 92.37, + BestMissRate = 1.24, + BestTrophyName = "SILVER", + PlayCount = 5, + }, + _ => new AeryCourseRecord(), + }; + } + + _courseIndex = preview; + _courseSession.Select(5, preview.Courses.Count); + _courseStatus = + "UP / DOWN SELECT // ENTER START // M MIRROR // 1 / 2 SPEED"; + } + + private void ConfigureEditorCourseResultsVisualQa(SongLibraryEntry selectedSong) + { + ConfigureEditorCourseVisualQa(selectedSong); + var course = _courseIndex?.Courses? + .FirstOrDefault(candidate => candidate?.IsPlayable == true && + candidate.StageCount == 4) ?? + _courseIndex?.Courses? + .FirstOrDefault(candidate => candidate?.IsPlayable == true); + if (course == null) + return; + + // Build a deterministic completed run without loading audio or waiting for four + // charts. This is editor-only visual QA data and never reaches persistence. + _courseSession.Begin(course); + const int notesPerStage = 64; + for (var stageIndex = 0; stageIndex < course.StageCount; stageIndex++) + { + if (!_courseSession.TryBeginStage(stageIndex, out _)) + break; + + var notes = Enumerable.Range(0, notesPerStage) + .Select(index => new PlayableNote + { + Id = stageIndex * notesPerStage + index + 1, + Lane = index % 5, + StartTime = 1.0 + index * 0.25, + }) + .ToArray(); + var judgement = new RhythmJudgementEngine(notes, laneCount: 5); + for (var index = 0; index < notesPerStage - 1; index++) + { + var offset = index >= notesPerStage - 3 + ? (index % 2 == 0 ? -0.100 : 0.100) + : index >= notesPerStage - 8 + ? (index % 2 == 0 ? -0.050 : 0.050) + : 0.0; + judgement.Press( + notes[index].Lane, + notes[index].StartTime + offset); + } + judgement.Update(notes[notesPerStage - 1].StartTime + 0.2); + _courseSession.TryAccumulateStage(judgement, cleared: true); + } + + _courseSession.CaptureGauge(63.4); + _courseSession.Complete( + passed: true, + modifier: LaneModifier.Off.ToString().ToUpperInvariant()); + } +#endif + +#if UNITY_STANDALONE_OSX && !UNITY_EDITOR + private IEnumerator ConfirmStandaloneDisplaySettings() + { + // macOS can restore the previous player window state during startup. + // Apply the requested borderless mode once more after that restore pass. + yield return null; + ApplyResolution(); + } +#endif + + private void Update() + { + ReapplyFrameLimitWhenDisplayRefreshRateChanges(); + UpdateFpsSample(); + HandleExternalAudioConfigurationChange(); + TryStartAutomaticUpdateCheck(); + TryPresentPendingUpdate(); + if (_updateModalOpen) + { + UpdateUpdateNoticeInput(); + FlushPendingLibraryViewState(force: false); + return; + } + switch (_mode) + { + case AppMode.StartupAttribution: + // Required middleware attribution is intentionally not skippable. + break; + case AppMode.TeamDeniaBranding: + // Studio branding follows FMOD and remains intentionally input-free. + break; + case AppMode.Home: + UpdateHomeInput(); + break; + case AppMode.PackSelect: + UpdatePackSelectInput(); + break; + case AppMode.PackImport: + UpdatePackImportInput(); + break; + case AppMode.DifficultyTableSetup: + UpdateDifficultyTableSetupInput(); + break; + case AppMode.Options: + UpdateAudioSyncCalibration(); + UpdateOptionsInput(); + break; + case AppMode.Library: + UpdateLibraryInput(); + break; + case AppMode.CourseSelect: + UpdateCourseSelectInput(); + break; + case AppMode.QuickOptions: + UpdateQuickOptionsInput(); + break; + case AppMode.Loading: + UpdateLoadingInput(); + break; + case AppMode.Gameplay: + UpdateGameplay(); + break; + case AppMode.Paused: + UpdatePauseInput(); + break; + case AppMode.GameOver: + UpdateGameOverInput(); + break; + case AppMode.Results: + UpdateResultsInput(); + break; + case AppMode.CourseResults: + UpdateCourseResultsInput(); + break; + case AppMode.Error: + if (Keyboard.current?.f5Key.wasPressedThisFrame == true) + StartCoroutine(InitializeLibrary(true)); + else if (Keyboard.current?.escapeKey.wasPressedThisFrame == true) + _mode = AppMode.Home; + break; + } + FlushPendingLibraryViewState(force: false); + } + + private void LateUpdate() + { + var startupElapsed = _mode == AppMode.StartupAttribution + ? Time.realtimeSinceStartupAsDouble - _startupAttributionEnteredRealtime + : Time.realtimeSinceStartupAsDouble - _teamDeniaBrandingEnteredRealtime; + _startupBrandingPresenter?.Update(_mode, startupElapsed); + + var homeVisible = _mode == AppMode.Home && !_profileModalOpen; + _homeScreenPresenter?.SetVisible(homeVisible); + _updateNoticePresenter?.SetVisible(_updateModalOpen); + if (_updateModalOpen && _availableUpdate != null) + { + _updateNoticePresenter?.Update( + _availableUpdate.version, + _availableUpdate.buildNumber, + _updateModalSelection, + _sparkleUpdater?.IsReady == true); + } + + UpdatePackMusicPresentation(); + UpdateOptionsProfileCoursePresentation(); + + var sessionVisible = + _mode == AppMode.PackImport || + _mode == AppMode.Loading || + _mode == AppMode.QuickOptions || + _mode == AppMode.Paused || + _mode == AppMode.GameOver || + _mode == AppMode.Results || + _mode == AppMode.CourseResults || + _mode == AppMode.Error; + _sessionScreensPresenter?.SetVisible(sessionVisible); + if (sessionVisible) + { + switch (_mode) + { + case AppMode.PackImport: + case AppMode.Loading: + _sessionScreensPresenter.UpdateLoading( + _loadingTitle, + _loadingDetail, + _loadingCurrent, + _loadingTotal, + Time.realtimeSinceStartupAsDouble - _loadingEnteredRealtime, + _loadingEntry ?? _playingEntry, + CurrentArtworkTexture); + break; + case AppMode.QuickOptions: + _sessionScreensPresenter.UpdateQuickOptions( + _autoPlayEnabled, + _autoScratchEnabled, + _gearPosition, + _noteSpeed, + _gaugeType, + _gaugeFallbackEnabled, + _laneModifier, + _gameplaySkin, + _noteSkin, + _longNoteStyle, + _bgaDisplayMode, + _laneSeparatorsEnabled, + _timingMeterEnabled, + _quickOptionsSelectedRow); + break; + case AppMode.Paused: + _sessionScreensPresenter.UpdatePause( + _pauseSelection, + _playingEntry, + CurrentArtworkTexture, + _frameSongTime, + _playingEntry?.DurationSeconds ?? _chart?.Duration ?? 0.0, + _noteSpeed, + _audioOutputLatencyMilliseconds); + break; + case AppMode.GameOver: + _sessionScreensPresenter.UpdateGameOver(_gameOverSelection, _gaugeType); + break; + case AppMode.Results: + _sessionScreensPresenter.UpdateResults( + _chart, + _judgement, + _gauge, + _songCleared, + _gameplaySession?.IsAutoPlay == true, + CurrentArtworkTexture, + _resultPreviousRecord, + _resultSession.ProfileUpdate, + _resultSession.RatingUpdate, + _resultSession.RecordSaveError, + _resultSession.PlayedUtcTicks, + _profileSnapshot, + _resultsSelection, + Math.Max(0.0, Time.realtimeSinceStartupAsDouble - _resultEnteredRealtime), + _gameplaySession?.IsAutoScratch == true); + break; + case AppMode.CourseResults: + _sessionScreensPresenter.UpdateCourseResults( + _activeCourse, + _courseProgress, + _courseGaugeValue, + _coursePassed, + _courseTrophy, + _courseBadgeEarned, + _profileSnapshot, + _courseResultSelection); + break; + case AppMode.Error: + _sessionScreensPresenter.UpdateError(_errorTitle, _errorDetail); + break; + } + } + + var gameplayVisible = + _mode == AppMode.Gameplay || + _mode == AppMode.Paused || + _mode == AppMode.GameOver; + _gameplayPlayfieldPresenter?.SetVisible(gameplayVisible); + _gameplayHudPresenter?.SetVisible(gameplayVisible); + if (gameplayVisible) + UpdateGameplayPresentation( + _mode == AppMode.Gameplay && !_resultSession.GameplayOutroActive); + + _fpsCounterPresenter?.Update( + _mode != AppMode.Home && + AppFlowController.ShouldShowFpsCounter(_mode, _showFpsCounter), + _displayedFps); + } + + private void UpdatePackMusicPresentation() + { + if (_packMusicScreenPresenter == null) + return; + + var screen = _mode switch + { + AppMode.PackSelect => PackMusicScreen.PackSelect, + AppMode.DifficultyTableSetup => PackMusicScreen.DifficultyTableSetup, + AppMode.Scanning => PackMusicScreen.Scanning, + AppMode.Library => PackMusicScreen.Library, + AppMode.QuickOptions => PackMusicScreen.Library, + _ => PackMusicScreen.None, + }; + _packMusicScreenPresenter.SetScreen(screen); + + switch (screen) + { + case PackMusicScreen.PackSelect: + { + var packs = _packRegistry?.Packs; + var selectedPack = packs != null && + _packSelection >= 0 && + _packSelection < packs.Count + ? packs[_packSelection] + : null; + var activePackId = _activePack?.Id ?? + _packRegistry?.ActivePack?.Id ?? + string.Empty; + var selectedIsActive = selectedPack != null && + string.Equals( + selectedPack.Id, + activePackId, + StringComparison.OrdinalIgnoreCase); + var selectedTableId = selectedPack?.DifficultyTableId ?? + string.Empty; + var selectedLibraryReady = selectedIsActive && _libraryReady; + var selectedTableDataAvailable = + selectedLibraryReady && + _activeCatalog != null && + string.Equals( + _activeCatalog.ProviderId, + selectedTableId, + StringComparison.OrdinalIgnoreCase); + _packMusicScreenPresenter.UpdatePackSelect( + packs, + _difficultyTableRegistry?.Tables, + selectedTableId, + _packSelection, + selectedLibraryReady + ? _libraryController?.State?.AllSongs?.Count ?? 0 + : -1, + selectedLibraryReady + ? _courseIndex?.Courses?.Count ?? 0 + : -1, + selectedLibraryReady && !string.IsNullOrWhiteSpace(selectedTableId) + ? _catalogMatchedCharts + : -1, + selectedTableDataAvailable, + _profileSnapshot, + _status, + _packKeyModeModalOpen, + _packKeyModeModalSelection, + _packRemovalConfirmationPending, + _pendingPackRemovalName); + break; + } + case PackMusicScreen.DifficultyTableSetup: + if (_tableUrlReleaseRequested) + { + _packMusicScreenPresenter.ReleaseTableUrlFocus(); + _tableUrlReleaseRequested = false; + } + _packMusicScreenPresenter.UpdateDifficultyTableSetup( + _difficultyTableRegistry?.Tables, + _tableSetupSelection, + ResolveDifficultyTableSetupPack()?.DifficultyTableId, + _tableUrlDraft, + _tableUrlFocusRequested, + _tableSetupStatus); + _tableUrlFocusRequested = false; + break; + case PackMusicScreen.Scanning: + case PackMusicScreen.Library: + { + var library = _libraryController?.State; + if (library == null) + break; + + var focusSearchRequested = + _mode == AppMode.Library && + _libraryController.ConsumeFocusSearchRequest(); + if (_libraryController.ConsumeReleaseSearchFocusRequest()) + _packMusicScreenPresenter.ReleaseSearchFocus(); + _packMusicScreenPresenter.UpdateLibrary( + library.VisibleSongs, + library.SelectedIndex, + _libraryRoot, + _packRegistry?.ActivePack?.DisplayName ?? _activePack?.DisplayName, + _status, + _scanner?.Completed ?? 0, + _scanner?.Total ?? 0, + _scanner?.Failed ?? 0, + library.AllSongs.Count, + _mode == AppMode.Scanning, + CurrentArtworkTexture, + library.SearchQuery, + focusSearchRequested, + library.LevelFilters, + library.SelectedLevelFilters, + library.LevelFilterPanelOpen, + library.LevelFilterCursorIndex, + library.SortPanelOpen, + library.SortCursorIndex, + library.SortMode, + library.FavoritesOnly, + SongLibraryView.SortLabel(library.SortMode), + _gearPosition, + _noteSpeed, + _gaugeType, + _laneModifier, + _gameplaySkin, + _bgaPlaybackMode, + _reactiveKeySoundsEnabled, + _profileSnapshot, + library.ViewRevision); + break; + } + } + } + + private void UpdateOptionsProfileCoursePresentation() + { + if (_optionsProfileCoursePresenter == null) + return; + + var optionsVisible = _mode == AppMode.Options; + var profileVisible = + _profileModalOpen && + (_mode == AppMode.Home || _mode == AppMode.Library); + var courseVisible = _mode == AppMode.CourseSelect; + _optionsProfileCoursePresenter.SetOptionsVisible(optionsVisible); + _optionsProfileCoursePresenter.SetProfileVisible(profileVisible); + _optionsProfileCoursePresenter.SetCourseVisible(courseVisible); + + if (optionsVisible) + { + _optionsProfileCoursePresenter.UpdateOptions( + _noteSpeed, + _judgementOffsetMilliseconds, + _audioOutputLatencyMilliseconds, + _masterVolumePercent, + _reactiveKeySoundsEnabled, + _audioDspBufferProfile, + CurrentAudioOutputDevice.Name, + CurrentAudioOutputDevice.DetailLabel, + _audioBackendFactory?.OutputConfiguration.AppliedLabel ?? string.Empty, + _audioDspStatus, + IsAudioSystemResetting, + _premixCacheStatus, + _premixCacheClearing, + _pinnedPackCacheStatus, + _pinnedPackCacheConfirmationPending, + _pinnedPackCacheWorking, + _packCacheManagerOpen, + _packCacheUiEntries, + _packCacheSelection, + _frameLimitMode, + _displayResolutionMode, + _windowMode, + _showFpsCounter, + _bgaPlaybackMode, + _bgaDisplayMode, + _vSyncEnabled, + _backgroundDimPercent, + _gameplaySkin, + _noteSkin, + _longNoteStyle, + _laneSeparatorsEnabled, + _optionsCategory, + _optionsSelectedRow, + _optionsInputKeyCount, + _inputPollingRate, + _inputPollingStatus, + _laneKeyLabels, + GameSettings.FormatLaneKeyLabel(_centerAuxKey), + GameSettings.FormatLaneKeyLabel(_settings.ScratchKey), + _rebindLane, + _profileTransferStatus, + _updateStatus, + _profileImportConfirmationPending, + _profileSnapshot, + BuildAudioSyncCalibrationView()); + } + + if (profileVisible) + { + _optionsProfileCoursePresenter.UpdateProfile( + _profileSnapshot, + _profileRatingSummary, + _profileCollection, + _profileCourses, + _profileModalTab, + _profileRatingContextIndex, + _ratingContexts.Count, + _profileBadgeSelection, + _profileCollectionColumn, + _profileCollectionSelections, + _profileRatingThumbnailSongs, + _profileRatingThumbnailSongsRevision); + } + + if (courseVisible) + { + _optionsProfileCoursePresenter.UpdateCourseSelect( + _courseIndex?.Courses, + _courseIndex?.ProviderDisplayName, + _courseSelectedIndex, + EffectiveCourseModifier, + _noteSpeed, + _courseStatus, + _profileSnapshot); + } + } + + private IEnumerator InitializeLibrary(bool openLibraryWhenReady) + { + _openLibraryWhenScanCompletes |= openLibraryWhenReady; + if (_libraryScanRunning) + { + if (openLibraryWhenReady) _mode = AppMode.Scanning; + yield break; + } + + _libraryScanRunning = true; + _libraryReady = false; + _libraryLoadError = string.Empty; + if (openLibraryWhenReady) _mode = AppMode.Scanning; + _libraryController.Reset(); + ClearLibraryThumbnail(); + _scanner = null; + var scanGeneration = ++_libraryScanGeneration; + _activePack = _packRegistry?.ActivePack; + _activeDifficultyTable = ResolvePackDifficultyTable(_activePack); + if (_activePack == null) + { + _libraryScanRunning = false; + _openLibraryWhenScanCompletes = false; + _status = "NO BMS PACK SELECTED"; + _mode = AppMode.PackSelect; + yield break; + } + + _libraryRoot = _activePack.ContentRoot; + _status = $"Opening {_activePack.DisplayName}..."; + if (!_activePack.IsAvailable) + { + FailLibraryScan( + "BMS PACK SOURCE MISSING", + $"The registered pack folder is unavailable:\n{_libraryRoot}\n\nReturn to pack selection and add the pack again."); + yield break; + } + + var isAeryTable = IsAeryTable(_activeDifficultyTable?.HeaderUrl); + var cachePath = _activeDifficultyTable == null + ? string.Empty + : _difficultyTableRegistry.DataCachePathFor(_activeDifficultyTable); + var courseCachePath = _activeDifficultyTable == null + ? string.Empty + : _difficultyTableRegistry.HeaderCachePathFor(_activeDifficultyTable); + var bundledTable = Resources.Load("ProjectQuint/Data/AeryData"); + var bundledCourseHeader = Resources.Load("ProjectQuint/Data/AeryHeader"); + ExternalDataOrigin catalogOrigin; + string catalogStatus; + string courseLocalStatus; + if (isAeryTable) + { + var tableResult = AeryTableIndex.LoadLocal( + cachePath, + bundledTable != null ? bundledTable.text : string.Empty); + _aeryCatalog = tableResult.Data; + _activeCatalog = _aeryCatalog; + catalogOrigin = tableResult.Origin; + catalogStatus = tableResult.Status; + + var courseResult = AeryCourseIndex.LoadLocal( + courseCachePath, + bundledCourseHeader != null ? bundledCourseHeader.text : string.Empty); + _courseIndex = courseResult.Data; + _courseStatus = courseResult.Status; + courseLocalStatus = courseResult.Status; + } + else + { + _aeryCatalog = null; + var localTable = _activeDifficultyTable == null + ? null + : BmsDifficultyTableIndex.LoadLocal( + _activeDifficultyTable.Id, + courseCachePath, + cachePath); + _activeCatalog = localTable?.Data; + catalogOrigin = localTable?.Origin ?? ExternalDataOrigin.Unavailable; + catalogStatus = localTable?.Status ?? "No difficulty table configured"; + var localCourses = _activeDifficultyTable == null + ? null + : AeryCourseIndex.LoadTableLocal( + _activeDifficultyTable.Id, + localTable?.Data?.DisplayName ?? _activeDifficultyTable.DisplayName, + courseCachePath); + _courseIndex = localCourses?.Data; + _courseStatus = localCourses?.Status ?? "NO BMSTABLE COURSE DATA CONFIGURED"; + courseLocalStatus = _courseStatus; + } + var bundledSearchAliases = Resources.Load("ProjectQuint/Data/SearchAliases"); + _searchAliases = SearchAliasIndex.Load( + bundledSearchAliases != null ? bundledSearchAliases.text : string.Empty, + PersistencePaths.SearchAliases); + + var libraryIndexCachePath = _packRegistry.CachePathFor(_activePack); + var legacyLibraryIndexCachePath = Path.Combine( + Application.persistentDataPath, + "LibraryIndex", + "song-index-v2.json"); + _scanner = new SongLibraryScanner( + libraryIndexCachePath, + legacyLibraryIndexCachePath, + _activePack.Id, + ResolvePackScanKeyCount(_activePack, _activeDifficultyTable)); + _scanCancellation?.Cancel(); + _scanCancellation?.Dispose(); + _scanCancellation = new CancellationTokenSource(); + var token = _scanCancellation.Token; + var scanTask = Task.Run(() => _scanner.Scan(_libraryRoot, token), token); + + while (!scanTask.IsCompleted) + { + _status = $"Scanning {_activePack.DisplayName} // {(_activeCatalog?.Count ?? 0):N0} table entries // " + + $"{_scanner.CacheHits:N0} cached // {_scanner.CacheMisses:N0} refreshed"; + yield return null; + } + + if (scanTask.IsCanceled) + { + _libraryScanRunning = false; + _libraryReady = false; + _openLibraryWhenScanCompletes = false; + _status = "LIBRARY SCAN CANCELLED"; + if (_mode == AppMode.Scanning) + _mode = AppMode.PackSelect; + yield break; + } + if (scanTask.IsFaulted) + { + FailLibraryScan("LIBRARY SCAN FAILED", scanTask.Exception?.GetBaseException().Message ?? "Unknown scan error"); + yield break; + } + + var scannedSongs = scanTask.Result; + _catalogMatchedCharts = ChartCatalogAnnotator.Apply(scannedSongs, _activeCatalog); + _aeryMatchedCharts = isAeryTable ? _catalogMatchedCharts : 0; + SongSearchIndexer.Rebuild(scannedSongs, _searchAliases); + _favoriteRepository.Apply(scannedSongs); + foreach (var song in scannedSongs) + { + song.ClearLamp = SongClearLampStore.Load(song); + song.BestRecord = SongPlayRecordStore.Load(song); + } + SongRecordRepository.Shared.EnrichChartMetadata(scannedSongs, _activePack?.DisplayName); + if (_playCommitCoordinator.TryResume(scannedSongs, out _)) + Debug.Log("[ProjectQuint Progression] Recovered the pending play result commit."); + RefreshProfileSummary(); + _courseIndex?.ResolveSongs(scannedSongs); + RefreshProfileSummary(); + if (_courseIndex != null) + { + var availableCourses = _courseIndex.Courses.Count(course => course.IsPlayable); + _courseStatus = $"{courseLocalStatus} // {_courseIndex.TableName} {_courseIndex.LastUpdate} // " + + $"{availableCourses}/{_courseIndex.Courses.Count} COURSES READY"; + } + if (scannedSongs.Count == 0) + { + FailLibraryScan("NO PLAYABLE CHARTS", $"No BMS/BME/BML files were indexed under:\n{_libraryRoot}"); + yield break; + } + + _libraryController.ReplaceSongs(scannedSongs); + _libraryController.RestoreViewState( + _activePack.SelectedLevelFilters, + (SongSortMode)_activePack.LibrarySortMode, + _activePack.LibraryFavoritesOnly, + _activePack.LastSelectedChartMd5); + var catalogName = _activeCatalog?.DisplayName ?? "BMS PLAYLEVEL"; + _status = $"{_activePack.DisplayName} // BMS {scannedSongs.Count:N0} // {catalogName} {_catalogMatchedCharts:N0} matched ({catalogOrigin}) // " + + $"{_scanner.CacheHits:N0} cached // {_scanner.CacheMisses:N0} refreshed"; + _libraryReady = true; + _libraryScanRunning = false; + RefreshLibraryThumbnail(); + if (_openLibraryWhenScanCompletes) _mode = AppMode.Library; + _openLibraryWhenScanCompletes = false; + if (isAeryTable) + { + StartCoroutine(RefreshAeryIntegration( + scanGeneration, + cachePath, + bundledTable != null ? bundledTable.text : string.Empty, + courseCachePath, + bundledCourseHeader != null ? bundledCourseHeader.text : string.Empty)); + } + else if (_activeDifficultyTable != null) + { + StartCoroutine(RefreshGenericDifficultyTable(scanGeneration, _activeDifficultyTable, _activePack)); + } + Debug.Log($"[ProjectQuint] Indexed {scannedSongs.Count} charts from {_libraryRoot}. Failed: {_scanner.Failed}. " + + $"Index cache: {_scanner.CacheStatus}. " + + $"Difficulty table: {catalogStatus}; {_catalogMatchedCharts} matched from {catalogOrigin}. " + + $"Default view: {SongLibraryView.AllLevelsFilter} " + + $"({_libraryController.State.VisibleSongs.Count} charts)."); + } + + private IEnumerator RefreshAeryIntegration( + int scanGeneration, + string tableCachePath, + string bundledTableJson, + string courseCachePath, + string bundledCourseJson) + { + ExternalDataResult tableResult = null; + yield return AeryTableIndex.LoadOnline( + tableCachePath, + bundledTableJson, + result => tableResult = result); + if (scanGeneration != _libraryScanGeneration || !_libraryReady) + yield break; + + if (tableResult?.Data != null && tableResult.Data.Count > 0) + { + _aeryCatalog = tableResult.Data; + _activeCatalog = _aeryCatalog; + _catalogMatchedCharts = ChartCatalogAnnotator.Apply( + _libraryController.State.AllSongs, + _aeryCatalog); + _aeryMatchedCharts = _catalogMatchedCharts; + SongSearchIndexer.Rebuild(_libraryController.State.AllSongs, _searchAliases); + _libraryController.RefreshCatalogMetadata(); + if (_mode == AppMode.Library || _mode == AppMode.QuickOptions) + RefreshLibraryThumbnail(); + } + + ExternalDataResult courseResult = null; + yield return AeryCourseIndex.LoadOnline( + courseCachePath, + bundledCourseJson, + result => courseResult = result); + if (scanGeneration != _libraryScanGeneration || !_libraryReady) + yield break; + + if (courseResult?.Data != null && courseResult.Data.Courses.Count > 0) + { + _courseIndex = courseResult.Data; + _courseIndex.ResolveSongs(_libraryController.State.AllSongs); + } + + var availableCourses = _courseIndex?.Courses.Count(course => course.IsPlayable) ?? 0; + var totalCourses = _courseIndex?.Courses.Count ?? 0; + _courseStatus = $"{courseResult?.Status ?? "AERY courses: unavailable"} // " + + $"{availableCourses}/{totalCourses} COURSES READY"; + _status = $"BMS {_libraryController.State.AllSongs.Count:N0} // " + + $"AERY {_aeryMatchedCharts:N0} matched ({_aeryCatalog?.Origin ?? ExternalDataOrigin.Unavailable}) // " + + $"{(_scanner?.CacheHits ?? 0):N0} cached // {(_scanner?.CacheMisses ?? 0):N0} refreshed"; + Debug.Log( + $"[ProjectQuint] Optional AERY refresh complete: " + + $"{_aeryMatchedCharts} chart matches from {tableResult?.Origin ?? ExternalDataOrigin.Unavailable}; " + + $"{availableCourses}/{totalCourses} courses from {courseResult?.Origin ?? ExternalDataOrigin.Unavailable}."); + } + + private IEnumerator RefreshGenericDifficultyTable( + int scanGeneration, + BmsDifficultyTableDefinition table, + BmsPackDefinition sourcePack) + { + ExternalDataResult result = null; + yield return BmsDifficultyTableIndex.LoadOnline( + table.Id, + table.HeaderUrl, + _difficultyTableRegistry.HeaderCachePathFor(table), + _difficultyTableRegistry.DataCachePathFor(table), + loaded => result = loaded); + if (scanGeneration != _libraryScanGeneration || !_libraryReady || + _activePack == null || !string.Equals(_activePack.Id, sourcePack.Id, StringComparison.OrdinalIgnoreCase) || + !string.Equals( + _activePack.DifficultyTableId, + table.Id, + StringComparison.OrdinalIgnoreCase)) + yield break; + + if (result?.Data != null && result.Data.Count > 0) + { + _activeCatalog = result.Data; + _difficultyTableRegistry.UpdateDisplayName(table.Id, result.Data.DisplayName); + _activeDifficultyTable = ResolvePackDifficultyTable(_activePack); + _catalogMatchedCharts = ChartCatalogAnnotator.Apply( + _libraryController.State.AllSongs, + _activeCatalog); + SongSearchIndexer.Rebuild(_libraryController.State.AllSongs, _searchAliases); + _libraryController.RefreshCatalogMetadata(); + if (_mode == AppMode.Library || _mode == AppMode.QuickOptions) + RefreshLibraryThumbnail(); + } + + var courseResult = AeryCourseIndex.LoadTableLocal( + table.Id, + result?.Data?.DisplayName ?? table.DisplayName, + _difficultyTableRegistry.HeaderCachePathFor(table)); + if (courseResult.Data != null) + { + _courseIndex = courseResult.Data; + _courseIndex.ResolveSongs(_libraryController.State.AllSongs); + } + var availableCourses = _courseIndex?.Courses.Count(course => course.IsPlayable) ?? 0; + var totalCourses = _courseIndex?.Courses.Count ?? 0; + _courseStatus = $"{courseResult.Status} // {availableCourses}/{totalCourses} COURSES READY"; + + _status = $"{sourcePack.DisplayName} // BMS {_libraryController.State.AllSongs.Count:N0} // " + + $"{_activeCatalog?.DisplayName ?? "TABLE"} {_catalogMatchedCharts:N0} matched " + + $"({result?.Origin ?? ExternalDataOrigin.Unavailable}) // " + + $"{(_scanner?.CacheHits ?? 0):N0} cached // {(_scanner?.CacheMisses ?? 0):N0} refreshed"; + Debug.Log($"[ProjectQuint] Difficulty table refresh: {result?.Status}; {_catalogMatchedCharts} chart matches; " + + $"{availableCourses}/{totalCourses} courses from {courseResult.Origin}."); + } + + private static bool IsAeryTable(string url) => + !string.IsNullOrWhiteSpace(url) && + url.IndexOf("/aery/", StringComparison.OrdinalIgnoreCase) >= 0; + + private void UpdateHomeInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (HandleProfileModalKeyboard(keyboard, true)) + return; + + _homeSelection = (int)HomeMenuAction.Play; + if (keyboard.escapeKey.wasPressedThisFrame) + { + ActivateHomeAction(HomeMenuAction.Quit); + return; + } + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + ActivateHomeAction(HomeMenuAction.Play); + } + + private void UpdateOptionsInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (_audioSyncCalibration != null || + !string.IsNullOrWhiteSpace(_audioSyncCalibrationError)) + { + if (keyboard.escapeKey.wasPressedThisFrame) + { + CloseAudioSyncCalibration(); + return; + } + if ((keyboard.enterKey.wasPressedThisFrame || + keyboard.numpadEnterKey.wasPressedThisFrame) && + _audioSyncCalibration?.Phase != AudioSyncCalibrationPhase.Running) + { + if (_audioSyncCalibration?.Phase == AudioSyncCalibrationPhase.Result && + _audioSyncCalibration.Result.CanApply) + ApplyAudioSyncCalibration(); + else + StartAudioSyncCalibration(); + } + return; + } + + if (_pinnedPackCacheConfirmationPending) + { + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + ConfirmPinnedPackCache(); + else if (keyboard.escapeKey.wasPressedThisFrame) + CancelPinnedPackCache(); + return; + } + if (_pinnedPackCacheWorking && keyboard.escapeKey.wasPressedThisFrame) + { + CancelPinnedPackCache(); + return; + } + + if (_packCacheManagerOpen) + { + var packCount = _packRegistry?.Packs.Count ?? 0; + if (keyboard.escapeKey.wasPressedThisFrame) + { + ClosePackCacheManager(); + return; + } + if (packCount > 0 && keyboard.upArrowKey.wasPressedThisFrame) + SetPackCacheSelection(ProjectQuintPresentation.CyclePackCacheSelection( + _packCacheSelection, -1, packCount)); + else if (packCount > 0 && keyboard.downArrowKey.wasPressedThisFrame) + SetPackCacheSelection(ProjectQuintPresentation.CyclePackCacheSelection( + _packCacheSelection, 1, packCount)); + else if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame || + keyboard.rKey.wasPressedThisFrame) + RequestPinnedPackCacheEstimate(); + else if (keyboard.deleteKey.wasPressedThisFrame || keyboard.backspaceKey.wasPressedThisFrame) + RequestDeleteSelectedPackCache(); + return; + } + + if (_rebindLane >= 0) + { + if (keyboard.escapeKey.wasPressedThisFrame) + { + _rebindLane = -1; + return; + } + + foreach (var keyControl in keyboard.allKeys) + { + if (!keyControl.wasPressedThisFrame || !GameSettings.IsAssignableLaneKey(keyControl.keyCode)) + continue; + + AssignInputBinding(_rebindLane, keyControl.keyCode); + _rebindLane = -1; + return; + } + return; + } + + if (_optionsCategory == OptionsCategory.Gameplay && + HandleOptionsPreviewLaneInput(keyboard)) + return; + + var categoryShortcut = PressedOptionsCategoryShortcut(keyboard); + if (categoryShortcut >= 0) + { + CommitContinuousOptionsIfNeeded(); + SetOptionsCategory((OptionsCategory)categoryShortcut); + return; + } + var categoryDelta = keyboard.leftShiftKey.wasPressedThisFrame ? -1 : + keyboard.rightShiftKey.wasPressedThisFrame ? 1 : 0; + if (categoryDelta != 0) + { + CommitContinuousOptionsIfNeeded(); + CycleOptionsCategory(categoryDelta); + return; + } + var selectionDelta = keyboard.upArrowKey.wasPressedThisFrame ? -1 : + keyboard.downArrowKey.wasPressedThisFrame ? 1 : 0; + if (selectionDelta != 0) + { + CommitContinuousOptionsIfNeeded(); + _optionsValueKeyRepeat.Reset(); + _optionsSelectedRow = ProjectQuintPresentation.CycleOptionsRow( + _optionsCategory, + _optionsSelectedRow, + selectionDelta, + _optionsInputKeyCount); + return; + } + var leftHeld = keyboard.leftArrowKey.isPressed; + var rightHeld = keyboard.rightArrowKey.isPressed; + var valueDirection = leftHeld == rightHeld ? 0 : leftHeld ? -1 : 1; + var valuePressedThisFrame = keyboard.leftArrowKey.wasPressedThisFrame || + keyboard.rightArrowKey.wasPressedThisFrame; + var continuousOption = IsSelectedOptionContinuous(); + var valueDelta = continuousOption + ? _optionsValueKeyRepeat.Update( + valueDirection, + valuePressedThisFrame, + Time.realtimeSinceStartupAsDouble) + : valuePressedThisFrame ? valueDirection : 0; + if (!continuousOption) + _optionsValueKeyRepeat.Reset(); + if (valueDirection == 0) + CommitContinuousOptionsIfNeeded(); + if (valueDelta != 0) + { + if (continuousOption) + ChangeSelectedContinuousOption(valueDelta); + else + ChangeSelectedOption(valueDelta); + return; + } + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + { + CommitContinuousOptionsIfNeeded(); + ActivateSelectedOption(); + return; + } + if (keyboard.escapeKey.wasPressedThisFrame) + { + CommitContinuousOptionsIfNeeded(); + if (_profileImportConfirmationPending) + { + _profileImportConfirmationPending = false; + _profileTransferStatus = "PROFILE IMPORT CANCELLED"; + return; + } + _rebindLane = -1; + CloseOptions(); + } + } + + private static int PressedOptionsCategoryShortcut(Keyboard keyboard) + { + if (keyboard.digit1Key.wasPressedThisFrame || keyboard.numpad1Key.wasPressedThisFrame) + return 0; + if (keyboard.digit2Key.wasPressedThisFrame || keyboard.numpad2Key.wasPressedThisFrame) + return 1; + if (keyboard.digit3Key.wasPressedThisFrame || keyboard.numpad3Key.wasPressedThisFrame) + return 2; + if (keyboard.digit4Key.wasPressedThisFrame || keyboard.numpad4Key.wasPressedThisFrame) + return 3; + if (keyboard.digit5Key.wasPressedThisFrame || keyboard.numpad5Key.wasPressedThisFrame) + return 4; + if (keyboard.digit6Key.wasPressedThisFrame || keyboard.numpad6Key.wasPressedThisFrame) + return 5; + return -1; + } + + private bool HandleOptionsPreviewLaneInput(Keyboard keyboard) + { + var keys = _settings.KeysFor(_optionsInputKeyCount); + var pressed = false; + for (var lane = 0; lane < keys.Length; lane++) + { + var key = keys[lane]; + if (!GameSettings.IsAssignableLaneKey(key)) + continue; + var control = keyboard[key]; + if (control == null) + continue; + if (control.wasPressedThisFrame) + { + _optionsProfileCoursePresenter?.PressOptionsPreviewLane(lane); + pressed = true; + } + if (control.wasReleasedThisFrame) + { + _optionsProfileCoursePresenter?.ReleaseOptionsPreviewLane(lane); + pressed = true; + } + } + return pressed; + } + + private void OpenOptions(AppMode returnMode) + { + _optionsReturnMode = returnMode == AppMode.Library ? AppMode.Library : AppMode.Home; + RefreshAudioOutputDevices(); + RefreshPremixCacheStatus(); + _flow.BeginOptions(); + } + + private void CloseOptions() + { + CloseAudioSyncCalibration(); + _rebindLane = -1; + _profileImportConfirmationPending = false; + _mode = _optionsReturnMode; + _optionsReturnMode = AppMode.Home; + } + + private void HandleOptionsUi(OptionsUiResult result) + { + if (result.SelectedCategory >= 0) + SetOptionsCategory((OptionsCategory)result.SelectedCategory); + if (Math.Abs(result.SpeedDelta) > 0.001f) + ChangeNoteSpeed(result.SpeedDelta); + if (result.JudgementOffsetDeltaMilliseconds != 0) + ChangeJudgementOffset(result.JudgementOffsetDeltaMilliseconds); + if (result.AudioOutputLatencyDeltaMilliseconds != 0) + ChangeAudioOutputLatency(result.AudioOutputLatencyDeltaMilliseconds); + if (result.AudioOutputDeviceDelta != 0) + CycleAudioOutputDevice(result.AudioOutputDeviceDelta); + if (result.AudioDspProfileDelta != 0) + CycleAudioDspBufferProfile(result.AudioDspProfileDelta); + if (result.MasterVolumePercent >= 0) + ChangeMasterVolume(result.MasterVolumePercent); + if (result.ToggleKeySound) + ToggleReactiveKeySounds(); + if (result.ClearPremixCache) + RequestClearPremixCache(); + if (result.OpenPackCacheManager) + OpenPackCacheManager(); + if (result.SelectedPackCacheIndex >= 0) + SetPackCacheSelection(result.SelectedPackCacheIndex); + if (result.BuildSelectedPackCache) + RequestPinnedPackCacheEstimate(); + if (result.DeleteSelectedPackCache) + RequestDeleteSelectedPackCache(); + if (result.ClosePackCacheManager) + ClosePackCacheManager(); + if (result.ConfirmPinnedPackCache) + ConfirmPinnedPackCache(); + if (result.CancelPinnedPackCache) + CancelPinnedPackCache(); + if (result.InputProfileDelta != 0) + CycleInputProfile(result.InputProfileDelta); + if (result.InputPollingRateDelta != 0) + CycleInputPollingRate(result.InputPollingRateDelta); + if (result.FrameLimitDelta != 0) + CycleFrameLimit(result.FrameLimitDelta); + if (result.ResolutionDelta != 0) + CycleDisplayResolution(result.ResolutionDelta); + if (result.WindowModeDelta != 0) + CycleWindowMode(result.WindowModeDelta); + if (result.ToggleFpsCounter) + ToggleFpsCounter(); + if (result.BgaPlaybackModeDelta != 0) + CycleBgaPlaybackMode(result.BgaPlaybackModeDelta); + if (result.BgaDisplayModeDelta != 0) + CycleBgaDisplayMode(result.BgaDisplayModeDelta); + if (result.ToggleVSync) + ToggleVSync(); + if (result.BackgroundDimDeltaPercent != 0) + ChangeBackgroundDim(result.BackgroundDimDeltaPercent); + if (result.ExportProfile) + ExportProfileArchive(); + if (result.ImportProfile) + RequestOrImportProfileArchive(); + if (result.CheckForUpdates) + RequestUpdateCheck(manual: true); + if (result.OpenAudioSyncCalibration) + OpenAudioSyncCalibration(); + if (result.StartAudioSyncCalibration) + StartAudioSyncCalibration(); + if (result.RetryAudioSyncCalibration) + StartAudioSyncCalibration(); + if (result.ApplyAudioSyncCalibration) + ApplyAudioSyncCalibration(); + if (result.CancelAudioSyncCalibration) + CloseAudioSyncCalibration(); + if (result.RebindLane >= 0) + { + _optionsSelectedRow = ProjectQuintPresentation.InputRowForBinding( + result.RebindLane, + _optionsInputKeyCount); + _rebindLane = result.RebindLane; + } + if (!result.Back) + return; + + _rebindLane = -1; + _profileImportConfirmationPending = false; + CloseOptions(); + } + + private void ActivateHomeAction(HomeMenuAction action) + { + switch (action) + { + case HomeMenuAction.Play: + _packSelection = Mathf.Clamp( + ResolveActivePackSelection(), + 0, + Math.Max(0, (_packRegistry?.Packs.Count ?? 0) + 1)); + _status = _packRegistry?.Packs.Count > 0 + ? $"{_packRegistry.Packs.Count:N0} BMS PACKS REGISTERED" + : "SELECT A FOLDER OR ZIP TO BEGIN"; + _mode = AppMode.PackSelect; + break; + case HomeMenuAction.Options: + OpenOptions(AppMode.Home); + break; + case HomeMenuAction.Quit: + PersistSettings(); +#if UNITY_EDITOR + UnityEditor.EditorApplication.isPlaying = false; +#else + Application.Quit(); +#endif + break; + case HomeMenuAction.Profile: + OpenProfileModal(0); + break; + } + } + + private void HandleHomePresenterAction(HomeMenuAction action) + { + if (action == HomeMenuAction.Play) + _homeSelection = (int)HomeMenuAction.Play; + ActivateHomeAction(action); + } + + private int ResolveActivePackSelection() + { + var packs = _packRegistry?.Packs; + if (packs == null || _activePack == null) + return 0; + for (var index = 0; index < packs.Count; index++) + if (string.Equals(packs[index].Id, _activePack.Id, StringComparison.OrdinalIgnoreCase)) + return index; + return 0; + } + + private void UpdatePackSelectInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (_packKeyModeModalOpen) + { + if (keyboard.escapeKey.wasPressedThisFrame) + { + ClosePackKeyModeModal(); + return; + } + if (keyboard.downArrowKey.wasPressedThisFrame) + MovePackKeyModeModalSelection(1); + if (keyboard.upArrowKey.wasPressedThisFrame) + MovePackKeyModeModalSelection(-1); + if (keyboard.enterKey.wasPressedThisFrame || + keyboard.numpadEnterKey.wasPressedThisFrame) + ApplySelectedPackKeyMode( + PackKeyModeChoices[_packKeyModeModalSelection]); + return; + } + + if (_packRemovalConfirmationPending) + { + if (keyboard.escapeKey.wasPressedThisFrame) + { + CancelPackRemoval(); + return; + } + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + ConfirmPackRemoval(); + return; + } + + var optionCount = (_packRegistry?.Packs.Count ?? 0) + 2; + if (keyboard.downArrowKey.wasPressedThisFrame) + _packSelection = (_packSelection + 1) % optionCount; + if (keyboard.upArrowKey.wasPressedThisFrame) + _packSelection = (_packSelection + optionCount - 1) % optionCount; + if (keyboard.escapeKey.wasPressedThisFrame) + { + _mode = AppMode.Home; + return; + } + if (IsPackDifficultyTableShortcut( + keyboard.tKey.wasPressedThisFrame, + keyboard.digit5Key.wasPressedThisFrame, + keyboard.numpad5Key.wasPressedThisFrame)) + { + OpenDifficultyTableSetup(); + return; + } + if (keyboard.digit6Key.wasPressedThisFrame || + keyboard.numpad6Key.wasPressedThisFrame) + { + ApplyRecommendedDifficultyTable(); + return; + } + if (keyboard.lKey.wasPressedThisFrame) + { + RelinkSelectedPack(); + return; + } + if (keyboard.nKey.wasPressedThisFrame) + { + RenameSelectedPack(); + return; + } + if (keyboard.f5Key.wasPressedThisFrame) + { + RescanSelectedPack(); + return; + } + if (keyboard.deleteKey.wasPressedThisFrame || keyboard.backspaceKey.wasPressedThisFrame) + { + RequestPackRemoval(); + return; + } + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + ActivatePackSelection(_packSelection); + } + + internal static bool IsPackDifficultyTableShortcut( + bool tableKeyPressed, + bool digit5Pressed, + bool numpad5Pressed) => + tableKeyPressed || digit5Pressed || numpad5Pressed; + + private void HandlePackSelectUi(PackSelectUiResult result) + { + if (result.CancelKeyMode) + { + ClosePackKeyModeModal(); + return; + } + if (result.KeyModeChoice >= 0) + { + ApplySelectedPackKeyMode(result.KeyModeChoice); + return; + } + if (_packKeyModeModalOpen) + return; + if (result.CancelRemove) + { + CancelPackRemoval(); + return; + } + if (result.ConfirmRemove) + { + ConfirmPackRemoval(); + return; + } + if (_packRemovalConfirmationPending) + return; + if (result.SelectedIndex >= 0) + _packSelection = result.SelectedIndex; + if (result.RequestRemove) + { + RequestPackRemoval(); + return; + } + if (result.Relink) + { + RelinkSelectedPack(); + return; + } + if (result.Rename) + { + RenameSelectedPack(); + return; + } + if (result.Rescan) + { + RescanSelectedPack(); + return; + } + if (result.OpenKeyMode) + { + OpenPackKeyModeModal(); + return; + } + if (result.Back) + { + _mode = AppMode.Home; + return; + } + if (result.ConfigureTable) + { + OpenDifficultyTableSetup(); + return; + } + if (result.UseRecommendedTable) + { + ApplyRecommendedDifficultyTable(); + return; + } + if (result.Confirm) + ActivatePackSelection(_packSelection); + } + + private void OpenPackKeyModeModal() + { + var selected = SelectedRegisteredPack(); + if (selected == null || _packRegistry == null) + { + _status = "SELECT A REGISTERED PACK BEFORE CHANGING KEY MODE"; + return; + } + + _pendingPackKeyModeId = selected.Id; + _packKeyModeModalSelection = + Math.Max(0, PackKeyModeChoiceIndex(selected.ForcedKeyCount)); + _packKeyModeModalOpen = true; + _status = $"SELECT KEY MODE // {selected.DisplayName}"; + } + + private void ClosePackKeyModeModal() + { + _packKeyModeModalOpen = false; + _pendingPackKeyModeId = string.Empty; + _status = "KEY MODE CHANGE CANCELLED"; + } + + private void MovePackKeyModeModalSelection(int delta) + { + var count = PackKeyModeChoices.Length; + _packKeyModeModalSelection = + ((_packKeyModeModalSelection + delta) % count + count) % count; + } + + private void ApplySelectedPackKeyMode(int keyCount) + { + if (PackKeyModeChoiceIndex(keyCount) < 0 || + _packRegistry == null || + string.IsNullOrWhiteSpace(_pendingPackKeyModeId)) + { + _status = "KEY MODE CHANGE FAILED // INVALID SELECTION"; + _packKeyModeModalOpen = false; + _pendingPackKeyModeId = string.Empty; + return; + } + + var selected = _packRegistry.Find(_pendingPackKeyModeId); + if (selected == null) + { + _status = "KEY MODE CHANGE FAILED // PACK NOT FOUND"; + _packKeyModeModalOpen = false; + _pendingPackKeyModeId = string.Empty; + return; + } + + var isActive = _activePack != null && + string.Equals( + _activePack.Id, + selected.Id, + StringComparison.OrdinalIgnoreCase); + if (selected.ForcedKeyCount == keyCount) + { + _packKeyModeModalOpen = false; + _pendingPackKeyModeId = string.Empty; + _status = $"KEY MODE UNCHANGED // {selected.DisplayName} // " + + $"{selected.KeyModeLabel}"; + return; + } + try + { + var updated = _packRegistry.SetForcedKeyCount(selected.Id, keyCount); + _packKeyModeModalOpen = false; + _pendingPackKeyModeId = string.Empty; + _status = $"KEY MODE // {updated.DisplayName} // {updated.KeyModeLabel}"; + if (!isActive) + return; + + ClearActiveLibraryState(); + _activePack = updated; + StartCoroutine(InitializeLibrary(false)); + } + catch (Exception exception) + { + _packKeyModeModalOpen = false; + _pendingPackKeyModeId = string.Empty; + _status = $"KEY MODE CHANGE FAILED // {exception.Message}"; + Debug.LogException(exception); + } + } + + internal static int PackKeyModeChoiceIndex(int keyCount) + { + for (var index = 0; index < PackKeyModeChoices.Length; index++) + if (PackKeyModeChoices[index] == keyCount) + return index; + return -1; + } + + internal static int ResolvePackScanKeyCount( + BmsPackDefinition pack, + BmsDifficultyTableDefinition table) + { + if (pack?.ForcedKeyCount >= 4 && pack.ForcedKeyCount <= 7) + return pack.ForcedKeyCount; + var preset = BmsDifficultyTableIndex.FindPreset(table?.Id, table?.HeaderUrl); + return preset.HasValue && + preset.Value.DefaultKeyCount >= 4 && + preset.Value.DefaultKeyCount <= 7 + ? preset.Value.DefaultKeyCount + : 0; + } + + private void RequestPackRemoval() + { + var packs = _packRegistry?.Packs; + if (packs == null || _packSelection < 0 || _packSelection >= packs.Count) + return; + + var pack = packs[_packSelection]; + _pendingPackRemovalId = pack.Id; + _pendingPackRemovalName = pack.DisplayName; + _packRemovalConfirmationPending = true; + _status = $"CONFIRM REMOVE // {pack.DisplayName}"; + } + + private void CancelPackRemoval() + { + _packRemovalConfirmationPending = false; + _pendingPackRemovalId = string.Empty; + _pendingPackRemovalName = string.Empty; + _status = "PACK REMOVAL CANCELLED"; + } + + private void ConfirmPackRemoval() + { + var pack = _packRegistry?.Find(_pendingPackRemovalId); + if (pack == null) + { + CancelPackRemoval(); + _status = "PACK IS NO LONGER REGISTERED"; + return; + } + + if (_packRemovalInProgress) + return; + StartCoroutine(RemovePack(pack)); + } + + private IEnumerator RemovePack(BmsPackDefinition pack) + { + var removedPackName = pack.DisplayName; + var removedManagedContent = pack.SourceKind == BmsPackSourceKind.ExtractedZip; + var removedSelection = _packSelection; + var removedActivePack = _activePack != null && + string.Equals(_activePack.Id, pack.Id, StringComparison.OrdinalIgnoreCase); + _packRemovalInProgress = true; + _packRemovalConfirmationPending = false; + _flow.BeginLoading( + "REMOVING BMS PACK", + removedPackName, + Time.realtimeSinceStartupAsDouble); + _mode = AppMode.PackImport; + + Task removalTask = null; + Exception removalError = null; + try + { + if (PathsEqual(_settings.BmsLibraryRoot, pack.ContentRoot)) + { + _settings.BmsLibraryRoot = string.Empty; + PersistSettings(); + } + + removalTask = Task.Run(() => _packRegistry.Unregister(pack.Id)); + } + catch (Exception exception) + { + removalError = exception; + } + + while (removalError == null && removalTask != null && !removalTask.IsCompleted) + { + _flow.ReportLoading( + removedManagedContent + ? "DELETING IMPORTED SONG FILES" + : "REMOVING REGISTRATION AND DERIVED INDEX", + 0, + 0); + yield return null; + } + + if (removalError == null && removalTask != null) + { + if (removalTask.IsFaulted) + removalError = removalTask.Exception?.GetBaseException() ?? + new InvalidOperationException("Unknown pack removal error."); + else if (!removalTask.Result) + removalError = + new InvalidOperationException("The selected pack is no longer registered."); + } + + try + { + if (removalError != null) + throw removalError; + if (removedActivePack) + ClearActiveLibraryState(); + + _activePack = _packRegistry.ActivePack; + var packCount = _packRegistry.Packs.Count; + _packSelection = packCount == 0 + ? 0 + : Mathf.Clamp(removedSelection, 0, packCount - 1); + _packRemovalConfirmationPending = false; + _pendingPackRemovalId = string.Empty; + _pendingPackRemovalName = string.Empty; + _mode = AppMode.PackSelect; + _status = !string.IsNullOrWhiteSpace(_packRegistry.LastRemovalWarning) + ? $"REMOVED WITH STORAGE WARNING // {_packRegistry.LastRemovalWarning}" + : removedManagedContent + ? $"REMOVED // {removedPackName} // IMPORTED SONG FILES DELETED" + : $"REMOVED // {removedPackName} // EXTERNAL SOURCE KEPT"; + } + catch (Exception exception) + { + _packRemovalConfirmationPending = false; + _pendingPackRemovalId = string.Empty; + _pendingPackRemovalName = string.Empty; + _status = $"PACK REMOVAL FAILED // {exception.Message}"; + _mode = AppMode.PackSelect; + Debug.LogException(exception); + } + _packRemovalInProgress = false; + } + + private void ClearActiveLibraryState() + { + _scanCancellation?.Cancel(); + _libraryScanGeneration++; + _libraryScanRunning = false; + _openLibraryWhenScanCompletes = false; + _libraryReady = false; + _libraryRoot = string.Empty; + _libraryLoadError = string.Empty; + _activeCatalog = null; + _aeryCatalog = null; + _courseIndex = null; + _searchAliases = SearchAliasIndex.Empty; + _aeryMatchedCharts = 0; + _catalogMatchedCharts = 0; + ClearLoadedSongState(); + _courseSession.ClearRun(); + _libraryController.Reset(); + ClearLibraryThumbnail(); + } + + private BmsPackDefinition SelectedRegisteredPack() + { + var packs = _packRegistry?.Packs; + return packs != null && _packSelection >= 0 && _packSelection < packs.Count + ? packs[_packSelection] + : null; + } + + private void RelinkSelectedPack() + { + var selected = SelectedRegisteredPack(); + if (selected == null) + return; + var selectedPath = NativeBmsPackPicker.ChooseFolder(); + if (string.IsNullOrWhiteSpace(selectedPath)) + { + _status = "PACK RELINK CANCELLED"; + return; + } + + var oldRoot = selected.ContentRoot; + var isActive = _activePack != null && + string.Equals(_activePack.Id, selected.Id, StringComparison.OrdinalIgnoreCase); + try + { + var pack = _packRegistry.RelinkFolder(selected.Id, selectedPath); + if (PathsEqual(_settings.BmsLibraryRoot, oldRoot)) + { + _settings.BmsLibraryRoot = string.Empty; + PersistSettings(); + } + _activePack = _packRegistry.ActivePack; + _status = $"RELINKED // {pack.DisplayName} // INDEX INVALIDATED"; + if (isActive) + { + ClearActiveLibraryState(); + StartCoroutine(InitializeLibrary(true)); + } + } + catch (Exception exception) + { + _status = $"PACK RELINK FAILED // {exception.Message}"; + Debug.LogException(exception); + } + } + + private void RenameSelectedPack() + { + var selected = SelectedRegisteredPack(); + if (selected == null) + return; + var displayName = NativeBmsPackPicker.ChoosePackDisplayName(selected.DisplayName); + if (string.IsNullOrWhiteSpace(displayName)) + { + _status = "PACK RENAME CANCELLED"; + return; + } + + try + { + var pack = _packRegistry.Rename(selected.Id, displayName); + _activePack = _packRegistry.ActivePack; + _status = $"RENAMED // {pack.DisplayName}"; + } + catch (Exception exception) + { + _status = $"PACK RENAME FAILED // {exception.Message}"; + Debug.LogException(exception); + } + } + + private void RescanSelectedPack() + { + var selected = SelectedRegisteredPack(); + if (selected == null) + return; + var isActive = _activePack != null && + string.Equals(_activePack.Id, selected.Id, StringComparison.OrdinalIgnoreCase); + try + { + _packRegistry.InvalidateIndex(selected.Id); + _status = $"INDEX CLEARED // {selected.DisplayName}"; + if (isActive) + { + ClearActiveLibraryState(); + StartCoroutine(InitializeLibrary(true)); + } + } + catch (Exception exception) + { + _status = $"PACK RESCAN FAILED // {exception.Message}"; + Debug.LogException(exception); + } + } + + private static bool PathsEqual(string left, string right) + { + if (string.IsNullOrWhiteSpace(left) || string.IsNullOrWhiteSpace(right)) + return false; + try + { + return string.Equals( + Path.GetFullPath(left), + Path.GetFullPath(right), + StringComparison.Ordinal); + } + catch + { + return false; + } + } + + private void ActivatePackSelection(int selection) + { + var packs = _packRegistry?.Packs; + var packCount = packs?.Count ?? 0; + if (selection < packCount) + { + var selected = packs[selection]; + if (!selected.IsAvailable) + { + _status = $"SOURCE MISSING // {selected.ContentRoot}"; + return; + } + + var alreadyLoaded = _libraryReady && _activePack != null && + string.Equals(_activePack.Id, selected.Id, StringComparison.OrdinalIgnoreCase); + _packRegistry.Select(selected.Id); + _activePack = selected; + if (alreadyLoaded) + { + _mode = AppMode.Library; + RefreshLibraryThumbnail(); + return; + } + + _courseSession.ClearRun(); + StartCoroutine(InitializeLibrary(true)); + return; + } + + if (selection == packCount) + AddPackFolder(); + else + StartZipPackImport(); + } + + private void AddPackFolder() + { + _flow.BeginLoading( + "ADD BMS PACK", + "WAITING FOR FOLDER SELECTION", + Time.realtimeSinceStartupAsDouble); + _mode = AppMode.PackImport; + StartCoroutine(PickPackFolder()); + } + + private IEnumerator PickPackFolder() + { + // Let retained click dispatch finish before opening the blocking native chooser. + yield return null; + var selectedPath = NativeBmsPackPicker.ChooseFolder(); + if (string.IsNullOrWhiteSpace(selectedPath)) + { + if (!string.IsNullOrWhiteSpace(NativeBmsPackPicker.LastError)) + { + SetError("PACK PICKER FAILED", NativeBmsPackPicker.LastError); + yield break; + } + _status = "FOLDER SELECTION CANCELLED"; + _mode = AppMode.PackSelect; + yield break; + } + + yield return RegisterPackFolder(selectedPath); + } + + private IEnumerator RegisterPackFolder(string selectedPath) + { + _packImportCancellation?.Cancel(); + _packImportCancellation?.Dispose(); + _packImportCancellation = new CancellationTokenSource(); + var token = _packImportCancellation.Token; + _flow.BeginLoading( + "VALIDATING BMS PACK", + Path.GetFileName(selectedPath.TrimEnd( + Path.DirectorySeparatorChar, + Path.AltDirectorySeparatorChar)), + Time.realtimeSinceStartupAsDouble); + _mode = AppMode.PackImport; + + var validationTask = Task.Run( + () => BmsLibraryLocator.ContainsChart(selectedPath, token), + token); + while (!validationTask.IsCompleted) + { + _flow.ReportLoading("SEARCHING FOR BMS / BME / BML CHARTS", 0, 0); + yield return null; + } + + if (validationTask.IsCanceled || token.IsCancellationRequested) + { + _status = "PACK REGISTRATION CANCELLED"; + _mode = AppMode.PackSelect; + yield break; + } + if (validationTask.IsFaulted) + { + SetError( + "PACK VALIDATION FAILED", + validationTask.Exception?.GetBaseException().Message ?? "Unknown folder validation error"); + yield break; + } + if (!validationTask.Result) + { + SetError( + "NO BMS CHARTS FOUND", + $"The selected folder does not contain BMS/BME/BML charts:\n{selectedPath}"); + yield break; + } + + try + { + var pack = _packRegistry.RegisterFolder(selectedPath, chartPresenceVerified: true); + _activePack = pack; + _packSelection = ResolveActivePackSelection(); + _status = $"REGISTERED // {pack.DisplayName}"; + StartCoroutine(InitializeLibrary(true)); + } + catch (Exception exception) + { + _status = $"PACK REGISTRATION FAILED // {exception.Message}"; + _mode = AppMode.PackSelect; + Debug.LogException(exception); + } + } + + private void StartZipPackImport() + { + _flow.BeginLoading( + "IMPORT BMS PACK ZIP", + "WAITING FOR ZIP SELECTION", + Time.realtimeSinceStartupAsDouble); + _mode = AppMode.PackImport; + StartCoroutine(PickPackZip()); + } + + private IEnumerator PickPackZip() + { + // See PickPackFolder: let retained input dispatch finish before AppleScript opens. + yield return null; + var selectedPath = NativeBmsPackPicker.ChooseZip(); + if (string.IsNullOrWhiteSpace(selectedPath)) + { + if (!string.IsNullOrWhiteSpace(NativeBmsPackPicker.LastError)) + { + SetError("PACK PICKER FAILED", NativeBmsPackPicker.LastError); + yield break; + } + _status = "ZIP SELECTION CANCELLED"; + _mode = AppMode.PackSelect; + yield break; + } + if (!string.Equals(Path.GetExtension(selectedPath), ".zip", StringComparison.OrdinalIgnoreCase)) + { + _status = "SELECT A .ZIP FILE"; + _mode = AppMode.PackSelect; + yield break; + } + yield return PreflightAndImportPackZip(selectedPath); + } + + private IEnumerator PreflightAndImportPackZip(string zipPath) + { + _flow.BeginLoading( + "INSPECTING BMS PACK ZIP", + Path.GetFileName(zipPath), + Time.realtimeSinceStartupAsDouble); + _mode = AppMode.PackImport; + var inspectionTask = Task.Run(() => BmsPackImportService.InspectAdjacentZip(zipPath)); + while (!inspectionTask.IsCompleted) + { + _flow.ReportLoading("CALCULATING EXTRACTED SIZE", 0, 0); + yield return null; + } + + if (inspectionTask.IsFaulted) + { + SetError( + "PACK INSPECTION FAILED", + inspectionTask.Exception?.GetBaseException().Message ?? "Unknown ZIP inspection error"); + yield break; + } + + var inspection = inspectionTask.Result; + if (Directory.Exists(inspection.DestinationPath) && + !BmsPackImportService.IsCompletedManagedExtraction(inspection.DestinationPath)) + { + SetError( + "EXTRACTION FOLDER ALREADY EXISTS", + $"ProjectQuint will not overwrite this folder:\n{inspection.DestinationPath}\n" + + "Rename the ZIP or the existing folder, then try again."); + yield break; + } + if (!inspection.HasEnoughSpace) + { + SetError( + "NOT ENOUGH STORAGE", + $"The pack needs {FormatStorageGigabytes(inspection.ExtractedBytes)}, but only " + + $"{FormatStorageGigabytes(inspection.AvailableBytes)} is available.\n" + + $"Destination: {inspection.DestinationPath}"); + yield break; + } + + if (!NativeBmsPackPicker.ConfirmZipImport(inspection)) + { + if (!string.IsNullOrWhiteSpace(NativeBmsPackPicker.LastError)) + { + SetError("PACK IMPORT CONFIRMATION FAILED", NativeBmsPackPicker.LastError); + yield break; + } + _status = "ZIP IMPORT CANCELLED"; + _mode = AppMode.PackSelect; + yield break; + } + + yield return ImportPackZip(zipPath); + } + + private IEnumerator ImportPackZip(string zipPath) + { + _packImportCancellation?.Cancel(); + _packImportCancellation?.Dispose(); + _packImportCancellation = new CancellationTokenSource(); + var token = _packImportCancellation.Token; + var progress = new BmsPackImportProgress(); + _flow.BeginLoading( + "IMPORTING BMS PACK", + Path.GetFileName(zipPath), + Time.realtimeSinceStartupAsDouble); + _mode = AppMode.PackImport; + + var importTask = Task.Run(() => BmsPackImportService.ExtractZipBesideArchive( + zipPath, + progress, + token), token); + while (!importTask.IsCompleted) + { + var snapshot = progress.Snapshot(); + var bytes = snapshot.TotalBytes > 0 + ? $"{snapshot.CompletedBytes / 1048576.0:0.0} / {snapshot.TotalBytes / 1048576.0:0.0} MiB" + : "READING ARCHIVE"; + _flow.ReportLoading( + $"{bytes} // {snapshot.CurrentEntry}", + snapshot.CompletedFiles, + snapshot.TotalFiles); + yield return null; + } + + if (importTask.IsCanceled || token.IsCancellationRequested) + { + _status = "ZIP IMPORT CANCELLED"; + _mode = AppMode.PackSelect; + yield break; + } + if (importTask.IsFaulted) + { + SetError( + "PACK IMPORT FAILED", + importTask.Exception?.GetBaseException().Message ?? "Unknown ZIP import error"); + yield break; + } + + try + { + var pack = _packRegistry.RegisterExtractedZip(zipPath, importTask.Result); + _activePack = pack; + _packSelection = ResolveActivePackSelection(); + _status = $"IMPORTED // {pack.DisplayName}"; + StartCoroutine(InitializeLibrary(true)); + } + catch (Exception exception) + { + SetError("PACK REGISTRATION FAILED", exception.Message); + Debug.LogException(exception); + } + } + + private static string FormatStorageGigabytes(long bytes) => + $"{Math.Max(0L, bytes) / 1_000_000_000.0:0.0} GB"; + + private void UpdatePackImportInput() + { + if (_packRemovalInProgress) + return; + if (Keyboard.current?.escapeKey.wasPressedThisFrame != true) + return; + _packImportCancellation?.Cancel(); + _status = "CANCELLING ZIP IMPORT..."; + } + + private void OpenDifficultyTableSetup() + { + if (_difficultyTableRegistry == null) + return; + var packs = _packRegistry?.Packs; + var selectedPack = packs != null && + _packSelection >= 0 && + _packSelection < packs.Count + ? packs[_packSelection] + : _activePack ?? _packRegistry?.ActivePack; + if (selectedPack == null) + { + _status = "SELECT A REGISTERED PACK BEFORE ASSIGNING A TABLE"; + return; + } + + _tableSetupPackId = selectedPack.Id; + _tableSetupSelection = ResolveTableSelection(selectedPack.DifficultyTableId); + var selected = ResolveSelectedDifficultyTable(); + _tableUrlDraft = selected?.HeaderUrl ?? string.Empty; + _tableSetupStatus = + $"{selectedPack.DisplayName} // UP / DOWN SELECT // ENTER ASSIGN // 1–6 PRESET"; + _tableUrlFocusRequested = false; + _tableUrlReleaseRequested = false; + _tableUrlFocused = false; + _mode = AppMode.DifficultyTableSetup; + } + + private BmsPackDefinition ResolveDifficultyTableSetupPack() => + _packRegistry?.Find(_tableSetupPackId) ?? + _activePack ?? + _packRegistry?.ActivePack; + + private BmsDifficultyTableDefinition ResolvePackDifficultyTable(BmsPackDefinition pack) => + pack == null || _difficultyTableRegistry == null + ? null + : _difficultyTableRegistry.Find(pack.DifficultyTableId); + + private void ApplyRecommendedDifficultyTable() + { + if (_difficultyTableRegistry == null || _packRegistry == null) + return; + + var targetPack = SelectedRegisteredPack(); + if (targetPack == null) + { + _status = "SELECT A REGISTERED PACK BEFORE ASSIGNING A TABLE"; + return; + } + + var recommendation = + PackMusicScreenPresenter.RecommendedDifficultyTableForPack(targetPack); + if (!recommendation.HasValue) + { + _status = "NO SINGLE TABLE RECOMMENDATION // PRESS 5 TO CHOOSE"; + return; + } + + var preset = recommendation.Value; + var table = _difficultyTableRegistry.Find(preset.Id) ?? + _difficultyTableRegistry.Register(preset.HeaderUrl, activate: false); + _packRegistry.AssignDifficultyTable(targetPack.Id, table.Id); + _tableSetupPackId = targetPack.Id; + _tableSetupSelection = ResolveTableSelection(table.Id); + OnPackDifficultyTableChanged( + targetPack, + $"{targetPack.DisplayName} // {table.DisplayName} ASSIGNED"); + } + + private int ResolveTableSelection(string tableId) + { + var tables = _difficultyTableRegistry?.Tables; + if (tables == null || tables.Count == 0) + return 0; + for (var index = 0; index < tables.Count; index++) + if (string.Equals( + tables[index].Id, + tableId, + StringComparison.OrdinalIgnoreCase)) + return index; + return 0; + } + + private BmsDifficultyTableDefinition ResolveSelectedDifficultyTable() + { + var tables = _difficultyTableRegistry?.Tables; + if (tables == null || tables.Count == 0) + return null; + _tableSetupSelection = Mathf.Clamp(_tableSetupSelection, 0, tables.Count - 1); + return tables[_tableSetupSelection]; + } + + private void UpdateDifficultyTableSetupInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (_tableUrlFocused) + { + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + { + _tableUrlReleaseRequested = true; + _tableUrlFocused = false; + ApplyDifficultyTableUrl(); + } + else if (keyboard.escapeKey.wasPressedThisFrame) + { + _tableUrlReleaseRequested = true; + _tableUrlFocused = false; + _tableSetupStatus = "CUSTOM URL EDITING ENDED"; + } + return; + } + + var tableCount = _difficultyTableRegistry?.Tables.Count ?? 0; + if (keyboard.downArrowKey.wasPressedThisFrame && tableCount > 0) + { + _tableSetupSelection = MoveDifficultyTableSelection( + _tableSetupSelection, + tableCount, + 1); + var selected = ResolveSelectedDifficultyTable(); + _tableUrlDraft = selected?.HeaderUrl ?? string.Empty; + _tableSetupStatus = $"SELECTED // {selected?.DisplayName ?? "TABLE"} // ENTER TO ASSIGN"; + return; + } + if (keyboard.upArrowKey.wasPressedThisFrame && tableCount > 0) + { + _tableSetupSelection = MoveDifficultyTableSelection( + _tableSetupSelection, + tableCount, + -1); + var selected = ResolveSelectedDifficultyTable(); + _tableUrlDraft = selected?.HeaderUrl ?? string.Empty; + _tableSetupStatus = $"SELECTED // {selected?.DisplayName ?? "TABLE"} // ENTER TO ASSIGN"; + return; + } + + var presetIndex = keyboard.digit1Key.wasPressedThisFrame ? 0 : + keyboard.digit2Key.wasPressedThisFrame ? 1 : + keyboard.digit3Key.wasPressedThisFrame ? 2 : + keyboard.digit4Key.wasPressedThisFrame ? 3 : + keyboard.digit5Key.wasPressedThisFrame ? 4 : + keyboard.digit6Key.wasPressedThisFrame ? 5 : -1; + if (presetIndex >= 0 && presetIndex < BmsDifficultyTableIndex.Presets.Length) + { + _tableUrlDraft = BmsDifficultyTableIndex.Presets[presetIndex].HeaderUrl; + _tableSetupStatus = $"SELECTED // {BmsDifficultyTableIndex.Presets[presetIndex].DisplayName} // ENTER TO APPLY"; + return; + } + if (keyboard.cKey.wasPressedThisFrame) + { + _tableUrlFocusRequested = true; + _tableUrlFocused = true; + _tableSetupStatus = "PASTE A HEADER JSON OR BMSTABLE PAGE URL, THEN PRESS ENTER"; + return; + } + if (keyboard.deleteKey.wasPressedThisFrame || keyboard.backspaceKey.wasPressedThisFrame) + { + RemoveSelectedDifficultyTable(); + return; + } + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + { + ApplyDifficultyTableUrl(); + return; + } + if (keyboard.escapeKey.wasPressedThisFrame) + _mode = AppMode.PackSelect; + } + + internal static int MoveDifficultyTableSelection(int current, int count, int delta) + { + if (count <= 0) + return 0; + var normalized = (current + delta) % count; + return normalized < 0 ? normalized + count : normalized; + } + + private void HandleDifficultyTableUi(DifficultyTableUiResult result) + { + _tableUrlDraft = result.Url ?? string.Empty; + _tableUrlFocused = result.UrlFocused; + if (result.SelectedIndex >= 0 && result.SelectedIndex < (_difficultyTableRegistry?.Tables.Count ?? 0)) + { + _tableSetupSelection = result.SelectedIndex; + var selected = ResolveSelectedDifficultyTable(); + _tableUrlDraft = selected?.HeaderUrl ?? string.Empty; + _tableSetupStatus = $"SELECTED // {selected?.DisplayName ?? "TABLE"} // ASSIGN TO PACK"; + } + if (result.PresetIndex >= 0 && result.PresetIndex < BmsDifficultyTableIndex.Presets.Length) + { + _tableUrlDraft = BmsDifficultyTableIndex.Presets[result.PresetIndex].HeaderUrl; + _tableSetupStatus = $"SELECTED // {BmsDifficultyTableIndex.Presets[result.PresetIndex].DisplayName} // APPLY TO SAVE"; + } + if (result.Clear) + { + ClearDifficultyTableAssignment(); + } + else if (result.Save) + { + ApplyDifficultyTableUrl(); + } + else if (result.Back) + { + _mode = AppMode.PackSelect; + } + } + + private void ApplyDifficultyTableUrl() + { + if (_difficultyTableRegistry == null || _packRegistry == null) + return; + var targetPack = ResolveDifficultyTableSetupPack(); + if (targetPack == null) + { + _tableSetupStatus = "PACK NOT FOUND // RETURN AND SELECT A PACK"; + return; + } + var url = (_tableUrlDraft ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(url)) + { + ClearDifficultyTableAssignment(); + return; + } + if ( + (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || + uri.Scheme != Uri.UriSchemeHttps)) + { + _tableSetupStatus = "INVALID URL // HTTPS IS REQUIRED"; + return; + } + + var table = _difficultyTableRegistry.Register(url, activate: false); + _packRegistry.AssignDifficultyTable(targetPack.Id, table.Id); + _tableSetupSelection = ResolveTableSelection(table.Id); + OnPackDifficultyTableChanged( + targetPack, + $"{targetPack.DisplayName} // {table.DisplayName} ASSIGNED"); + } + + private void ClearDifficultyTableAssignment() + { + var targetPack = ResolveDifficultyTableSetupPack(); + if (targetPack == null || _packRegistry == null) + return; + _packRegistry.AssignDifficultyTable(targetPack.Id, string.Empty); + OnPackDifficultyTableChanged( + targetPack, + $"{targetPack.DisplayName} // TABLE ASSIGNMENT CLEARED"); + } + + private void RemoveSelectedDifficultyTable() + { + var selected = ResolveSelectedDifficultyTable(); + if (selected == null) + return; + var name = selected.DisplayName; + var activePackAffected = + _activePack != null && + string.Equals( + _activePack.DifficultyTableId, + selected.Id, + StringComparison.OrdinalIgnoreCase); + var clearedAssignments = + _packRegistry?.ClearDifficultyTableAssignments(selected.Id) ?? 0; + _difficultyTableRegistry.Remove(selected.Id); + _tableSetupSelection = Mathf.Clamp( + _tableSetupSelection, + 0, + Math.Max(0, (_difficultyTableRegistry?.Tables.Count ?? 1) - 1)); + _tableUrlDraft = ResolveSelectedDifficultyTable()?.HeaderUrl ?? string.Empty; + if (activePackAffected) + InvalidateActiveDifficultyTableState(); + _status = $"TABLE REMOVED // {name}"; + _tableSetupStatus = + $"REMOVED // {name} // {clearedAssignments:N0} PACK ASSIGNMENTS CLEARED"; + } + + private void OnPackDifficultyTableChanged( + BmsPackDefinition targetPack, + string status, + bool returnToPackSelect = true) + { + if (_activePack != null && targetPack != null && + string.Equals( + _activePack.Id, + targetPack.Id, + StringComparison.OrdinalIgnoreCase)) + InvalidateActiveDifficultyTableState(); + _status = status; + if (returnToPackSelect) + _mode = AppMode.PackSelect; + } + + private void InvalidateActiveDifficultyTableState() + { + _activeDifficultyTable = ResolvePackDifficultyTable(_activePack); + _libraryReady = false; + _activeCatalog = null; + _courseIndex = null; + _catalogMatchedCharts = 0; + } + + private void HandleLibraryUi(LibraryUiResult result) + { + HandleLibraryControllerResult( + _libraryController.HandleUi(result, _mode == AppMode.Library)); + } + + private void HandleLibraryControllerResult(LibraryControllerResult result) + { + if (result.RefreshThumbnail) + RefreshLibraryThumbnail(); + + switch (result.Command) + { + case LibraryCommand.BackToHome: + _mode = AppMode.PackSelect; + break; + case LibraryCommand.ReloadLibrary: + StartCoroutine(InitializeLibrary(true)); + break; + case LibraryCommand.OpenCourses: + OpenCourseSelect(); + break; + case LibraryCommand.OpenRanking: + OpenProfileModal(1); + break; + case LibraryCommand.OpenOptions: + OpenOptions(AppMode.Library); + break; + case LibraryCommand.OpenProfile: + OpenProfileModal(0); + break; + case LibraryCommand.OpenQuickOptions: + _flow.BeginQuickOptions(); + break; + case LibraryCommand.PlayRandomSong: + StartCoroutine(LoadRandomSong()); + break; + case LibraryCommand.PlaySelectedSong: + StartCoroutine(LoadSelectedSong()); + break; + case LibraryCommand.ToggleFavorite: + { + var song = _libraryController.State.SelectedSong; + if (song == null) + break; + var isFavorite = _favoriteRepository.Toggle(song); + _libraryController.State.RefreshFavoriteStatus(); + _status = isFavorite + ? $"FAVORITE ADDED // {song.DisplayTitle}" + : $"FAVORITE REMOVED // {song.DisplayTitle}"; + RefreshLibraryThumbnail(); + break; + } + } + + if (result.RefreshThumbnail || result.Command == LibraryCommand.ToggleFavorite) + StageActivePackLibraryViewState(); + if (result.Command != LibraryCommand.None) + FlushPendingLibraryViewState(force: true); + } + + private void StageActivePackLibraryViewState() + { + var state = _libraryController?.State; + if (_packRegistry == null || _activePack == null || state == null || !_libraryReady) + return; + var selectedChartMd5 = state.SelectedSong?.Metadata?.Md5 ?? string.Empty; + if (!_packRegistry.StageLibraryViewState( + _activePack.Id, + state.SelectedLevelFilters, + state.SortMode, + state.FavoritesOnly, + selectedChartMd5)) + return; + _libraryViewStateDirty = true; + _libraryViewStateSaveDeadline = + Time.realtimeSinceStartupAsDouble + LibraryViewStateSaveDelaySeconds; + } + + private void FlushPendingLibraryViewState(bool force) + { + if (!_libraryViewStateDirty || _packRegistry == null || + !force && Time.realtimeSinceStartupAsDouble < _libraryViewStateSaveDeadline) + return; + try + { + _packRegistry.FlushStagedChanges(); + _libraryViewStateDirty = false; + } + catch (Exception exception) + { + _libraryViewStateSaveDeadline = + Time.realtimeSinceStartupAsDouble + LibraryViewStateSaveDelaySeconds; + _status = $"LIBRARY VIEW STATE NOT SAVED // {exception.Message}"; + Debug.LogWarning( + $"[ProjectQuint Library] Pack view state could not be saved: {exception}"); + } + } + + private AeryCourseDefinition SelectedCourse + { + get + { + var courses = _courseIndex?.Courses; + return courses == null || courses.Count == 0 + ? null + : courses[Mathf.Clamp(_courseSelectedIndex, 0, courses.Count - 1)]; + } + } + + private LaneModifier EffectiveCourseModifier + { + get + { + var course = _activeCourse ?? SelectedCourse; + return CourseLaneModifierPolicy.Normalize(_laneModifier, course?.AllowsMirror == true); + } + } + + private void OpenCourseSelect() + { + _libraryController.ClearSearchFocus(); + var courseCount = _courseIndex?.Courses?.Count ?? 0; + _courseSession.Select(_courseSelectedIndex, courseCount); + _courseStatus = ResolveCourseEntryStatus(courseCount, _courseStatus); + _mode = AppMode.CourseSelect; + } + + internal static string ResolveCourseEntryStatus(int courseCount, string currentStatus) + { + if (courseCount <= 0) + return "ACTIVE DIFFICULTY TABLE HAS NO COURSES // ESC RETURN"; + return string.IsNullOrWhiteSpace(currentStatus) + ? "UP / DOWN SELECT // ENTER START // ESC RETURN" + : currentStatus; + } + + private void HandleCourseSelectUi(CourseSelectUiResult result) + { + if (result.SelectedIndex >= 0) + _courseSession.Select(result.SelectedIndex, _courseIndex?.Courses.Count ?? 0); + if (result.ToggleMirror) + ToggleCourseMirror(); + if (Math.Abs(result.SpeedDelta) > 0.001f) + ChangeNoteSpeed(result.SpeedDelta); + if (result.Play) + StartSelectedCourse(); + if (result.Back) + _mode = AppMode.Library; + } + + private void ToggleCourseMirror() + { + var course = _activeCourse ?? SelectedCourse; + if (course?.AllowsMirror != true) + { + _laneModifier = LaneModifier.Off; + _courseStatus = $"{course?.DisplayName ?? "COURSE"}: MIRROR IS NOT ALLOWED"; + PersistSettings(); + return; + } + _laneModifier = EffectiveCourseModifier == LaneModifier.Mirror + ? LaneModifier.Off + : LaneModifier.Mirror; + PersistSettings(); + } + + private void ChangeNoteSpeed(float delta, bool persist = true) + { + _noteSpeed = Mathf.Round(Mathf.Clamp( + _noteSpeed + delta, + GameSettings.MinimumNoteSpeed, + GameSettings.MaximumNoteSpeed) * 10f) / 10f; + if (persist) + PersistSettings(); + } + + private void CycleGearPosition(int delta) + { + const int count = 3; + var value = ((int)_gearPosition + delta) % count; + if (value < 0) value += count; + _gearPosition = (GameplayGearPosition)value; + PersistSettings(); + } + + private void CycleLaneModifier(int delta) + { + const int count = 3; + var value = ((int)_laneModifier + delta) % count; + if (value < 0) value += count; + _laneModifier = (LaneModifier)value; + PersistSettings(); + } + + private void CycleGaugeType(int delta) + { + _gaugeType = CycleSelectableGaugeType(_gaugeType, delta); + PersistSettings(); + } + + internal static GaugeMode CycleSelectableGaugeType(GaugeMode current, int delta) + { + var currentIndex = current switch + { + GaugeMode.Hard => 1, + GaugeMode.ExHard => 2, + _ => 0, + }; + var nextIndex = (currentIndex + delta) % 3; + if (nextIndex < 0) nextIndex += 3; + return nextIndex switch + { + 1 => GaugeMode.Hard, + 2 => GaugeMode.ExHard, + _ => GaugeMode.Normal, + }; + } + + private void CycleGameplaySkin(int delta) + { + const int count = 2; + var value = ((int)_gameplaySkin + delta) % count; + if (value < 0) value += count; + _gameplaySkin = (GameplaySkin)value; + PersistSettings(); + } + + private void CycleNoteSkin(int delta) + { + const int count = 2; + var value = ((int)_noteSkin + delta) % count; + if (value < 0) value += count; + _noteSkin = (GameplayNoteSkin)value; + PersistSettings(); + } + + private void CycleLongNoteStyle(int delta) + { + const int count = 2; + var value = ((int)_longNoteStyle + delta) % count; + if (value < 0) value += count; + _longNoteStyle = (LongNoteVisualStyle)value; + PersistSettings(); + } + + private void CycleBgaDisplayMode(int delta) + { + const int count = 2; + var value = ((int)_bgaDisplayMode + delta) % count; + if (value < 0) value += count; + _bgaDisplayMode = (BgaDisplayMode)value; + PersistSettings(); + } + + private void ToggleLaneSeparators() + { + _laneSeparatorsEnabled = !_laneSeparatorsEnabled; + PersistSettings(); + } + + private void ToggleTimingMeter() + { + _timingMeterEnabled = !_timingMeterEnabled; + PersistSettings(); + } + + private void ChangeJudgementOffset(int deltaMilliseconds, bool persist = true) + { + _judgementOffsetMilliseconds = Mathf.Clamp( + _judgementOffsetMilliseconds + deltaMilliseconds, + GameSettings.MinimumJudgementOffsetMilliseconds, + GameSettings.MaximumJudgementOffsetMilliseconds); + if (persist) + PersistSettings(); + } + + private void ChangeAudioOutputLatency(int deltaMilliseconds, bool persist = true) + { + _audioOutputLatencyMilliseconds = Mathf.Clamp( + _audioOutputLatencyMilliseconds + deltaMilliseconds, + GameSettings.MinimumAudioOutputLatencyMilliseconds, + GameSettings.MaximumAudioOutputLatencyMilliseconds); + if (persist) + { + SaveActiveDeviceAudioCalibration( + _audioOutputLatencyMilliseconds, + acceptedSampleCount: 0, + medianAbsoluteDeviationMilliseconds: 0f); + PersistSettings(); + } + } + + private void OpenAudioSyncCalibration() + { + if (_mode != AppMode.Options || _audioSyncCalibration != null) + return; + _audioSyncCalibrationError = string.Empty; + if (_audioBackendFactory is not FmodCoreSongAudioBackendFactory fmodFactory) + { + _audioSyncCalibrationError = + "AUDIO SYNC REQUIRES THE FMOD CORE AUDIO BACKEND."; + return; + } + + try + { + var deviceName = string.IsNullOrWhiteSpace(fmodFactory.ActiveOutputDeviceName) + ? fmodFactory.OutputConfiguration.DeviceName + : fmodFactory.ActiveOutputDeviceName; + _audioSyncCalibration = new AudioSyncCalibrationSession( + fmodFactory.CreateAudioSyncMetronome(_masterVolumePercent * 0.01f), + fmodFactory.Clock, + deviceName); + } + catch (Exception exception) + { + _audioSyncCalibrationError = exception.Message; + Debug.LogError( + $"[ProjectQuint Audio Sync] Could not open calibration: {exception.Message}"); + } + } + + private void StartAudioSyncCalibration() + { + if (_audioSyncCalibration == null) + { + OpenAudioSyncCalibration(); + if (_audioSyncCalibration == null) + return; + } + + DisposeAudioSyncTapAction(); + var keyboard = Keyboard.current; + if (keyboard == null) + { + _audioSyncCalibrationError = "A KEYBOARD IS REQUIRED FOR AUDIO SYNC."; + return; + } + + _audioSyncTapAction = new InputAction( + "Audio Sync Tap", + InputActionType.Button); + var boundNames = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var key in _settings.KeysFor(_optionsInputKeyCount)) + AddAudioSyncBinding(keyboard, key, boundNames); + if (_optionsInputKeyCount == 5) + AddAudioSyncBinding(keyboard, _centerAuxKey, boundNames); + AddAudioSyncBinding(keyboard, _settings.ScratchKey, boundNames); + _audioSyncTapAction.performed += OnAudioSyncTap; + _audioSyncTapAction.Enable(); + _audioSyncCalibration.Begin(); + if (_audioSyncCalibration.Phase != AudioSyncCalibrationPhase.Running) + DisposeAudioSyncTapAction(); + } + + private void AddAudioSyncBinding( + Keyboard keyboard, + Key key, + ISet boundNames) + { + if (!GameSettings.IsAssignableLaneKey(key)) + return; + var controlName = keyboard[key].name; + if (string.IsNullOrWhiteSpace(controlName) || !boundNames.Add(controlName)) + return; + _audioSyncTapAction.AddBinding($"/{controlName}"); + } + + private void OnAudioSyncTap(InputAction.CallbackContext context) + { + _audioSyncCalibration?.RecordTap(context.time, InputState.currentTime); + } + + private void UpdateAudioSyncCalibration() + { + if (_audioSyncCalibration == null) + return; + if (_audioBackendFactory is FmodCoreSongAudioBackendFactory fmodFactory) + fmodFactory.UpdateAudioSystem(); + _audioSyncCalibration.Update(); + if (_audioSyncCalibration.Phase != AudioSyncCalibrationPhase.Running) + DisposeAudioSyncTapAction(); + } + + private void ApplyAudioSyncCalibration() + { + if (_audioSyncCalibration?.Phase != AudioSyncCalibrationPhase.Result || + !_audioSyncCalibration.Result.CanApply) + return; + var result = _audioSyncCalibration.Result; + _audioOutputLatencyMilliseconds = Mathf.Clamp( + result.RecommendedLatencyMilliseconds, + GameSettings.MinimumAudioOutputLatencyMilliseconds, + GameSettings.MaximumAudioOutputLatencyMilliseconds); + SaveActiveDeviceAudioCalibration( + _audioOutputLatencyMilliseconds, + result.AcceptedSampleCount, + (float)result.MedianAbsoluteDeviationMilliseconds, + (float)result.MedianMilliseconds); + PersistSettings(); + Debug.Log( + $"[ProjectQuint Audio Sync] Applied {_audioOutputLatencyMilliseconds} ms to " + + $"'{ActiveAudioOutputDeviceName}' ({result.AcceptedSampleCount} accepted, " + + $"MAD {result.MedianAbsoluteDeviationMilliseconds:0.0} ms)."); + CloseAudioSyncCalibration(); + } + + private void CloseAudioSyncCalibration() + { + DisposeAudioSyncTapAction(); + _audioSyncCalibration?.Dispose(); + _audioSyncCalibration = null; + _audioSyncCalibrationError = string.Empty; + } + + private void DisposeAudioSyncTapAction() + { + if (_audioSyncTapAction == null) + return; + _audioSyncTapAction.performed -= OnAudioSyncTap; + _audioSyncTapAction.Disable(); + _audioSyncTapAction.Dispose(); + _audioSyncTapAction = null; + } + + private AudioSyncCalibrationViewState BuildAudioSyncCalibrationView() + { + if (_audioSyncCalibration == null) + { + return string.IsNullOrWhiteSpace(_audioSyncCalibrationError) + ? default + : new AudioSyncCalibrationViewState( + AudioSyncCalibrationViewPhase.Error, + ActiveAudioOutputDeviceName, + _audioOutputLatencyMilliseconds, + 0, + 0, + 0, + string.Empty, + false, + false, + _audioSyncCalibrationError); + } + + var phase = _audioSyncCalibration.Phase switch + { + AudioSyncCalibrationPhase.Intro => AudioSyncCalibrationViewPhase.Intro, + AudioSyncCalibrationPhase.Running => AudioSyncCalibrationViewPhase.Measuring, + AudioSyncCalibrationPhase.Result => AudioSyncCalibrationViewPhase.Result, + _ => AudioSyncCalibrationViewPhase.Error, + }; + var result = _audioSyncCalibration.Result; + return new AudioSyncCalibrationViewState( + phase, + _audioSyncCalibration.DeviceName, + _audioOutputLatencyMilliseconds, + _audioSyncCalibration.PracticeProgress + + _audioSyncCalibration.MeasurementProgress, + ProjectQuint.Core.Timing.AudioSyncCalibrationEstimator.PracticeClickCount + + ProjectQuint.Core.Timing.AudioSyncCalibrationEstimator.MeasurementClickCount, + result.RecommendedLatencyMilliseconds, + result.Stability == ProjectQuint.Core.Timing.AudioSyncStability.OutOfRange + ? "OUT OF RANGE" + : result.Stability.ToString().ToUpperInvariant(), + result.CanApply, + result.RecommendedLatencyMilliseconds >= 60, + _audioSyncCalibration.Error); + } + + private string ActiveAudioOutputDeviceId => + _audioBackendFactory is FmodCoreSongAudioBackendFactory fmodFactory + ? fmodFactory.ActiveOutputDeviceId + : _audioBackendFactory?.OutputConfiguration.DevicePersistentId ?? string.Empty; + + private string ActiveAudioOutputDeviceName => + _audioBackendFactory is FmodCoreSongAudioBackendFactory fmodFactory && + !string.IsNullOrWhiteSpace(fmodFactory.ActiveOutputDeviceName) + ? fmodFactory.ActiveOutputDeviceName + : _audioBackendFactory?.OutputConfiguration.DeviceName ?? "UNKNOWN DEVICE"; + + private void RestoreActiveDeviceAudioCalibration() + { + var backendId = _audioBackendFactory?.BackendId ?? string.Empty; + var deviceId = ActiveAudioOutputDeviceId; + if (string.IsNullOrWhiteSpace(backendId) || string.IsNullOrWhiteSpace(deviceId)) + return; + var profiles = _settings.AudioOutputCalibrationProfiles; + for (var index = 0; index < profiles.Length; index++) + { + if (profiles[index]?.Matches(backendId, deviceId) != true) + continue; + if (profiles[index].AlgorithmVersion != + AudioOutputCalibrationProfile.CurrentAlgorithmVersion) + { + _audioOutputLatencyMilliseconds = + GameSettingsDefaults.AudioOutputLatencyMilliseconds; + _settings.AudioOutputCalibrationMigrationPending = false; + return; + } + _audioOutputLatencyMilliseconds = profiles[index].OutputLatencyMilliseconds; + _settings.AudioOutputCalibrationMigrationPending = false; + return; + } + if (_settings.AudioOutputCalibrationMigrationPending) + { + SaveActiveDeviceAudioCalibration( + _audioOutputLatencyMilliseconds, + acceptedSampleCount: 0, + medianAbsoluteDeviationMilliseconds: 0f); + _settings.AudioOutputCalibrationMigrationPending = false; + return; + } + _audioOutputLatencyMilliseconds = + GameSettingsDefaults.AudioOutputLatencyMilliseconds; + } + + private void SaveActiveDeviceAudioCalibration( + int latencyMilliseconds, + int acceptedSampleCount, + float medianAbsoluteDeviationMilliseconds, + float medianMilliseconds = 0f) + { + var backendId = _audioBackendFactory?.BackendId ?? string.Empty; + var deviceId = ActiveAudioOutputDeviceId; + if (string.IsNullOrWhiteSpace(backendId) || string.IsNullOrWhiteSpace(deviceId)) + return; + + var profiles = new List( + _settings.AudioOutputCalibrationProfiles ?? Array.Empty()); + AudioOutputCalibrationProfile profile = null; + for (var index = 0; index < profiles.Count; index++) + { + if (profiles[index]?.Matches(backendId, deviceId) != true) + continue; + profile = profiles[index]; + break; + } + if (profile == null) + { + profile = new AudioOutputCalibrationProfile(); + profiles.Add(profile); + } + profile.BackendId = backendId; + profile.AlgorithmVersion = AudioOutputCalibrationProfile.CurrentAlgorithmVersion; + profile.DevicePersistentId = deviceId; + profile.DeviceName = ActiveAudioOutputDeviceName; + profile.OutputLatencyMilliseconds = latencyMilliseconds; + if (acceptedSampleCount > 0) + { + profile.AcceptedSampleCount = acceptedSampleCount; + profile.MedianMilliseconds = medianMilliseconds; + profile.MedianAbsoluteDeviationMilliseconds = + medianAbsoluteDeviationMilliseconds; + profile.CalibratedUtcTicks = DateTime.UtcNow.Ticks; + } + _settings.AudioOutputCalibrationProfiles = profiles.ToArray(); + } + + private void CycleAudioDspBufferProfile(int delta) + { + if (_mode != AppMode.Options || IsAudioSystemResetting) + return; + ApplyAudioDspBufferProfile( + GameSettings.CycleAudioDspBufferProfile(_audioDspBufferProfile, delta), + startup: false); + } + + private void ApplyAudioDspBufferProfile(AudioDspBufferProfile profile, bool startup) + { + if (_audioDspBufferController == null) + return; + + if (!startup && _audioBackendKind == SongAudioBackendKind.FmodCore) + { + ApplyFmodDspBufferProfile(profile); + return; + } + + var applied = _audioDspBufferController.TryApply( + profile, + PrepareForAudioReset, + out var snapshot); + if (!applied) + { + _audioDspStatus = $"RESET FAILED // {_audioDspBufferController.LastError}"; + Debug.LogError($"[ProjectQuint Audio] {_audioDspStatus}"); + return; + } + + _audioDspBufferProfile = profile; + _audioDspStatus = snapshot.IsExactBufferMatch + ? $"{GameSettings.AudioDspBufferProfileLabel(profile)} APPLIED" + : $"DEVICE USED {snapshot.BufferLength} SAMPLES FOR {snapshot.RequestedBufferSamples} REQUEST"; + if (!startup) + PersistSettings(); + Debug.Log( + $"[ProjectQuint Audio] DSP profile {GameSettings.AudioDspBufferProfileLabel(profile)}; " + + $"{snapshot.AppliedLabel}; exact match {snapshot.IsExactBufferMatch}."); + } + + private bool PrepareForAudioReset() + { + if (_mode != AppMode.Home && _mode != AppMode.Options) + return false; + CloseAudioSyncCalibration(); + ClearLoadedSongState(); + return true; + } + + private void ApplyFmodDspBufferProfile(AudioDspBufferProfile profile) + { + RestartFmodCore( + profile, + _settings.AudioOutputDeviceId, + _settings.AudioOutputDeviceName, + deviceSelectionChange: false); + } + + private void CycleAudioOutputDevice(int delta) + { + if (_mode != AppMode.Options || IsAudioSystemResetting || + _audioBackendFactory is not FmodCoreSongAudioBackendFactory fmodFactory) + return; + + try + { + fmodFactory.RefreshOutputDevices(); + } + catch (Exception exception) + { + _audioDspStatus = $"DEVICE LIST FAILED // {exception.Message}"; + Debug.LogError($"[ProjectQuint Audio] {_audioDspStatus}"); + return; + } + + var devices = fmodFactory.OutputDevices; + if (devices == null || devices.Count == 0) + { + _audioDspStatus = "NO COREAUDIO OUTPUT DEVICES FOUND"; + return; + } + + var currentIndex = 0; + if (!string.IsNullOrWhiteSpace(_settings.AudioOutputDeviceId)) + { + currentIndex = -1; + for (var index = 0; index < devices.Count; index++) + { + if (!string.Equals( + devices[index].PersistentId, + _settings.AudioOutputDeviceId, + StringComparison.OrdinalIgnoreCase)) + continue; + currentIndex = index; + break; + } + if (currentIndex < 0) + currentIndex = 0; + } + + var direction = delta < 0 ? -1 : 1; + var targetIndex = ((currentIndex + direction) % devices.Count + devices.Count) % devices.Count; + var target = devices[targetIndex]; + RestartFmodCore( + _audioDspBufferProfile, + target.PersistentId, + target.IsSystemDefault ? string.Empty : target.Name, + deviceSelectionChange: true); + } + + private void RefreshAudioOutputDevices() + { + if (_audioBackendFactory is not FmodCoreSongAudioBackendFactory fmodFactory || + IsAudioSystemResetting) + return; + try + { + fmodFactory.RefreshOutputDevices(); + } + catch (Exception exception) + { + _audioDspStatus = $"DEVICE LIST FAILED // {exception.Message}"; + Debug.LogError($"[ProjectQuint Audio] {_audioDspStatus}"); + } + } + + private void RestartFmodCore( + AudioDspBufferProfile profile, + string requestedOutputDeviceId, + string requestedOutputDeviceName, + bool deviceSelectionChange) + { + profile = Enum.IsDefined(typeof(AudioDspBufferProfile), profile) + ? profile + : GameSettingsDefaults.AudioDspBuffer; + if (!PrepareForAudioReset()) + { + _audioDspStatus = "RESET BLOCKED // RETURN TO HOME OR OPTIONS"; + return; + } + + _audioBackendResetting = true; + FmodCoreSongAudioBackendFactory candidate = null; + try + { + candidate = new FmodCoreSongAudioBackendFactory( + (uint)GameSettings.RequestedDspBufferSamples(profile), + requestedOutputDeviceId); + var output = candidate.OutputConfiguration; + var previousFactory = _audioBackendFactory; + + _audioBackendFactory = candidate; + _audioClock = candidate.Clock; + _songLoadCoordinator = new SongLoadCoordinator(gameObject, candidate); + _audioBackendInitializationError = string.Empty; + _audioDspBufferProfile = profile; + if (candidate.DeviceSelectionFallback) + { + _settings.AudioOutputDeviceId = string.Empty; + _settings.AudioOutputDeviceName = string.Empty; + _audioDspStatus = "SAVED DEVICE UNAVAILABLE // SYSTEM DEFAULT APPLIED"; + } + else + { + _settings.AudioOutputDeviceId = candidate.SelectedOutputDeviceId; + _settings.AudioOutputDeviceName = string.IsNullOrWhiteSpace(candidate.SelectedOutputDeviceId) + ? string.Empty + : requestedOutputDeviceName ?? string.Empty; + _audioDspStatus = deviceSelectionChange + ? $"OUTPUT DEVICE APPLIED // {output.DeviceName}" + : FormatAudioDspStatus(profile, output); + } + candidate = null; + RestoreActiveDeviceAudioCalibration(); + + if (previousFactory is IDisposable disposablePrevious) + { + try + { + disposablePrevious.Dispose(); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + + PersistSettings(); + Debug.Log( + $"[ProjectQuint Audio] FMOD Core restarted; {output.AppliedLabel}; " + + $"saved device '{_settings.AudioOutputDeviceName}'."); + } + catch (Exception exception) + { + candidate?.Dispose(); + _audioDspStatus = $"FMOD RESET FAILED // {exception.Message}"; + Debug.LogError($"[ProjectQuint Audio] {_audioDspStatus}"); + } + finally + { + _audioBackendResetting = false; + } + } + + private static string FormatAudioDspStatus( + AudioDspBufferProfile profile, + AudioOutputConfigurationSnapshot output) + { + var requestedSamples = GameSettings.RequestedDspBufferSamples(profile); + return output.BufferLength == requestedSamples + ? $"{GameSettings.AudioDspBufferProfileLabel(profile)} APPLIED TO {output.BackendId}" + : $"{output.BackendId} USED {output.BufferLength} SAMPLES FOR {requestedSamples} REQUEST"; + } + + private void HandleExternalAudioConfigurationChange() + { + if (_audioDspBufferController?.ConsumeExternalDeviceChange() != true) + return; + + var snapshot = _audioDspBufferController.Snapshot; + var hadActiveSong = _preparedSong != null || _mode == AppMode.Loading || _mode == AppMode.Gameplay || + _mode == AppMode.Paused || _mode == AppMode.Results || _mode == AppMode.GameOver; + if (hadActiveSong) + { + ClearLoadedSongState(); + _courseSession.ClearRun(); + _mode = _libraryReady ? AppMode.Library : AppMode.Home; + RefreshLibraryThumbnail(); + } + + _audioDspStatus = "AUDIO DEVICE CHANGED // GENERATED AUDIO RELOAD REQUIRED"; + if (_audioBackendFactory is FmodCoreSongAudioBackendFactory fmodFactory) + fmodFactory.UpdateAudioSystem(); + RestoreActiveDeviceAudioCalibration(); + Debug.LogWarning( + $"[ProjectQuint Audio] Output device configuration changed; {snapshot.AppliedLabel}. " + + $"Active generated song resources discarded: {hadActiveSong}."); + } + + private void ChangeMasterVolume(int volumePercent, bool persist = true) + { + var clamped = Mathf.Clamp( + volumePercent, + GameSettings.MinimumMasterVolumePercent, + GameSettings.MaximumMasterVolumePercent); + if (_masterVolumePercent == clamped) + return; + + _masterVolumePercent = clamped; + ApplyMasterVolume(); + if (persist) + PersistSettings(); + } + + private void ApplyMasterVolume() + { + var normalizedVolume = _masterVolumePercent * 0.01f; + AudioListener.volume = normalizedVolume; + _audio?.SetMasterVolume(normalizedVolume); + } + + private void ConfigureAudioBackend(string[] commandLineArguments) + { + _audioBackendInitializationError = string.Empty; + if (!AudioBackendSelection.TryParse( + commandLineArguments, + out var backendKind, + out var parseError)) + { + _audioBackendInitializationError = parseError; + } + _audioBackendKind = backendKind; + + if (string.IsNullOrWhiteSpace(_audioBackendInitializationError)) + { + try + { + _audioBackendFactory = backendKind == SongAudioBackendKind.FmodCore + ? new FmodCoreSongAudioBackendFactory( + (uint)GameSettings.RequestedDspBufferSamples(_audioDspBufferProfile), + _settings.AudioOutputDeviceId) + : new UnitySongAudioBackendFactory(); + } + catch (Exception exception) + { + _audioBackendInitializationError = exception.Message; + } + } + + if (_audioBackendFactory == null) + { + // The Unity factory exists only to provide a valid inert composition graph. + // LoadSong refuses to play while the explicit initialization error is present. + _audioBackendFactory = new UnitySongAudioBackendFactory(); + Debug.LogError( + $"[ProjectQuint Audio] Requested backend is unavailable: " + + _audioBackendInitializationError); + } + + _audioClock = _audioBackendFactory.Clock; + _songLoadCoordinator = new SongLoadCoordinator(gameObject, _audioBackendFactory); + if (string.IsNullOrWhiteSpace(_audioBackendInitializationError)) + { + if (_audioBackendFactory is FmodCoreSongAudioBackendFactory fmodFactory && + fmodFactory.DeviceSelectionFallback) + { + _settings.AudioOutputDeviceId = string.Empty; + _settings.AudioOutputDeviceName = string.Empty; + _audioDspStatus = "SAVED DEVICE UNAVAILABLE // SYSTEM DEFAULT APPLIED"; + } + else + { + _audioDspStatus = FormatAudioDspStatus( + _audioDspBufferProfile, + _audioBackendFactory.OutputConfiguration); + } + } + Debug.Log( + $"[ProjectQuint Audio] Selected backend {_audioBackendFactory.BackendId}/" + + $"{_audioClock.ClockId}; {_audioBackendFactory.OutputConfiguration.AppliedLabel}."); + } + + private void ToggleReactiveKeySounds() + { + _reactiveKeySoundsEnabled = !_reactiveKeySoundsEnabled; + PersistSettings(); + } + + private void CycleFrameLimit(int delta) + { + const int count = 4; + var value = ((int)_frameLimitMode + delta) % count; + if (value < 0) value += count; + _frameLimitMode = (FrameLimitMode)value; + ApplyFrameLimit(); + PersistSettings(); + } + + private void ApplyFrameLimit() + { + // Unity uses vSyncCount instead of targetFrameRate when VSync is enabled. + QualitySettings.vSyncCount = _vSyncEnabled ? 1 : 0; + var refreshRate = Screen.currentResolution.refreshRateRatio.value; + _appliedDisplayRefreshRate = refreshRate; + _effectiveTargetFrameRate = GameSettings.EffectiveTargetFrameRate( + _frameLimitMode, + refreshRate, + _vSyncEnabled, + UsesDisplayLinkedPresentation); + Application.targetFrameRate = _effectiveTargetFrameRate; + Debug.Log( + $"[ProjectQuint Display] Frame policy: requested " + + $"{FormatFrameRate(GameSettings.TargetFrameRate(_frameLimitMode))}, effective " + + $"{FormatFrameRate(_effectiveTargetFrameRate)}, display {refreshRate:0.##}Hz, " + + $"VSync {(_vSyncEnabled ? "ON" : "OFF")}, " + + $"DisplayLink {(UsesDisplayLinkedPresentation ? "ON" : "OFF")}."); + } + + private void ReapplyFrameLimitWhenDisplayRefreshRateChanges() + { + if (!UsesDisplayLinkedPresentation) + return; + + var now = Time.realtimeSinceStartupAsDouble; + if (now < _nextDisplayRefreshCheckRealtime) + return; + + _nextDisplayRefreshCheckRealtime = now + 0.25; + var currentRefreshRate = Screen.currentResolution.refreshRateRatio.value; + if (!GameSettings.DisplayRefreshRateChanged( + _appliedDisplayRefreshRate, + currentRefreshRate)) + return; + + ApplyFrameLimit(); + } + + private static string FormatFrameRate(int framesPerSecond) => + framesPerSecond > 0 ? $"{framesPerSecond} FPS" : "UNLIMITED"; + + private static bool UsesDisplayLinkedPresentation + { + get + { +#if UNITY_STANDALONE_OSX && !UNITY_EDITOR + return true; +#else + return false; +#endif + } + } + + private void ToggleVSync() + { + _vSyncEnabled = !_vSyncEnabled; + ApplyFrameLimit(); + PersistSettings(); + } + + private void CycleDisplayResolution(int delta) + { + const int count = 4; + var value = ((int)_displayResolutionMode + delta) % count; + if (value < 0) value += count; + _displayResolutionMode = (DisplayResolutionMode)value; + PersistSettings(); +#if !UNITY_EDITOR + ApplyResolution(); +#endif + } + + private void ApplyResolution() + { + var size = GameSettings.ResolutionSize(_displayResolutionMode); + Screen.SetResolution(size.x, size.y, GameSettings.FullScreenModeFor(_windowMode)); + } + + private void CycleWindowMode(int delta) + { + const int count = 2; + var value = ((int)_windowMode + delta) % count; + if (value < 0) value += count; + _windowMode = (WindowMode)value; + PersistSettings(); +#if !UNITY_EDITOR + ApplyResolution(); +#endif + } + + private void ToggleFpsCounter() + { + _showFpsCounter = !_showFpsCounter; + _fpsSampleFrames = 0; + _fpsSampleElapsed = 0f; + _displayedFps = 0f; + PersistSettings(); + } + + private void CycleBgaPlaybackMode(int delta) + { + const int count = 3; + var value = ((int)_bgaPlaybackMode + delta) % count; + if (value < 0) value += count; + _bgaPlaybackMode = (BgaPlaybackMode)value; + if (!DynamicBgaEnabled) + _bga?.Clear(); + PersistSettings(); + } + + private void ChangeBackgroundDim(int deltaPercent, bool persist = true) + { + _backgroundDimPercent = Mathf.Clamp(_backgroundDimPercent + deltaPercent, 0, 90); + if (persist) + PersistSettings(); + } + + private void UpdateFpsSample() + { + if (!_showFpsCounter) + return; + + _fpsSampleFrames++; + _fpsSampleElapsed += Time.unscaledDeltaTime; + if (_fpsSampleElapsed < 0.35f) + return; + + _displayedFps = CalculateFrameRate(_fpsSampleFrames, _fpsSampleElapsed); + _fpsSampleFrames = 0; + _fpsSampleElapsed = 0f; + } + + internal static float CalculateFrameRate(int frames, float elapsedSeconds) => + frames <= 0 || elapsedSeconds <= 0f ? 0f : frames / elapsedSeconds; + + private void AssignInputBinding(int binding, Key key) + { + var changed = binding switch + { + GameSettings.CenterAuxBindingIndex when _optionsInputKeyCount == 5 => + _settings.AssignCenterAuxKey(key), + GameSettings.ScratchBindingIndex => _settings.AssignScratchKey(key), + _ => _settings.AssignProfileKey(_optionsInputKeyCount, binding, key), + }; + if (!changed) + return; + PersistSettings(); + RefreshLaneKeyLabels(); + } + + private void CycleInputProfile(int delta) + { + _optionsInputKeyCount = GameSettings.CycleInputKeyCount(_optionsInputKeyCount, delta); + _optionsSelectedRow = 0; + _rebindLane = -1; + RefreshLaneKeyLabels(); + } + + private void CycleOptionsCategory(int delta) => + SetOptionsCategory(ProjectQuintPresentation.CycleOptionsCategory(_optionsCategory, delta)); + + private void SetOptionsCategory(OptionsCategory category) + { + var normalized = ProjectQuintPresentation.CycleOptionsCategory(category, 0); + if (_optionsCategory == normalized) + return; + _optionsCategory = normalized; + _optionsSelectedRow = 0; + _optionsValueKeyRepeat.Reset(); + _rebindLane = -1; + _profileImportConfirmationPending = false; + if (_optionsCategory == OptionsCategory.Audio) + RefreshAudioOutputDevices(); + } + + private void ChangeSelectedOption(int direction) + { + direction = direction < 0 ? -1 : 1; + switch (_optionsCategory) + { + case OptionsCategory.Gameplay: + if (_optionsSelectedRow == 0) + ChangeNoteSpeed(direction * 0.1f); + else + ChangeJudgementOffset(direction); + break; + case OptionsCategory.Input: + if (_optionsSelectedRow == 0) + CycleInputProfile(direction); + else + _rebindLane = ProjectQuintPresentation.InputBindingForRow( + _optionsSelectedRow, + _optionsInputKeyCount); + break; + case OptionsCategory.Latency: + switch (_optionsSelectedRow) + { + case 0: + CycleInputPollingRate(direction); + break; + case 1: + CycleAudioDspBufferProfile(direction); + break; + case 2: + ChangeAudioOutputLatency(direction); + break; + case 3: + OpenAudioSyncCalibration(); + break; + } + break; + case OptionsCategory.Audio: + switch (_optionsSelectedRow) + { + case 0: + ChangeMasterVolume(_masterVolumePercent + direction); + break; + case 1: + ToggleReactiveKeySounds(); + break; + case 2: + CycleAudioOutputDevice(direction); + break; + case 3: + CycleAudioDspBufferProfile(direction); + break; + case 4: + ChangeAudioOutputLatency(direction); + break; + case 5: + RequestClearPremixCache(); + break; + case 6: + OpenPackCacheManager(); + break; + } + break; + case OptionsCategory.Display: + switch (_optionsSelectedRow) + { + case 0: + CycleFrameLimit(direction); + break; + case 1: + ToggleVSync(); + break; + case 2: + CycleDisplayResolution(direction); + break; + case 3: + CycleWindowMode(direction); + break; + case 4: + ToggleFpsCounter(); + break; + case 5: + CycleBgaPlaybackMode(direction); + break; + case 6: + CycleBgaDisplayMode(direction); + break; + case 7: + ChangeBackgroundDim(direction * 5); + break; + } + break; + case OptionsCategory.Data: + if (_optionsSelectedRow == 0) + ExportProfileArchive(); + else if (_optionsSelectedRow == 1) + RequestOrImportProfileArchive(); + else + RequestUpdateCheck(manual: true); + break; + } + } + + private bool IsSelectedOptionContinuous() => _optionsCategory switch + { + OptionsCategory.Gameplay => _optionsSelectedRow is 0 or 1, + OptionsCategory.Latency => _optionsSelectedRow == 2, + OptionsCategory.Audio => _optionsSelectedRow is 0 or 4, + OptionsCategory.Display => _optionsSelectedRow == 7, + _ => false, + }; + + private void ChangeSelectedContinuousOption(int stepCount) + { + if (stepCount == 0) + return; + + switch (_optionsCategory) + { + case OptionsCategory.Gameplay when _optionsSelectedRow == 0: + ChangeNoteSpeed(stepCount * 0.1f, persist: false); + break; + case OptionsCategory.Gameplay when _optionsSelectedRow == 1: + ChangeJudgementOffset(stepCount, persist: false); + break; + case OptionsCategory.Latency when _optionsSelectedRow == 2: + ChangeAudioOutputLatency(stepCount, persist: false); + break; + case OptionsCategory.Audio when _optionsSelectedRow == 0: + ChangeMasterVolume(_masterVolumePercent + stepCount, persist: false); + break; + case OptionsCategory.Audio when _optionsSelectedRow == 4: + ChangeAudioOutputLatency(stepCount, persist: false); + break; + case OptionsCategory.Display when _optionsSelectedRow == 7: + ChangeBackgroundDim(stepCount * 5, persist: false); + break; + } + _continuousOptionsDirty = true; + } + + private void CommitContinuousOptionsIfNeeded() + { + if (!_continuousOptionsDirty) + return; + _continuousOptionsDirty = false; + if ((_optionsCategory == OptionsCategory.Latency && _optionsSelectedRow == 2) || + (_optionsCategory == OptionsCategory.Audio && _optionsSelectedRow == 4)) + { + SaveActiveDeviceAudioCalibration( + _audioOutputLatencyMilliseconds, + acceptedSampleCount: 0, + medianAbsoluteDeviationMilliseconds: 0f); + } + PersistSettings(); + } + + private void ActivateSelectedOption() => ChangeSelectedOption(1); + + private void CycleInputPollingRate(int direction) + { + ApplyInputPollingRate( + GameSettings.CycleInputPollingRate(_inputPollingRate, direction), + startup: false); + } + + private void ApplyInputPollingRate(InputPollingRate rate, bool startup) + { + _inputPollingRate = Enum.IsDefined(typeof(InputPollingRate), rate) + ? rate + : GameSettingsDefaults.InputPolling; +#if UNITY_STANDALONE_OSX && !UNITY_EDITOR + _inputPollingStatus = + $"NATIVE macOS GAMEPLAY SAMPLER // TARGET {GameSettings.InputPollingRateHz(_inputPollingRate)} Hz"; +#elif UNITY_EDITOR + _inputPollingStatus = "STANDALONE PLAYER ONLY // EDITOR USES UNITY INPUT EVENTS"; +#else + _inputPollingStatus = "NATIVE POLLING BACKEND IS CURRENTLY AVAILABLE ON macOS ONLY"; +#endif + if (!startup) + PersistSettings(); + Debug.Log($"[ProjectQuint Input] {_inputPollingStatus}"); + } + + private void RefreshPremixCacheStatus(string prefix = null) + { + try + { + var label = PremixedSongCache.Shared.GetSnapshot().Label; + _premixCacheStatus = string.IsNullOrWhiteSpace(prefix) ? label : $"{prefix} // {label}"; + } + catch (Exception exception) + { + _premixCacheStatus = $"CACHE STATUS FAILED // {exception.Message}"; + } + } + + private void RequestClearPremixCache() + { + if (_mode != AppMode.Options || _premixCacheClearing) + return; + StartCoroutine(ClearPremixCache()); + } + + private IEnumerator ClearPremixCache() + { + _premixCacheClearing = true; + _premixCacheStatus = "CLEARING CACHE..."; + var clearTask = Task.Run(() => PremixedSongCache.Shared.Clear()); + while (!clearTask.IsCompleted) + yield return null; + _premixCacheClearing = false; + if (clearTask.IsFaulted) + { + _premixCacheStatus = + $"CACHE CLEAR FAILED // {clearTask.Exception?.GetBaseException().Message}"; + Debug.LogError($"[ProjectQuint Audio] {_premixCacheStatus}"); + yield break; + } + RefreshPremixCacheStatus("CACHE CLEARED"); + Debug.Log("[ProjectQuint Audio] Automatic premix cache cleared; approved pinned packs preserved."); + } + + private void RequestPinnedPackCacheEstimate() + { + if (_mode != AppMode.Options || _pinnedPackCacheWorking || + _pinnedPackCacheConfirmationPending) + return; + var pack = SelectedPackCacheDefinition(); + if (pack == null || !pack.IsAvailable) + { + _pinnedPackCacheStatus = "SELECTED PACK SOURCE IS UNAVAILABLE"; + return; + } + StartCoroutine(AnalyzePinnedPackCache(pack)); + } + + private IEnumerator AnalyzePinnedPackCache(BmsPackDefinition pack) + { + _pinnedPackCacheWorking = true; + _pinnedPackCacheStatus = $"INDEXING {pack.DisplayName}..."; + _pinnedPackCacheCancellation?.Cancel(); + _pinnedPackCacheCancellation?.Dispose(); + _pinnedPackCacheCancellation = new CancellationTokenSource(); + var token = _pinnedPackCacheCancellation.Token; + List entries; + if (_libraryReady && _activePack != null && + string.Equals(_activePack.Id, pack.Id, StringComparison.OrdinalIgnoreCase)) + { + entries = _libraryController.State.AllSongs.ToList(); + } + else + { + var scanner = new SongLibraryScanner( + _packRegistry.CachePathFor(pack), + legacyCachePath: null, + pack.Id, + pack.ForcedKeyCount); + var scanTask = Task.Run(() => scanner.Scan(pack.ContentRoot, token), token); + while (!scanTask.IsCompleted) + { + _pinnedPackCacheStatus = + $"INDEXING {pack.DisplayName} // {scanner.Completed:N0}/{scanner.Total:N0}"; + yield return null; + } + if (scanTask.IsCanceled) + { + _pinnedPackCacheWorking = false; + _pinnedPackCacheStatus = "PACK CACHE INDEX CANCELLED"; + yield break; + } + if (scanTask.IsFaulted) + { + _pinnedPackCacheWorking = false; + _pinnedPackCacheStatus = + $"PACK INDEX FAILED // {scanTask.Exception?.GetBaseException().Message}"; + yield break; + } + entries = scanTask.Result; + } + if (entries.Count == 0) + { + _pinnedPackCacheWorking = false; + _pinnedPackCacheStatus = "SELECTED PACK CONTAINS NO PLAYABLE CHARTS"; + yield break; + } + _pinnedPackCacheStatus = $"ANALYZING {pack.DisplayName} // {entries.Count:N0} CHARTS..."; + var sampleRate = _audioBackendFactory?.OutputConfiguration.SampleRate ?? 0; + if (sampleRate <= 0) + sampleRate = AudioSettings.outputSampleRate; + var task = _pinnedPackCacheService.AnalyzeAsync( + pack, + entries, + sampleRate, + token); + while (!task.IsCompleted) + yield return null; + _pinnedPackCacheWorking = false; + if (task.IsCanceled) + { + _pinnedPackCacheStatus = "PINNED PACK ESTIMATE CANCELLED"; + yield break; + } + if (task.IsFaulted) + { + _pinnedPackCacheStatus = + $"ESTIMATE FAILED // {task.Exception?.GetBaseException().Message}"; + yield break; + } + + _pendingPinnedPackCachePlan = task.Result; + _pinnedPackCacheStatus = _pendingPinnedPackCachePlan.Summary; + if (_pendingPinnedPackCachePlan.AvailableBytes > 0 && + _pendingPinnedPackCachePlan.EstimatedAdditionalBytes > + _pendingPinnedPackCachePlan.AvailableBytes) + _pinnedPackCacheStatus += "\nWARNING // ESTIMATE EXCEEDS CURRENT FREE SPACE"; + _pinnedPackCacheConfirmationPending = true; + } + + private void ConfirmPinnedPackCache() + { + if (!_pinnedPackCacheConfirmationPending || _pendingPinnedPackCachePlan == null || + _pinnedPackCacheWorking) + return; + _pinnedPackCacheConfirmationPending = false; + StartCoroutine(BuildPinnedPackCache(_pendingPinnedPackCachePlan)); + } + + private IEnumerator BuildPinnedPackCache(PinnedPackCachePlan plan) + { + _pinnedPackCacheWorking = true; + _pinnedPackCacheCancellation?.Cancel(); + _pinnedPackCacheCancellation?.Dispose(); + _pinnedPackCacheCancellation = new CancellationTokenSource(); + var token = _pinnedPackCacheCancellation.Token; + var completedStatus = string.Empty; + yield return _pinnedPackCacheService.Build( + plan, + progress => _pinnedPackCacheStatus = + $"{progress.Current:N0}/{progress.Total:N0} // {progress.Detail}", + () => token.IsCancellationRequested, + status => completedStatus = status); + _pinnedPackCacheWorking = false; + _pendingPinnedPackCachePlan = null; + _pinnedPackCacheStatus = string.IsNullOrWhiteSpace(completedStatus) + ? "PINNED PACK CACHE STOPPED" + : completedStatus; + RefreshPremixCacheStatus(); + RefreshPackCacheManagerStatuses(); + } + + private void CancelPinnedPackCache() + { + _pinnedPackCacheConfirmationPending = false; + _pendingPinnedPackCachePlan = null; + _pinnedPackCacheCancellation?.Cancel(); + _pinnedPackCacheStatus = _pinnedPackCacheWorking + ? "CANCELLING PINNED PACK CACHE..." + : "PINNED PACK CACHE CANCELLED"; + } + + private void OpenPackCacheManager() + { + if (_mode != AppMode.Options || _pinnedPackCacheWorking) + return; + _packCacheManagerOpen = true; + var packs = _packRegistry?.Packs; + if (packs != null && packs.Count > 0 && _activePack != null) + { + var activeIndex = packs.ToList().FindIndex(pack => + string.Equals(pack.Id, _activePack.Id, StringComparison.OrdinalIgnoreCase)); + if (activeIndex >= 0) + _packCacheSelection = activeIndex; + } + RefreshPackCacheManagerStatuses(); + } + + private void ClosePackCacheManager() + { + if (_pinnedPackCacheWorking || _pinnedPackCacheConfirmationPending) + return; + _packCacheManagerOpen = false; + } + + private void SetPackCacheSelection(int index) + { + var count = _packRegistry?.Packs.Count ?? 0; + _packCacheSelection = count <= 0 ? 0 : Mathf.Clamp(index, 0, count - 1); + } + + private BmsPackDefinition SelectedPackCacheDefinition() + { + var packs = _packRegistry?.Packs; + if (packs == null || packs.Count == 0) + return null; + _packCacheSelection = Mathf.Clamp(_packCacheSelection, 0, packs.Count - 1); + return packs[_packCacheSelection]; + } + + private void RefreshPackCacheManagerStatuses() + { + var packs = _packRegistry?.Packs ?? Array.Empty(); + try + { + _packCacheStatuses = _pinnedPackCacheService.GetPackStatuses(packs); + _packCacheUiEntries = _packCacheStatuses.Select(status => new PackCacheUiEntry( + status.Pack?.DisplayName ?? "UNKNOWN PACK", + status.IsPinned, + status.ObjectCount, + status.SharedObjectCount, + status.MissingObjectCount, + PremixedSongCacheSnapshot.FormatBytes(status.ReferencedBytes), + PremixedSongCacheSnapshot.FormatBytes(status.ExclusiveBytes), + status.CreatedUtc == default + ? "-" + : status.CreatedUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm"))).ToArray(); + SetPackCacheSelection(_packCacheSelection); + } + catch (Exception exception) + { + _pinnedPackCacheStatus = $"PACK CACHE STATUS FAILED // {exception.Message}"; + _packCacheStatuses = Array.Empty(); + _packCacheUiEntries = Array.Empty(); + } + } + + private void RequestDeleteSelectedPackCache() + { + if (!_packCacheManagerOpen || _pinnedPackCacheWorking) + return; + var pack = SelectedPackCacheDefinition(); + var status = _packCacheStatuses.FirstOrDefault(item => + item.Pack != null && pack != null && + string.Equals(item.Pack.Id, pack.Id, StringComparison.OrdinalIgnoreCase)); + if (pack == null || !status.IsPinned) + { + _pinnedPackCacheStatus = "SELECTED PACK HAS NO PINNED CACHE"; + return; + } + StartCoroutine(DeleteSelectedPackCache(pack)); + } + + private IEnumerator DeleteSelectedPackCache(BmsPackDefinition pack) + { + _pinnedPackCacheWorking = true; + _pinnedPackCacheStatus = $"REMOVING {pack.DisplayName} CACHE..."; + var task = Task.Run(() => _pinnedPackCacheService.DeletePackCache(pack.Id)); + while (!task.IsCompleted) + yield return null; + _pinnedPackCacheWorking = false; + if (task.IsFaulted) + _pinnedPackCacheStatus = + $"DELETE FAILED // {task.Exception?.GetBaseException().Message}"; + else + _pinnedPackCacheStatus = task.Result.Status; + RefreshPremixCacheStatus(); + RefreshPackCacheManagerStatuses(); + } + + private void ExportProfileArchive() + { + _profileImportConfirmationPending = false; + var defaultName = $"ProjectQuint-{PersistencePaths.DefaultProfileId}-{DateTime.Now:yyyyMMdd-HHmmss}" + + ProfileArchiveService.FileExtension; + var destination = NativeProfileArchivePicker.ChooseExportPath(defaultName); + if (string.IsNullOrWhiteSpace(destination)) + { + _profileTransferStatus = "PROFILE EXPORT CANCELLED"; + return; + } + + var result = _profileArchiveService.Export(PersistencePaths.DefaultProfileId, destination); + _profileTransferStatus = result.Status; + } + + private void RequestOrImportProfileArchive() + { + if (!_profileImportConfirmationPending) + { + _profileImportConfirmationPending = true; + _profileTransferStatus = "IMPORT REPLACES CURRENT RECORDS // CONFIRM ONCE MORE"; + return; + } + + _profileImportConfirmationPending = false; + var source = NativeProfileArchivePicker.ChooseImportPath(); + if (string.IsNullOrWhiteSpace(source)) + { + _profileTransferStatus = "PROFILE IMPORT CANCELLED"; + return; + } + + var result = _profileArchiveService.Import(source, PersistencePaths.DefaultProfileId); + _profileTransferStatus = result.Status; + if (result.Success) + ReloadImportedProfileState(); + } + + private void ReloadImportedProfileState() + { + SongRecordRepository.Shared.Reload(); + CourseRecordRepository.Shared.Reload(); + _progressionRepository.Reload(); + _favoriteRepository.Reload(); + + var songs = _libraryController?.State?.AllSongs; + if (songs != null) + { + _favoriteRepository.Apply(songs); + foreach (var song in songs) + { + song.ClearLamp = SongClearLampStore.Load(song); + song.BestRecord = SongPlayRecordStore.Load(song); + } + SongRecordRepository.Shared.EnrichChartMetadata(songs, _activePack?.DisplayName); + _courseIndex?.ResolveSongs(songs); + } + RefreshProfileSummary(); + LoadProfileImage(); + } + + private void RefreshLaneKeyLabels() + { + Array.Clear(_laneKeyLabels, 0, _laneKeyLabels.Length); + var keys = _settings.KeysFor(_optionsInputKeyCount); + for (var lane = 0; lane < keys.Length && lane < _laneKeyLabels.Length; lane++) + _laneKeyLabels[lane] = GameSettings.FormatLaneKeyLabel(keys[lane]); + } + + private void TryStartAutomaticUpdateCheck() + { + if (_automaticUpdateCheckStarted || + _settings == null || + _mode != AppMode.Home) + return; + + _automaticUpdateCheckStarted = true; + if (Application.isEditor) + return; + if (!ProjectQuintUpdateService.ShouldCheckAutomatically( + _settings.LastUpdateCheckUtcTicks, + DateTime.UtcNow.Ticks)) + { + _updateStatus = "CHECKED RECENTLY"; + return; + } + + RequestUpdateCheck(manual: false); + } + + private void RequestUpdateCheck(bool manual) + { + if (_updateCheckRunning) + { + if (manual) + _updateStatus = "CHECKING..."; + return; + } + + if (manual) + { + _updateDismissedThisRun = false; + _updateOfferPending = false; + } + _updateStatus = "CHECKING..."; + StartCoroutine(CheckForUpdates(manual)); + } + + private IEnumerator CheckForUpdates(bool manual) + { + _updateCheckRunning = true; + ProjectQuintUpdateCheckResult result = default; + yield return ProjectQuintUpdateService.Check(value => result = value); + _updateCheckRunning = false; + + if (!result.Succeeded) + { + if (manual) + _updateStatus = "CHECK FAILED"; + else + _updateStatus = "NOT CHECKED"; + Debug.LogWarning($"[ProjectQuint Update] {result.Error}"); + yield break; + } + + _settings.LastUpdateCheckUtcTicks = DateTime.UtcNow.Ticks; + PersistSettings(); + _availableUpdate = result.Manifest; + + if (!ProjectQuintUpdateService.IsUpdateAvailable(_availableUpdate)) + { + _updateStatus = $"UP TO DATE // {ProjectQuintReleaseVersion.DisplayVersion.ToUpperInvariant()}"; + yield break; + } + + _updateStatus = $"AVAILABLE // {_availableUpdate.version.ToUpperInvariant()}"; + if (ProjectQuintUpdateService.ShouldOffer( + _availableUpdate, + _settings.SkippedUpdateVersion, + manual)) + _updateOfferPending = true; + } + + private void TryPresentPendingUpdate() + { + if (!_updateOfferPending || + _updateModalOpen || + _updateDismissedThisRun || + _availableUpdate == null) + return; + + var safeHome = _mode == AppMode.Home && !_profileModalOpen; + var safeOptions = + _mode == AppMode.Options && + _audioSyncCalibration == null && + string.IsNullOrWhiteSpace(_audioSyncCalibrationError) && + !_packCacheManagerOpen && + !_profileImportConfirmationPending; + if (!safeHome && !safeOptions) + return; + + _updateOfferPending = false; + _updateModalSelection = 0; + _updateModalOpen = true; + } + + private void UpdateUpdateNoticeInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (keyboard.escapeKey.wasPressedThisFrame) + { + HandleUpdateNoticeAction(UpdateNoticeAction.Later); + return; + } + + if (keyboard.leftArrowKey.wasPressedThisFrame || + keyboard.upArrowKey.wasPressedThisFrame) + _updateModalSelection = (_updateModalSelection + 2) % 3; + else if (keyboard.rightArrowKey.wasPressedThisFrame || + keyboard.downArrowKey.wasPressedThisFrame) + _updateModalSelection = (_updateModalSelection + 1) % 3; + + if (keyboard.enterKey.wasPressedThisFrame || + keyboard.numpadEnterKey.wasPressedThisFrame) + HandleUpdateNoticeAction((UpdateNoticeAction)_updateModalSelection); + } + + private void HandleUpdateNoticeAction(UpdateNoticeAction action) + { + if (!_updateModalOpen || _availableUpdate == null) + return; + + switch (action) + { + case UpdateNoticeAction.OpenDownload: + var sparkleError = "Sparkle updater is not available."; + if (_sparkleUpdater != null && + _sparkleUpdater.TryCheckForUpdates(out sparkleError)) + { + _updateStatus = "OPENING SECURE UPDATE INSTALLER..."; + } + else + { + Debug.LogWarning( + $"[ProjectQuint] Sparkle update fallback: {sparkleError}"); + Application.OpenURL(ProjectQuintUpdateService.DownloadPageUrl); + } + break; + case UpdateNoticeAction.Skip: + _settings.SkippedUpdateVersion = _availableUpdate.version; + PersistSettings(); + break; + } + + _updateDismissedThisRun = true; + _updateOfferPending = false; + _updateModalOpen = false; + } + + private void PersistSettings() => _settingsRepository.Save(_settings); + + private void FailLibraryScan(string title, string detail) + { + _libraryScanRunning = false; + _libraryReady = false; + _libraryLoadError = $"{title}\n{detail}"; + _status = title; + if (_openLibraryWhenScanCompletes) + SetError(title, detail); + else + Debug.LogWarning($"[ProjectQuint] {title}: {detail}"); + _openLibraryWhenScanCompletes = false; + } + + private void UpdateLoadingInput() + { + if (Keyboard.current?.escapeKey.wasPressedThisFrame != true) + return; + + HandleRetainedLoadingCancel(); + } + + private void HandleRetainedLoadingCancel() + { + if (_mode == AppMode.PackImport) + { + _packImportCancellation?.Cancel(); + _status = "CANCELLING ZIP IMPORT..."; + return; + } + if (_mode != AppMode.Loading) + return; + + Debug.Log("[ProjectQuint] Song loading cancelled by player."); + if (_activeCourse != null) + ReturnToCourseSelect(); + else + ReturnToLibrary(); + } + + private void HandleRetainedErrorRetry() + { + if (_mode == AppMode.Error) + StartCoroutine(InitializeLibrary(true)); + } + + private void UpdateLibraryInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + var library = _libraryController.State; + var allowProfileShortcut = ShouldAllowProfileShortcutInLibrary( + library.SearchFocused, + library.LevelFilterPanelOpen, + library.SortPanelOpen); + if (HandleProfileModalKeyboard(keyboard, allowProfileShortcut)) + return; + HandleLibraryControllerResult(_libraryController.HandleKeyboard(keyboard)); + } + + internal static bool ShouldAllowProfileShortcutInLibrary( + bool searchFocused, + bool levelFilterPanelOpen, + bool sortPanelOpen) => + !searchFocused && !levelFilterPanelOpen && !sortPanelOpen; + + private bool HandleProfileModalKeyboard(Keyboard keyboard, bool allowOpen) + { + if (keyboard == null) + return false; + if (_profileModalOpen) + { + if (keyboard.iKey.wasPressedThisFrame) + { + ChangeProfileImage(); + return true; + } + if (keyboard.deleteKey.wasPressedThisFrame || + keyboard.backspaceKey.wasPressedThisFrame) + { + ResetProfileImage(); + return true; + } + if (keyboard.leftShiftKey.wasPressedThisFrame) + _profileModalTab = (_profileModalTab + 3) % 4; + if (keyboard.rightShiftKey.wasPressedThisFrame) + _profileModalTab = (_profileModalTab + 1) % 4; + if (_profileModalTab == 1 && _ratingContexts.Count > 1) + { + if (keyboard.upArrowKey.wasPressedThisFrame) + ShiftProfileRatingContext(-1); + if (keyboard.downArrowKey.wasPressedThisFrame) + ShiftProfileRatingContext(1); + } + if (_profileModalTab == 2) + { + if (keyboard.leftArrowKey.wasPressedThisFrame) + ShiftProfileCollectionColumn(-1); + if (keyboard.rightArrowKey.wasPressedThisFrame) + ShiftProfileCollectionColumn(1); + if (keyboard.upArrowKey.wasPressedThisFrame) + ShiftProfileCollectionSelection(-1); + if (keyboard.downArrowKey.wasPressedThisFrame) + ShiftProfileCollectionSelection(1); + } + if (_profileModalTab == 3 && _profileCourses.BadgeCount > 0) + { + if (keyboard.upArrowKey.wasPressedThisFrame) + ShiftProfileBadgeSelection(-1); + if (keyboard.downArrowKey.wasPressedThisFrame) + ShiftProfileBadgeSelection(1); + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + SetFeaturedProfileBadge(_profileBadgeSelection); + } + var enterCloses = _profileModalTab != 3 || _profileCourses.BadgeCount == 0; + if (keyboard.escapeKey.wasPressedThisFrame || + keyboard.f8Key.wasPressedThisFrame || + keyboard.f11Key.wasPressedThisFrame || + enterCloses && (keyboard.enterKey.wasPressedThisFrame || + keyboard.numpadEnterKey.wasPressedThisFrame)) + _profileModalOpen = false; + return true; + } + + if (!allowOpen || !keyboard.f11Key.wasPressedThisFrame) + return false; + OpenProfileModal(0); + return true; + } + + private void OpenProfileModal(int tab) + { + RefreshProfileSummary(); + RefreshProfileRatingThumbnailSongs(); + _profileModalTab = Mathf.Clamp(tab, 0, 3); + _profileModalOpen = true; + } + + private void HandleProfileModalUi(ProfileModalUiResult profileResult) + { + if (profileResult.ChangeProfileImage) + ChangeProfileImage(); + if (profileResult.ResetProfileImage) + ResetProfileImage(); + if (profileResult.SelectedTab >= 0) + _profileModalTab = profileResult.SelectedTab; + if (profileResult.RatingContextDelta != 0) + ShiftProfileRatingContext(profileResult.RatingContextDelta); + if (profileResult.SelectedBadgeIndex >= 0) + _profileBadgeSelection = profileResult.SelectedBadgeIndex; + if (profileResult.SelectedCollectionColumn >= 0) + SetProfileCollectionSelection( + profileResult.SelectedCollectionColumn, + profileResult.SelectedCollectionIndex); + if (!string.IsNullOrWhiteSpace(profileResult.FeaturedBadgeCourseId)) + SetFeaturedProfileBadge(_profileBadgeSelection); + if (profileResult.Close) + _profileModalOpen = false; + } + + private void LoadProfileImage() + { + if (_profileImageStore == null) + return; + if (_profileImageStore.TryLoad(out var texture, out var error)) + { + ReplaceProfileImage(texture, string.Empty); + return; + } + if (string.IsNullOrWhiteSpace(error)) + { + ReplaceProfileImage(null, string.Empty); + return; + } + + _profileImageStatus = $"PHOTO LOAD FAILED // {error}"; + ApplyProfileImageToPresenters(); + Debug.LogWarning($"[ProjectQuint Profile] {_profileImageStatus}"); + } + + private void ChangeProfileImage() + { + if (_profileImageStore == null) + return; + var sourcePath = NativeProfileImagePicker.ChooseImagePath(); + if (string.IsNullOrWhiteSpace(sourcePath)) + { + if (!string.IsNullOrWhiteSpace(NativeBmsPackPicker.LastError)) + { + _profileImageStatus = + $"PHOTO PICKER FAILED // {NativeBmsPackPicker.LastError}"; + ApplyProfileImageToPresenters(); + Debug.LogWarning($"[ProjectQuint Profile] {_profileImageStatus}"); + } + return; + } + + var result = _profileImageStore.Import(sourcePath); + if (!result.Success) + { + _profileImageStatus = result.Status; + ApplyProfileImageToPresenters(); + Debug.LogWarning($"[ProjectQuint Profile] {_profileImageStatus}"); + return; + } + ReplaceProfileImage(result.Texture, result.Status); + } + + private void ResetProfileImage() + { + if (_profileImageStore == null) + return; + var result = _profileImageStore.Reset(); + if (!result.Success) + { + _profileImageStatus = result.Status; + ApplyProfileImageToPresenters(); + Debug.LogWarning($"[ProjectQuint Profile] {_profileImageStatus}"); + return; + } + + ReplaceProfileImage(null, result.Status); + if (!string.IsNullOrWhiteSpace(result.RecoveryPath)) + { + Debug.Log( + "[ProjectQuint Profile] The previous profile photo was preserved at " + + $"'{result.RecoveryPath}'."); + } + } + + private void ReplaceProfileImage(Texture2D texture, string status) + { + if (_profileImageTexture != null && _profileImageTexture != texture) + Destroy(_profileImageTexture); + _profileImageTexture = texture; + _profileImageStatus = status ?? string.Empty; + ApplyProfileImageToPresenters(); + } + + private void ApplyProfileImageToPresenters() + { + _packMusicScreenPresenter?.SetProfileImage(_profileImageTexture); + _optionsProfileCoursePresenter?.SetProfileImage( + _profileImageTexture, + _profileImageStatus); + _sessionScreensPresenter?.SetProfileImage(_profileImageTexture); + } + + private void UpdateCourseSelectInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (keyboard.escapeKey.wasPressedThisFrame) + { + _mode = AppMode.Library; + return; + } + var courses = _courseIndex?.Courses; + if (courses == null) + return; + if (keyboard.mKey.wasPressedThisFrame) + ToggleCourseMirror(); + if (keyboard.digit1Key.wasPressedThisFrame || keyboard.numpad1Key.wasPressedThisFrame) + ChangeNoteSpeed(-0.1f); + if (keyboard.digit2Key.wasPressedThisFrame || keyboard.numpad2Key.wasPressedThisFrame) + ChangeNoteSpeed(0.1f); + if (courses.Count == 0) + return; + + if (keyboard.downArrowKey.wasPressedThisFrame) + _courseSession.MoveSelection(1, courses.Count); + if (keyboard.upArrowKey.wasPressedThisFrame) + _courseSession.MoveSelection(-1, courses.Count); + if (keyboard.pageDownKey.wasPressedThisFrame) + _courseSession.MoveSelection(5, courses.Count); + if (keyboard.pageUpKey.wasPressedThisFrame) + _courseSession.MoveSelection(-5, courses.Count); + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + StartSelectedCourse(); + } + + private void StartSelectedCourse() + { + var courses = _courseIndex?.Courses; + if (courses == null || courses.Count == 0) + return; + var course = courses[Mathf.Clamp(_courseSelectedIndex, 0, courses.Count - 1)]; + if (!course.IsPlayable) + { + _courseStatus = $"{course.DisplayName}: {course.UnavailableReason}"; + return; + } + StartCourse(course); + } + + private void StartCourse(AeryCourseDefinition course) + { + if (course == null || !course.IsPlayable) + return; + + ClearLoadedSongState(); + _autoPlayEnabled = false; + _courseSession.Begin(course); + StartCoroutine(LoadCourseStage(0)); + } + + private void UpdateQuickOptionsInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (keyboard.spaceKey.wasPressedThisFrame || keyboard.escapeKey.wasPressedThisFrame) + { + _mode = AppMode.Library; + return; + } + if (keyboard.digit1Key.wasPressedThisFrame || keyboard.numpad1Key.wasPressedThisFrame) + ChangeNoteSpeed(-0.1f); + if (keyboard.digit2Key.wasPressedThisFrame || keyboard.numpad2Key.wasPressedThisFrame) + ChangeNoteSpeed(0.1f); + if (keyboard.downArrowKey.wasPressedThisFrame) + _quickOptionsSelectedRow = MoveQuickOptionsSelection( + _quickOptionsSelectedRow, + 1); + if (keyboard.upArrowKey.wasPressedThisFrame) + _quickOptionsSelectedRow = MoveQuickOptionsSelection( + _quickOptionsSelectedRow, + -1); + + var delta = keyboard.leftArrowKey.wasPressedThisFrame ? -1 : + keyboard.rightArrowKey.wasPressedThisFrame ? 1 : 0; + if (delta == 0 && (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame)) + delta = 1; + if (delta == 0) + return; + + if (_quickOptionsSelectedRow == 0) + ToggleAutoPlay(); + else if (_quickOptionsSelectedRow == 12) + ToggleAutoScratch(); + else if (_quickOptionsSelectedRow == 1) + CycleGearPosition(delta); + else if (_quickOptionsSelectedRow == 2) + ChangeNoteSpeed(delta * 0.1f); + else if (_quickOptionsSelectedRow == 3) + CycleGaugeType(delta); + else if (_quickOptionsSelectedRow == 4) + ToggleGaugeFallback(); + else if (_quickOptionsSelectedRow == 5) + CycleLaneModifier(delta); + else if (_quickOptionsSelectedRow == 6) + CycleGameplaySkin(delta); + else if (_quickOptionsSelectedRow == 7) + CycleNoteSkin(delta); + else if (_quickOptionsSelectedRow == 8) + CycleLongNoteStyle(delta); + else if (_quickOptionsSelectedRow == 9) + CycleBgaDisplayMode(delta); + else if (_quickOptionsSelectedRow == 10) + ToggleLaneSeparators(); + else + ToggleTimingMeter(); + } + + internal static int MoveQuickOptionsSelection(int currentRow, int direction) + { + var currentVisualIndex = Array.IndexOf(QuickOptionsVisualOrder, currentRow); + if (currentVisualIndex < 0) + currentVisualIndex = 0; + var nextVisualIndex = ( + currentVisualIndex + + Math.Sign(direction) + + QuickOptionsVisualOrder.Length) % + QuickOptionsVisualOrder.Length; + return QuickOptionsVisualOrder[nextVisualIndex]; + } + + private void HandleQuickOptionsUi(QuickOptionsUiResult result) + { + if (result.ToggleAutoPlay) + ToggleAutoPlay(); + if (result.ToggleAutoScratch) + ToggleAutoScratch(); + if (result.GearPositionDelta != 0) + CycleGearPosition(result.GearPositionDelta); + if (Math.Abs(result.SpeedDelta) > 0.001f) + ChangeNoteSpeed(result.SpeedDelta); + if (result.GaugeTypeDelta != 0) + CycleGaugeType(result.GaugeTypeDelta); + if (result.ToggleGaugeFallback) + ToggleGaugeFallback(); + if (result.LaneModifierDelta != 0) + CycleLaneModifier(result.LaneModifierDelta); + if (result.SkinDelta != 0) + CycleGameplaySkin(result.SkinDelta); + if (result.NoteSkinDelta != 0) + CycleNoteSkin(result.NoteSkinDelta); + if (result.LongNoteStyleDelta != 0) + CycleLongNoteStyle(result.LongNoteStyleDelta); + if (result.BgaDisplayModeDelta != 0) + CycleBgaDisplayMode(result.BgaDisplayModeDelta); + if (result.ToggleLaneSeparators) + ToggleLaneSeparators(); + if (result.ToggleTimingMeter) + ToggleTimingMeter(); + if (result.Close) + _mode = AppMode.Library; + } + + private void ToggleAutoPlay() + { + _autoPlayEnabled = !_autoPlayEnabled; + if (_autoPlayEnabled) + _autoScratchEnabled = false; + } + + private void ToggleAutoScratch() + { + _autoScratchEnabled = !_autoScratchEnabled; + if (_autoScratchEnabled) + _autoPlayEnabled = false; + } + + private void ToggleGaugeFallback() + { + _gaugeFallbackEnabled = !_gaugeFallbackEnabled; + PersistSettings(); + } + + private IEnumerator LoadSelectedSong() + { + var entry = _libraryController.State.SelectedSong; + if (_mode != AppMode.Library || entry == null) + yield break; + + _courseSession.ClearRun(); + yield return LoadSong(entry); + } + + private IEnumerator LoadRandomSong() + { + var candidates = _libraryController.State.VisibleSongs; + if (_mode != AppMode.Library || candidates.Count == 0) + yield break; + + var entry = ResolveRandomSong( + candidates, + UnityEngine.Random.Range(0, candidates.Count)); + if (entry == null) + yield break; + + _status = $"RANDOM SELECT // {entry.DisplayTitle}"; + _courseSession.ClearRun(); + yield return LoadSong(entry); + } + + internal static SongLibraryEntry ResolveRandomSong( + IReadOnlyList candidates, + int randomIndex) + { + if (candidates == null || candidates.Count == 0) + return null; + if (randomIndex < 0 || randomIndex >= candidates.Count) + throw new ArgumentOutOfRangeException(nameof(randomIndex)); + return candidates[randomIndex]; + } + + private IEnumerator LoadCourseStage(int stageIndex) + { + if (!_courseSession.TryBeginStage(stageIndex, out var entry)) + { + ReturnToCourseSelect(); + yield break; + } + + yield return LoadSong(entry); + } + + private IEnumerator LoadSong(SongLibraryEntry entry) + { + if (!string.IsNullOrWhiteSpace(_audioBackendInitializationError)) + { + SetError("AUDIO BACKEND UNAVAILABLE", _audioBackendInitializationError); + yield break; + } + if (entry == null || IsAudioSystemResetting) + { + if (entry != null) + _status = "AUDIO SYSTEM IS RESTARTING // TRY AGAIN"; + yield break; + } + + var loadGeneration = ++_songLoadGeneration; + _loadingEntry = entry; + var loadingTitle = _activeCourse == null + ? entry.DisplayTitle + : $"{_activeCourse.DisplayName} // STAGE {_courseStageIndex + 1}/{_activeCourse.StageCount}"; + _flow.BeginLoading( + loadingTitle, + "Parsing BMS timeline", + Time.realtimeSinceStartupAsDouble); + var playModifier = _activeCourse == null ? _laneModifier : EffectiveCourseModifier; + var request = new SongLoadRequest + { + Entry = entry, + LaneModifier = playModifier, + LaneRandomSeed = _activeCourse == null ? unchecked((int)DateTime.UtcNow.Ticks) : 0, + ReactiveKeySoundsEnabled = _reactiveKeySoundsEnabled, + BgaPlaybackMode = _bgaPlaybackMode, + CancellationRequested = () => IsSongLoadCancelled(loadGeneration), + }; + SongLoadResult result = null; + yield return _songLoadCoordinator.Load( + request, + progress => + { + if (IsSongLoadCancelled(loadGeneration)) + return; + _flow.ReportLoading(progress.Detail, progress.Current, progress.Total); + }, + completed => result = completed); + + if (IsSongLoadCancelled(loadGeneration)) + { + result?.Dispose(); + yield break; + } + if (result == null || !result.IsSuccess) + { + if (result?.IsCancelled == true) + { + result.Dispose(); + yield break; + } + var errorTitle = result?.ErrorTitle ?? "SONG LOAD FAILED"; + var errorDetail = result?.ErrorDetail ?? entry.ChartPath; + result?.Dispose(); + _loadingEntry = null; + SetError( + errorTitle, + errorDetail); + yield break; + } + + var candidate = result.TakePreparedSong(); + result.Dispose(); + if (!CommitPreparedSong(candidate)) + { + candidate?.Dispose(); + _loadingEntry = null; + SetError("SONG LOAD FAILED", "The prepared song could not be committed."); + yield break; + } + + BeginLoadedSongPlayback(); + Debug.Log(_songLoadCoordinator.BuildDiagnostics( + _preparedSong, + request, + $"{_gauge.Mode}, TOTAL {_gauge.Total:0.###}")); + } + + private bool CommitPreparedSong(PreparedSong candidate) + { + if (candidate == null || !candidate.TryCommit()) + return false; + + var previous = _preparedSong; + try + { + _preparedSong = candidate; + _audio = candidate.Audio; + _audio.SetMasterVolume(_masterVolumePercent * 0.01f); + _bga = candidate.Bga; + _stageTexture = candidate.StageTexture; + _playingEntry = candidate.Entry; + _loadingEntry = null; + _gameplaySession.LoadChart(candidate.Chart, candidate.NotesById); + } + catch (Exception exception) + { + _preparedSong = previous; + _audio = previous?.Audio; + _bga = previous?.Bga; + _stageTexture = previous?.StageTexture; + _playingEntry = previous?.Entry; + if (previous != null) + _gameplaySession.LoadChart(previous.Chart, previous.NotesById); + else + _gameplaySession.Clear(); + candidate.Dispose(); + Debug.LogException(exception); + return false; + } + + // The prior song remains intact throughout preparation. It is released only + // after every active reference has switched to the committed candidate. + try + { + previous?.Dispose(); + } + catch (Exception exception) + { + // A cleanup fault must not roll a successfully committed song back to an + // already-partially-disposed predecessor. + Debug.LogException(exception); + } + return true; + } + + private bool IsSongLoadCancelled(int loadGeneration) => + SongLoadCoordinator.ShouldCancel(loadGeneration, _songLoadGeneration, _mode == AppMode.Loading); + + private void BeginLoadedSongPlayback() + { + if (_chart == null || _audio == null || _bga == null) + return; + + _audio.Resume(); + DisableLaneInput(); + _resultSession.BeginPlayback(); + _gameplayNoteWindow.Reset(_chart.Notes); + _audio.BeginPlayback(_reactiveKeySoundsEnabled); + if (DynamicBgaEnabled) + _bga.BeginPlayback(); + var isCourseStage = _activeCourse != null; + _gameplaySession.Start( + new GameplaySessionStartRequest( + isCourseStage ? GaugeMode.Class : _gaugeType, + isCourseStage ? _courseGaugeValue : null, + isCourseStage && _courseProgress != null ? _courseProgress.CurrentCombo : 0, + isCourseStage && _courseProgress != null ? _courseProgress.MaxCombo : 0, + isCourseStage + ? _activeCourse.ResolvedGaugeProfile + : CourseGaugeProfile.Lr2, + autoPlay: !isCourseStage && _autoPlayEnabled, + gaugeFallbackEnabled: !isCourseStage && _gaugeFallbackEnabled, + autoScratch: ResolveAutoScratchForSession( + _autoScratchEnabled, + isCourseStage)), + CaptureGameplayClock()); + var gameplayStartRealtime = Time.realtimeSinceStartupAsDouble; + _gameplayPlayfieldPresenter.SetTelemetryEnabled(_gameplayDiagnosticsEnabled); + if (_gameplayDiagnosticsEnabled) + { + _framePacingMonitor.Reset(gameplayStartRealtime, GC.GetAllocatedBytesForCurrentThread()); + _unityFrameTimingMonitor.Reset(); + _inputMonitor.Reset(Keyboard.current?.displayName); + } + _gameplayNoteWindow.Update( + _judgement, + _frameSongTime, + ProjectQuintPresentation.CalculateApproachSeconds(_noteSpeed), + _chart.ScrollMap, + _chart.Metadata.InitialTempo); + if (!_gameplaySession.IsAutoPlay && !EnableLaneInput()) + return; + _mode = AppMode.Gameplay; + } + + private void OnJudged(JudgementResult result) + { + if (DynamicBgaEnabled && + result.Grade == JudgementGrade.Miss && + !result.SuppressGameplayPenalty) + _bga?.TriggerPoor(_frameSongTime); + } + + private void OnAutoPlayNotePressed(PlayableNote note) + { + if (_reactiveKeySoundsEnabled && note != null) + _audio?.PlayKeySound(note.SoundId, note.Lane); + } + + private void UpdateGameplay() + { + if (_resultSession.GameplayOutroActive) + { + var now = Time.realtimeSinceStartupAsDouble; + if (_resultSession.GameplayOutroComplete(now)) + { + _resultSession.CompleteGameplayOutro(now); + _mode = AppMode.Results; + } + return; + } + + using var gameplayScope = GameplayUpdateMarker.Auto(); + if (_gameplayDiagnosticsEnabled) + TrackGameplayFramePacing(); + var inputRealtime = Time.realtimeSinceStartupAsDouble; + _nativeInputPoller?.Drain(OnNativeLaneTransition, inputRealtime); + var phaseStarted = BeginGameplayPhaseTiming(); + _audio?.Update(); + RecordGameplayPhase(GameplayWorkloadPhase.Audio, phaseStarted); + var sessionSignal = _gameplaySession.AdvanceClock(CaptureGameplayClock()); + if ((sessionSignal & GameplaySessionSignal.ClockFallbackActivated) != 0) + { + var audioConfiguration = _audioDspBufferController?.Snapshot ?? default; + Debug.LogWarning( + "[ProjectQuint Audio] DSP clock stalled for at least 250 ms; possible output-device " + + $"interruption or buffer underrun ({audioConfiguration.AppliedLabel}). " + + "Gameplay is continuing on the monotonic realtime fallback clock. " + + "Frame-pacing hitches are measured and logged independently."); + } + if (DynamicBgaEnabled) + { + phaseStarted = BeginGameplayPhaseTiming(); + using (BgaUpdateMarker.Auto()) + _bga?.Update(_frameSongTime); + RecordGameplayPhase(GameplayWorkloadPhase.Bga, phaseStarted); + } + phaseStarted = BeginGameplayPhaseTiming(); + using (JudgementUpdateMarker.Auto()) + sessionSignal |= _gameplaySession.EvaluateFrame(_judgementOffsetMilliseconds); + RecordGameplayPhase(GameplayWorkloadPhase.Judgement, phaseStarted); + + if ((sessionSignal & GameplaySessionSignal.GaugeShifted) != 0) + { + _gameplayHudPresenter?.NotifyGaugeShift( + _gameplaySession.LastGaugeShiftFrom, + _gameplaySession.LastGaugeShiftTo, + _frameSongTime); + } + + if ((sessionSignal & GameplaySessionSignal.GaugeFailed) != 0) + { + EnterGameOver(); + return; + } + + var keyboard = Keyboard.current; + if (keyboard?.digit1Key.wasPressedThisFrame == true || keyboard?.numpad1Key.wasPressedThisFrame == true) + ChangeNoteSpeed(-0.1f); + if (keyboard?.digit2Key.wasPressedThisFrame == true || keyboard?.numpad2Key.wasPressedThisFrame == true) + ChangeNoteSpeed(0.1f); + + if (keyboard?.escapeKey.wasPressedThisFrame == true) + { + PauseGameplay(); + return; + } + + if ((sessionSignal & GameplaySessionSignal.SongFinished) != 0) + FinishSong(); + } + + private void TrackGameplayFramePacing() + { + var now = Time.realtimeSinceStartupAsDouble; + var targetFramesPerSecond = _effectiveTargetFrameRate; + _unityFrameTimingMonitor.CaptureLatest(); + if (!_framePacingMonitor.AddSample( + now, + GC.GetAllocatedBytesForCurrentThread(), + targetFramesPerSecond, + out var snapshot)) + return; + + var unityTiming = _unityFrameTimingMonitor.CaptureAndReset(); + var inputSnapshot = _inputMonitor.CaptureSnapshot(); + var workload = _workloadMonitor.CaptureAndReset(); + var message = + $"[ProjectQuint Profile] {(snapshot.IsUnstable ? "UNSTABLE" : "STABLE")} at song {_frameSongTime:0.000}s: " + + $"avg {snapshot.AverageMilliseconds:0.00} ms, p50 {snapshot.P50Milliseconds:0.00} ms, " + + $"p95 {snapshot.P95Milliseconds:0.00} ms, " + + $"p99 {snapshot.P99Milliseconds:0.00} ms, max {snapshot.MaximumMilliseconds:0.00} ms, " + + $"budget {snapshot.FrameBudgetMilliseconds:0.00} ms, max main-thread GC " + + $"{snapshot.MaximumAllocatedBytes / 1024.0:0.0} KiB, missed-refresh frames " + + $"{snapshot.MissedRefreshFrameCount}/{snapshot.SampleCount} " + + $"({(snapshot.SampleCount <= 0 ? 0.0 : snapshot.MissedRefreshFrameCount * 100.0 / snapshot.SampleCount):0.000}%), " + + $"estimated missed refreshes {snapshot.EstimatedMissedRefreshCount}; " + + $"Unity timing {(unityTiming.FeatureEnabled ? "ON" : "OFF")}, " + + $"samples {unityTiming.CpuFrame.SampleCount}, VSync source " + + $"{FormatUnityVSyncSource(unityTiming)}, " + + $"CPU total {FormatInputPhase(unityTiming.CpuFrame)}, main " + + $"{FormatInputPhase(unityTiming.CpuMainThread)}, present-wait " + + $"{FormatInputPhase(unityTiming.CpuPresentWait)}, GPU " + + $"{FormatInputPhase(unityTiming.GpuFrame)}; " + + $"phase avg/p99/max ms: audio {FormatPhase(workload.Audio)}, " + + $"judge {FormatPhase(workload.Judgement)}, BGA {FormatPhase(workload.Bga)}, " + + $"note-window {FormatPhase(workload.NoteWindow)}, " + + $"playfield-submit {FormatPhase(workload.PlayfieldSubmit)}, " + + $"HUD {FormatPhase(workload.HudSubmit)}, mesh {FormatPhase(workload.PlayfieldMesh)}; " + + $"visible notes avg/max {workload.AverageVisibleNotes:0.0}/{workload.MaximumVisibleNotes}, " + + $"mesh vertices avg/max {workload.AverageMeshVertices:0}/{workload.MaximumMeshVertices}; " + + $"audio sources {_audio?.ScheduledSourceCount ?? 0}, " + + $"pending audio events {_audio?.PendingScheduledEventCount ?? 0}, " + + $"dropped late audio {_audio?.DroppedLateEventCount ?? 0}, BGA '{_bga?.Status ?? string.Empty}', " + + $"input events {inputSnapshot.EventCount}, input queue p50/p95/p99/max " + + $"{inputSnapshot.P50QueueDelayMilliseconds:0.###}/" + + $"{inputSnapshot.P95QueueDelayMilliseconds:0.###}/" + + $"{inputSnapshot.P99QueueDelayMilliseconds:0.###}/" + + $"{inputSnapshot.MaximumQueueDelayMilliseconds:0.###} ms, " + + $"input pipeline p50/p95/p99/max ms: judge " + + $"{FormatInputPhase(inputSnapshot.JudgementDispatchLatency)}, reactive-audio " + + $"{FormatInputPhase(inputSnapshot.ReactiveAudioCommandLatency)}, visual " + + $"{FormatInputPhase(inputSnapshot.VisualFeedbackLatency)}, visual drops " + + $"{inputSnapshot.DroppedVisualFeedbackSamples}, out-of-order " + + $"{inputSnapshot.OutOfOrderCount}, invalid transitions " + + $"{inputSnapshot.InvalidTransitionCount}, " + + $"display {Screen.width}x{Screen.height}@" + + $"{Screen.currentResolution.refreshRateRatio.value:0.##}Hz, " + + $"VSync {(_vSyncEnabled ? "ON" : "OFF")}, requested " + + $"{FormatFrameRate(GameSettings.TargetFrameRate(_frameLimitMode))}, effective " + + $"{FormatFrameRate(targetFramesPerSecond)}, " + + $"BGA {_bgaPlaybackMode}, note {_noteSkin}/{_longNoteStyle}, " + + $"runtime {(Application.isEditor ? "Editor" : "Player")}."; + if (snapshot.IsUnstable) + Debug.LogWarning(message); + else + Debug.Log(message); + } + + private long BeginGameplayPhaseTiming() => + _gameplayDiagnosticsEnabled ? GameplayWorkloadMonitor.Timestamp() : 0; + + private void RecordGameplayPhase(GameplayWorkloadPhase phase, long startedTimestamp) + { + if (_gameplayDiagnosticsEnabled) + _workloadMonitor.Record(phase, startedTimestamp); + } + + private static string FormatPhase(GameplayPhaseTimingSnapshot phase) => + $"{phase.AverageMilliseconds:0.000}/{phase.P99Milliseconds:0.000}/{phase.MaximumMilliseconds:0.000}"; + + private static string FormatInputPhase(GameplayPhaseTimingSnapshot phase) => + phase.SampleCount <= 0 + ? "-" + : $"{phase.P50Milliseconds:0.000}/{phase.P95Milliseconds:0.000}/" + + $"{phase.P99Milliseconds:0.000}/{phase.MaximumMilliseconds:0.000}" + + $"({phase.SampleCount})"; + + private static string FormatUnityVSyncSource(UnityFrameTimingSnapshot timing) => + timing.VSyncsPerSecond > 0f + ? $"{timing.VSyncsPerSecond:0.##}Hz x{timing.SyncInterval}" + : $"unavailable x{timing.SyncInterval}"; + + private void FinishSong() + { + LogInputDiagnostics("SONG FINISHED"); + DisableLaneInput(); + _audio?.Stop(); + _bga?.Stop(); + if (_activeCourse != null) + { + FinishCourseStage(); + return; + } + + var previousRecord = SnapshotPlayRecord( + _playingEntry?.BestRecord ?? SongPlayRecordStore.Load(_playingEntry)); + var songCleared = _gauge != null && _gauge.IsCleared; + var lamp = SongClearLampStore.Resolve( + _gauge?.Mode ?? GaugeMode.Normal, + songCleared, + _gameplaySession?.IsAutoScratch == true); + RecordCurrentPlay(completed: true, songCleared, lamp); + var completion = GameplayCompletionOutcome.Evaluate( + previousRecord, + songCleared, + _gameplaySession?.IsAutoPlay == true, + !string.IsNullOrWhiteSpace(_resultSession.RecordSaveError), + _judgement?.Score ?? 0, + _judgement?.AccuracyPercent ?? 0.0, + _judgement?.MaxCombo ?? 0, + _judgement?.MissCount ?? 0, + _judgement?.PurePerfectCount ?? 0, + _judgement?.TotalScoringUnitCount ?? 0); + if (completion.HasPresentation) + { + _resultSession.BeginGameplayOutro( + previousRecord, + songCleared, + completion, + Time.realtimeSinceStartupAsDouble); + return; + } + _resultSession.BeginResults( + previousRecord, + songCleared, + Time.realtimeSinceStartupAsDouble); + _mode = AppMode.Results; + } + + private void FinishCourseStage() + { + if (_activeCourse == null || _courseProgress == null) + { + ReturnToLibrary(); + return; + } + + _courseSession.CaptureGauge(_gauge?.Value ?? 0.0); + AccumulateCourseStage(true); + var nextStageIndex = _courseStageIndex + 1; + + if (nextStageIndex < _activeCourse.StageCount) + { + // Course progress owns the gauge/combo that survives the transition. Audio, + // chart and BGA are stage-local: release them before decoding the next bank so + // dense courses never retain two songs' native samples at the same time. + ClearLoadedSongState(); + StartCoroutine(LoadCourseStage(nextStageIndex)); + return; + } + + ClearLoadedSongState(); + FinishCourseResult(true); + } + + private void AccumulateCourseStage(bool cleared) + { + _courseSession.TryAccumulateStage(_judgement, cleared); + } + + private void FinishCourseFailure() + { + DisableLaneInput(); + _audio?.Stop(); + _bga?.Stop(); + _courseSession.CaptureGauge(0.0); + AccumulateCourseStage(false); + ClearLoadedSongState(); + FinishCourseResult(false); + } + + private void FinishCourseResult(bool passed) + { + _resultSession.BeginResults(null, passed, Time.realtimeSinceStartupAsDouble); + if (_activeCourse != null && _courseProgress != null) + { + var result = _courseSession.Complete( + passed, + EffectiveCourseModifier.ToString().ToUpperInvariant()); + AeryCourseRecordStore.Record( + _activeCourse, + result); + } + else + { + _courseSession.Complete( + passed, + EffectiveCourseModifier.ToString().ToUpperInvariant()); + } + _mode = AppMode.CourseResults; + } + + private static SongPlayRecord SnapshotPlayRecord(SongPlayRecord source) + { + if (source == null) + return new SongPlayRecord(); + + return new SongPlayRecord + { + HasRecord = source.HasRecord, + BestScore = source.BestScore, + BestAccuracyPercent = source.BestAccuracyPercent, + BestMaxCombo = source.BestMaxCombo, + HasBestScoreJudgementBreakdown = source.HasBestScoreJudgementBreakdown, + BestScoreKoolCount = source.BestScoreKoolCount, + BestScoreCoolCount = source.BestScoreCoolCount, + BestScoreGoodCount = source.BestScoreGoodCount, + BestScoreMissCount = source.BestScoreMissCount, + BestScorePurePerfectCount = source.BestScorePurePerfectCount, + PlayCount = source.PlayCount, + HasJudgementBreakdown = source.HasJudgementBreakdown, + LastScore = source.LastScore, + LastAccuracyPercent = source.LastAccuracyPercent, + LastMaxCombo = source.LastMaxCombo, + LastKoolCount = source.LastKoolCount, + LastCoolCount = source.LastCoolCount, + LastGoodCount = source.LastGoodCount, + LastMissCount = source.LastMissCount, + LastFastCount = source.LastFastCount, + LastLateCount = source.LastLateCount, + LastAppliedEventId = source.LastAppliedEventId, + LastPurePerfectCount = source.LastPurePerfectCount, + LastJudgedUnits = source.LastJudgedUnits, + LastTotalScoringUnits = source.LastTotalScoringUnits, + LastCleared = source.LastCleared, + LastHardClear = source.LastHardClear, + EverFullCombo = source.EverFullCombo, + EverAllPure = source.EverAllPure, + BestRating = SnapshotRatingRecord(source.BestRating), + BestRatings = (source.BestRatings ?? new List()) + .ConvertAll(SnapshotRatingRecord), + }; + } + + private static RatingPlayRecord SnapshotRatingRecord(RatingPlayRecord source) + { + if (source == null) + return new RatingPlayRecord(); + + return new RatingPlayRecord + { + HasRecord = source.HasRecord, + EventId = source.EventId, + ProviderId = source.ProviderId, + ProviderDisplayName = source.ProviderDisplayName, + DifficultyLabel = source.DifficultyLabel, + DifficultyValue = source.DifficultyValue, + Title = source.Title, + Artist = source.Artist, + ChartConstant = source.ChartConstant, + KeyCount = source.KeyCount, + HasScratch = source.HasScratch, + LaneModifier = source.LaneModifier, + HardClear = source.HardClear, + AccuracyPercent = source.AccuracyPercent, + PlayRating = source.PlayRating, + PlayCount = source.PlayCount, + RatingRulesVersion = source.RatingRulesVersion, + RecordedUtcTicks = source.RecordedUtcTicks, + }; + } + + private void RecordCurrentPlay(bool completed, bool cleared, SongClearLamp lamp) + { + if (_gameplaySession?.AllowsRecordCommit != true || + _playingEntry == null || _judgement == null || !_resultSession.TryMarkRecorded()) + return; + + var annotation = _playingEntry.Catalog; + var difficultyLabel = annotation?.DifficultyLabel ?? string.Empty; + var difficultyValue = annotation?.DifficultyValue ?? string.Empty; + var chartConstant = annotation?.ChartConstant ?? 0.0; + if (chartConstant <= 0.0) + RatingRules.TryResolveChartConstant( + difficultyValue, + difficultyLabel, + out chartConstant); + var playEvent = new PlayResultEvent + { + EventId = string.IsNullOrWhiteSpace(_resultSession.CurrentPlayEventId) + ? Guid.NewGuid().ToString("N") + : _resultSession.CurrentPlayEventId, + PackId = _playingEntry.PackId ?? string.Empty, + PackDisplayName = _activePack?.DisplayName ?? string.Empty, + ProviderId = annotation?.ProviderId ?? string.Empty, + ProviderDisplayName = annotation?.ProviderDisplayName ?? string.Empty, + DifficultyLabel = difficultyLabel, + DifficultyValue = difficultyValue, + Title = _playingEntry.DisplayTitle ?? string.Empty, + Artist = _playingEntry.DisplayArtist ?? string.Empty, + ChartConstant = chartConstant, + KeyCount = _chart?.Metadata?.KeyCount ?? _playingEntry?.EffectiveKeyCount ?? 5, + HasScratch = _chart?.Metadata?.HasScratch ?? _playingEntry.Metadata?.HasScratch == true, + LaneModifier = _laneModifier.ToString().ToUpperInvariant(), + GaugeType = (_gauge?.Mode ?? _gaugeType).ToString().ToUpperInvariant(), + Completed = completed, + Cleared = cleared, + HardClear = lamp == SongClearLamp.HardClear || + lamp == SongClearLamp.ExHardClear, + ClearLamp = (int)lamp, + Score = _judgement.Score, + AccuracyPercent = _judgement.AccuracyPercent, + MaxCombo = _judgement.MaxCombo, + KoolCount = _judgement.KoolCount, + PurePerfectCount = _judgement.PurePerfectCount, + CoolCount = _judgement.CoolCount, + GoodCount = _judgement.GoodCount, + MissCount = _judgement.MissCount, + FastCount = _judgement.FastCount, + LateCount = _judgement.LateCount, + JudgedUnits = _judgement.JudgedNoteCount, + TotalScoringUnits = _judgement.TotalScoringUnitCount, + ActivePlaySeconds = Math.Max(0.0, _frameSongTime), + OccurredUtcTicks = DateTime.UtcNow.Ticks, + }; + if (PlayResultCommitGuard.TryCommit( + () => _playCommitCoordinator.Commit(_playingEntry, playEvent), + out var outcome, + out var userFacingError, + out var commitException)) + { + _resultSession.SetCommitOutcome(outcome); + RefreshProfileSummary(); + return; + } + + _resultSession.SetCommitFailure(userFacingError); + Debug.LogError( + $"[ProjectQuint Persistence] Play result was not saved; gameplay will still " + + $"advance to its terminal screen. {commitException}"); + } + + private void RefreshProfileSummary() + { + _profileSnapshot = _progressionRepository?.Snapshot() ?? default; + _profileCollection = SongRecordRepository.Shared.CalculateCollection(); + _profileCourses = CourseRecordRepository.Shared.CalculateCollection( + _progressionRepository?.FeaturedCourseBadgeId); + _ratingContexts = BuildProfileRatingContexts( + SongRecordRepository.Shared.CalculateRatingContexts(), + _difficultyTableRegistry?.Tables, + _libraryController?.State?.AllSongs, + _activeDifficultyTable?.Id, + _profileSnapshot, + SongRecordRepository.Shared.CalculateRating); + _homeRatingSummary = _profileSnapshot.LastRatingKeyCount > 0 && + !string.IsNullOrWhiteSpace(_profileSnapshot.LastRatingProviderId) + ? SongRecordRepository.Shared.CalculateRating( + _profileSnapshot.LastRatingProviderId, + _profileSnapshot.LastRatingProviderDisplayName, + _profileSnapshot.LastRatingKeyCount, + _profileSnapshot.LastRatingHasScratch) + : SongRecordRepository.Shared.CalculateMostRecentRatingContext( + _profileSnapshot.LastRatingKeyCount, + _profileSnapshot.LastRatingHasScratch); + _profileRatingContextIndex = FindRatingContextIndex(_homeRatingSummary); + if (_ratingContexts.Count > 0) + _profileRatingContextIndex = 0; + _profileRatingSummary = _ratingContexts.Count > 0 + ? _ratingContexts[_profileRatingContextIndex] + : _homeRatingSummary; + _profileBadgeSelection = FindFeaturedBadgeIndex(_profileCourses); + ClampProfileCollectionSelections(); + } + + private void RefreshProfileRatingThumbnailSongs() + { + var requestedChartIds = new HashSet(StringComparer.Ordinal); + foreach (var context in _ratingContexts ?? Array.Empty()) + { + foreach (var entry in context.BestPlays ?? Array.Empty()) + { + if (!string.IsNullOrWhiteSpace(entry.ChartId)) + requestedChartIds.Add(entry.ChartId); + } + } + + var signature = 17L; + unchecked + { + signature = signature * 31 + (_libraryController?.State?.SongsRevision ?? 0); + foreach (var chartId in requestedChartIds.OrderBy(id => id, StringComparer.Ordinal)) + signature = signature * 31 + StringComparer.Ordinal.GetHashCode(chartId); + foreach (var pack in _packRegistry?.Packs ?? Array.Empty()) + { + var cachePath = _packRegistry.CachePathFor(pack); + var cache = new FileInfo(cachePath); + signature = signature * 31 + StringComparer.OrdinalIgnoreCase.GetHashCode(pack.Id ?? string.Empty); + signature = signature * 31 + (cache.Exists ? cache.Length : 0L); + signature = signature * 31 + (cache.Exists ? cache.LastWriteTimeUtc.Ticks : 0L); + } + } + if (signature == _profileRatingThumbnailSourceSignature) + return; + + var songsByChartId = new Dictionary(StringComparer.Ordinal); + void AddSong(SongLibraryEntry song) + { + if (song == null) + return; + var chartId = ChartIdentityResolver.Resolve(song).Value; + if (requestedChartIds.Contains(chartId) && !songsByChartId.ContainsKey(chartId)) + songsByChartId.Add(chartId, song); + } + + foreach (var song in _libraryController?.State?.AllSongs ?? Array.Empty()) + AddSong(song); + foreach (var pack in _packRegistry?.Packs ?? Array.Empty()) + { + foreach (var song in SongLibraryScanner.LoadCachedThumbnailEntries( + _packRegistry.CachePathFor(pack), + pack.ContentRoot, + pack.Id, + requestedChartIds)) + AddSong(song); + } + + _profileRatingThumbnailSongs = songsByChartId.Values.ToArray(); + _profileRatingThumbnailSourceSignature = signature; + unchecked + { + _profileRatingThumbnailSongsRevision++; + } + } + + internal static IReadOnlyList BuildProfileRatingContexts( + IReadOnlyList recordedContexts, + IReadOnlyList registeredTables, + IReadOnlyList activeSongs, + string activeTableId, + ProfileProgressSnapshot profile, + Func resolveRating) + { + var contexts = new Dictionary(StringComparer.OrdinalIgnoreCase); + var tableNames = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var table in registeredTables ?? Array.Empty()) + { + if (table == null || string.IsNullOrWhiteSpace(table.Id)) + continue; + tableNames[table.Id.Trim()] = string.IsNullOrWhiteSpace(table.DisplayName) + ? table.Id.Trim() + : table.DisplayName.Trim(); + } + + void AddResolved(string providerId, string providerName, int keyCount, bool hasScratch) + { + if (string.IsNullOrWhiteSpace(providerId) || keyCount < 4 || keyCount > 7) + return; + var normalizedId = providerId.Trim(); + var key = RatingContextIdentity(normalizedId, keyCount, hasScratch); + if (contexts.ContainsKey(key)) + return; + var displayName = string.IsNullOrWhiteSpace(providerName) + ? tableNames.TryGetValue(normalizedId, out var registeredName) + ? registeredName + : normalizedId + : providerName.Trim(); + contexts[key] = resolveRating?.Invoke(normalizedId, displayName, keyCount, hasScratch) ?? + new RatingSummary(normalizedId, displayName, keyCount, hasScratch, 0.0, 0); + } + + foreach (var recorded in recordedContexts ?? Array.Empty()) + { + if (!recorded.HasContext) + continue; + contexts[RatingContextIdentity(recorded.ProviderId, recorded.KeyCount, recorded.HasScratch)] = recorded; + } + + foreach (var table in registeredTables ?? Array.Empty()) + { + if (table == null) + continue; + var preset = BmsDifficultyTableIndex.FindPreset(table.Id, table.HeaderUrl); + if (preset?.DefaultKeyCount >= 4) + AddResolved(table.Id, table.DisplayName, preset.Value.DefaultKeyCount, + preset.Value.DefaultHasScratch); + } + + if (!string.IsNullOrWhiteSpace(activeTableId)) + { + var activeName = tableNames.TryGetValue(activeTableId, out var registeredName) + ? registeredName + : activeTableId; + foreach (var form in (activeSongs ?? Array.Empty()) + .Where(song => song?.Metadata != null && + song.EffectiveKeyCount >= 4 && song.EffectiveKeyCount <= 7 && + song.Catalog?.HasDifficulty == true && + string.Equals(song.Catalog.ProviderId, activeTableId, + StringComparison.OrdinalIgnoreCase)) + .Select(song => new { KeyCount = song.EffectiveKeyCount, song.Metadata.HasScratch }) + .Distinct()) + AddResolved(activeTableId, activeName, form.KeyCount, form.HasScratch); + } + + AddResolved( + profile.LastRatingProviderId, + profile.LastRatingProviderDisplayName, + profile.LastRatingKeyCount, + profile.LastRatingHasScratch); + + return contexts.Values + .OrderByDescending(summary => summary.ActivityPlayCount) + .ThenByDescending(summary => summary.FilledSlots) + .ThenBy(summary => summary.ProviderDisplayName, StringComparer.OrdinalIgnoreCase) + .ThenBy(summary => summary.KeyCount) + .ThenBy(summary => summary.HasScratch) + .ToArray(); + } + + private static string RatingContextIdentity(string providerId, int keyCount, bool hasScratch) => + $"{(providerId ?? string.Empty).Trim().ToLowerInvariant()}:{keyCount}:{hasScratch}"; + + private int FindRatingContextIndex(RatingSummary target) + { + for (var index = 0; index < _ratingContexts.Count; index++) + { + var context = _ratingContexts[index]; + if (string.Equals(context.ProviderId, target.ProviderId, StringComparison.OrdinalIgnoreCase) && + context.KeyCount == target.KeyCount && context.HasScratch == target.HasScratch) + return index; + } + return 0; + } + + private void ShiftProfileRatingContext(int delta) + { + if (_ratingContexts.Count == 0 || delta == 0) + return; + _profileRatingContextIndex = (_profileRatingContextIndex + delta) % _ratingContexts.Count; + if (_profileRatingContextIndex < 0) + _profileRatingContextIndex += _ratingContexts.Count; + _profileRatingSummary = _ratingContexts[_profileRatingContextIndex]; + } + + private IReadOnlyList ProfileCollectionGroups(int column) + { + return Mathf.Clamp(column, 0, 2) switch + { + 0 => _profileCollection.Packs, + 1 => _profileCollection.KeyModes, + _ => _profileCollection.Difficulties, + }; + } + + private void ShiftProfileCollectionColumn(int delta) + { + if (delta == 0) + return; + _profileCollectionColumn = (_profileCollectionColumn + delta) % 3; + if (_profileCollectionColumn < 0) + _profileCollectionColumn += 3; + ClampProfileCollectionSelections(); + } + + private void ShiftProfileCollectionSelection(int delta) + { + var groups = ProfileCollectionGroups(_profileCollectionColumn); + if (groups == null || groups.Count == 0 || delta == 0) + return; + var current = _profileCollectionSelections[_profileCollectionColumn]; + current = (current + delta) % groups.Count; + if (current < 0) + current += groups.Count; + _profileCollectionSelections[_profileCollectionColumn] = current; + } + + private void SetProfileCollectionSelection(int column, int index) + { + _profileCollectionColumn = Mathf.Clamp(column, 0, 2); + var groups = ProfileCollectionGroups(_profileCollectionColumn); + _profileCollectionSelections[_profileCollectionColumn] = groups == null || groups.Count == 0 + ? 0 + : Mathf.Clamp(index, 0, groups.Count - 1); + } + + private void ClampProfileCollectionSelections() + { + _profileCollectionColumn = Mathf.Clamp(_profileCollectionColumn, 0, 2); + for (var column = 0; column < _profileCollectionSelections.Length; column++) + { + var groups = ProfileCollectionGroups(column); + _profileCollectionSelections[column] = groups == null || groups.Count == 0 + ? 0 + : Mathf.Clamp(_profileCollectionSelections[column], 0, groups.Count - 1); + } + } + + private static int FindFeaturedBadgeIndex(ProfileCourseSnapshot courses) + { + if (courses == null) + return 0; + for (var index = 0; index < courses.CertificationBadges.Count; index++) + { + if (string.Equals( + courses.CertificationBadges[index].CourseId, + courses.FeaturedBadgeCourseId, + StringComparison.OrdinalIgnoreCase)) + return index; + } + return 0; + } + + private void ShiftProfileBadgeSelection(int delta) + { + if (_profileCourses.BadgeCount == 0 || delta == 0) + return; + _profileBadgeSelection = (_profileBadgeSelection + delta) % _profileCourses.BadgeCount; + if (_profileBadgeSelection < 0) + _profileBadgeSelection += _profileCourses.BadgeCount; + } + + private void SetFeaturedProfileBadge(int badgeIndex) + { + if (_profileCourses.BadgeCount == 0) + return; + var normalized = Mathf.Clamp(badgeIndex, 0, _profileCourses.BadgeCount - 1); + var badge = _profileCourses.CertificationBadges[normalized]; + _progressionRepository?.SetFeaturedCourseBadge(badge.CourseId); + _profileCourses = CourseRecordRepository.Shared.CalculateCollection(badge.CourseId); + _profileBadgeSelection = FindFeaturedBadgeIndex(_profileCourses); + } + + private void UpdateResultsInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (keyboard.escapeKey.wasPressedThisFrame) + { + ReturnToLibrary(); + return; + } + + if (keyboard.rKey.wasPressedThisFrame) + { + ActivateResultsAction(ResultsMenuAction.Retry); + return; + } + + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + ActivateResultsAction(ResultsMenuAction.ReturnToSelect); + } + + private void ActivateResultsAction(ResultsMenuAction action) + { + switch (action) + { + case ResultsMenuAction.Retry: + QuickRetryLoadedSong(); + break; + case ResultsMenuAction.ReturnToSelect: + ReturnToLibrary(); + break; + } + } + + private void UpdateCourseResultsInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (keyboard.escapeKey.wasPressedThisFrame) + { + ReturnToCourseSelect(); + return; + } + + if (keyboard.leftArrowKey.wasPressedThisFrame || keyboard.upArrowKey.wasPressedThisFrame || + keyboard.rightArrowKey.wasPressedThisFrame || keyboard.downArrowKey.wasPressedThisFrame) + _courseResultSelection = 1 - _courseResultSelection; + + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + ActivateCourseResultAction((CourseResultMenuAction)_courseResultSelection); + } + + private void ActivateCourseResultAction(CourseResultMenuAction action) + { + switch (action) + { + case CourseResultMenuAction.Retry: + if (_activeCourse != null) + StartCourse(_activeCourse); + else + ReturnToCourseSelect(); + break; + case CourseResultMenuAction.ReturnToCourseSelect: + ReturnToCourseSelect(); + break; + } + } + + private void UpdatePauseInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (keyboard.escapeKey.wasPressedThisFrame) + { + ResumeGameplay(); + return; + } + + var speedDelta = PauseNoteSpeedDelta( + keyboard.digit1Key.wasPressedThisFrame || + keyboard.numpad1Key.wasPressedThisFrame, + keyboard.digit2Key.wasPressedThisFrame || + keyboard.numpad2Key.wasPressedThisFrame); + if (Math.Abs(speedDelta) > 0.001f) + ChangeNoteSpeed(speedDelta); + + if (keyboard.downArrowKey.wasPressedThisFrame) + _pauseSelection = (_pauseSelection + 1) % 3; + if (keyboard.upArrowKey.wasPressedThisFrame) + _pauseSelection = (_pauseSelection + 2) % 3; + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + ActivatePauseAction((PauseMenuAction)_pauseSelection); + } + + internal static float PauseNoteSpeedDelta(bool decreasePressed, bool increasePressed) + { + if (decreasePressed == increasePressed) + return 0f; + return decreasePressed ? -0.1f : 0.1f; + } + + internal static bool ResolveAutoScratchForSession( + bool autoScratchEnabled, + bool isCourseStage) => + autoScratchEnabled && !isCourseStage; + + private void UpdateGameOverInput() + { + var keyboard = Keyboard.current; + if (keyboard == null) + return; + + if (keyboard.escapeKey.wasPressedThisFrame) + { + ReturnToLibrary(); + return; + } + _gameOverSelection = NavigateGameOverSelection( + _gameOverSelection, + keyboard.upArrowKey.wasPressedThisFrame, + keyboard.downArrowKey.wasPressedThisFrame); + if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + ActivateGameOverAction((GameOverMenuAction)_gameOverSelection); + } + + internal static int NavigateGameOverSelection( + int currentSelection, + bool upPressed, + bool downPressed) + { + var normalized = ((currentSelection % 2) + 2) % 2; + if (upPressed == downPressed) + return normalized; + return 1 - normalized; + } + + private void PauseGameplay() + { + _flow.BeginPause(); + _nativeInputPoller?.Suspend(); + if (DynamicBgaEnabled) + _bga?.Pause(); + _audio?.Pause(); + } + + private void ResumeGameplay() + { + _audio?.Resume(); + if (_gameplayDiagnosticsEnabled) + { + _framePacingMonitor.Reset( + Time.realtimeSinceStartupAsDouble, + GC.GetAllocatedBytesForCurrentThread()); + } + if (DynamicBgaEnabled) + _bga?.Resume(_frameSongTime); + + bool[] physicallyHeldLanes = null; + if (_nativeInputPoller?.IsRunning == true) + { + physicallyHeldLanes = _nativeInputPoller.ResumeAndCaptureHeldLanes(); + } + else + { + var keyboard = Keyboard.current; + if (keyboard != null && _laneHeld != null && _chart?.Metadata != null) + { + physicallyHeldLanes = new bool[_laneHeld.Length]; + for (var lane = 0; lane < _laneHeld.Length; lane++) + { + physicallyHeldLanes[lane] = IsGameplayLanePhysicallyHeld( + keyboard, + _chart.Metadata.KeyCount, + _chart.Metadata.HasScratch, + lane); + } + } + } + _gameplaySession.Resume(CaptureGameplayClock(), physicallyHeldLanes); + + _mode = AppMode.Gameplay; + } + + private void ActivatePauseAction(PauseMenuAction action) + { + switch (action) + { + case PauseMenuAction.Resume: + ResumeGameplay(); + break; + case PauseMenuAction.Retry: + QuickRetryLoadedSong(); + break; + case PauseMenuAction.ExitToSelect: + _audio?.Resume(); + if (_activeCourse != null) + ReturnToCourseSelect(); + else + ReturnToLibrary(); + break; + } + } + + private void EnterGameOver() + { + LogInputDiagnostics("GAME OVER"); + if (_activeCourse != null) + { + FinishCourseFailure(); + return; + } + + DisableLaneInput(); + _audio?.Stop(); + if (DynamicBgaEnabled) + _bga?.Pause(); + RecordCurrentPlay(completed: false, cleared: false, SongClearLamp.Failed); + _resultSession.BeginGameOver(); + _flow.BeginGameOver(); + } + + private void ActivateGameOverAction(GameOverMenuAction action) + { + switch (action) + { + case GameOverMenuAction.Retry: + QuickRetryLoadedSong(); + break; + case GameOverMenuAction.ExitToSelect: + ReturnToLibrary(); + break; + } + } + + private void QuickRetryLoadedSong() + { + if (_activeCourse != null) + { + StartCourse(_activeCourse); + return; + } + + if (_chart == null) + { + ReturnToLibrary(); + return; + } + + BeginLoadedSongPlayback(); + Debug.Log($"[ProjectQuint] Quick retry {_chart.Metadata.Title}; loaded audio, chart and BGA resources reused."); + } + + private void UpdateGameplayPresentation(bool advanceNoteWindow) + { + using var renderScope = GameplayRenderMarker.Auto(); + if (_gameplayDiagnosticsEnabled && + _gameplayPlayfieldPresenter.TryConsumeMeshTelemetry( + out var meshMilliseconds, + out var meshVertices)) + { + _workloadMonitor.RecordDuration(GameplayWorkloadPhase.PlayfieldMesh, meshMilliseconds); + _workloadMonitor.RecordMeshVertices(meshVertices); + } + var renderSongTime = GameplaySessionController.ResolveGameplayRenderSongTime( + advanceNoteWindow && (_audio?.StartDspTime ?? 0) > 0, + _gameplaySession.SampleSongTime(CaptureGameplayClock()), + _frameSongTime); + var phaseStarted = BeginGameplayPhaseTiming(); + if (advanceNoteWindow) + { + _gameplayNoteWindow.Update( + _judgement, + renderSongTime, + ProjectQuintPresentation.CalculateApproachSeconds(_noteSpeed), + _chart?.ScrollMap, + _chart?.Metadata?.InitialTempo ?? 150.0); + } + RecordGameplayPhase(GameplayWorkloadPhase.NoteWindow, phaseStarted); + if (_gameplayDiagnosticsEnabled) + _workloadMonitor.RecordVisibleNotes(_gameplayNoteWindow.VisibleNotes.Count); + + var bgaBaseTexture = DynamicBgaEnabled ? _bga?.CurrentBaseTexture : null; + var bgaLayerTexture = DynamicBgaEnabled ? _bga?.CurrentLayerTexture : null; + var bgaPoorTexture = DynamicBgaEnabled ? _bga?.CurrentPoorTexture : null; + var effectiveBgaDisplayMode = DynamicBgaEnabled + ? _bgaDisplayMode + : BgaDisplayMode.Full; + phaseStarted = BeginGameplayPhaseTiming(); + _gameplayPlayfieldPresenter.Update( + _gameplayNoteWindow.VisibleNotes, + _judgement, + renderSongTime, + _laneHeld, + _laneFlashTimes, + StageBackgroundEnabled ? _stageTexture : null, + bgaBaseTexture, + bgaLayerTexture, + bgaPoorTexture, + GameSettings.BackgroundDimOpacity(_backgroundDimPercent), + _noteSpeed, + _gearPosition, + _noteSkin, + _longNoteStyle, + effectiveBgaDisplayMode, + _gameplaySkin, + _laneSeparatorsEnabled, + _chart?.Metadata?.HasScratch == true, + _chart?.ScrollMap, + _chart?.Metadata?.InitialTempo ?? 150.0, + autoScratch: _gameplaySession?.IsAutoScratch == true); + RecordGameplayPhase(GameplayWorkloadPhase.PlayfieldSubmit, phaseStarted); + + var bgaStatus = _gameplaySession.IsClockFallbackActive + ? "DSP CLOCK STALLED // REALTIME FALLBACK ACTIVE" + : DynamicBgaEnabled ? _bga?.Status ?? string.Empty : string.Empty; + if (_activeCourse != null) + { + var courseStage = + $"DAN {_activeCourse.DisplayName} // STAGE {_courseStageIndex + 1}/{_activeCourse.StageCount}"; + bgaStatus = string.IsNullOrWhiteSpace(bgaStatus) + ? courseStage + : $"{courseStage} // {bgaStatus}"; + } + if (_gameplaySession.IsAutoPlay) + { + const string autoPlayStatus = "AUTO PLAY // RECORD NOT SAVED"; + bgaStatus = string.IsNullOrWhiteSpace(bgaStatus) + ? autoPlayStatus + : $"{autoPlayStatus} // {bgaStatus}"; + } + else if (_gameplaySession.IsAutoScratch) + { + const string autoScratchStatus = "AUTO SCRATCH // SCRATCH NOTES COUNT AS MISS"; + bgaStatus = string.IsNullOrWhiteSpace(bgaStatus) + ? autoScratchStatus + : $"{autoScratchStatus} // {bgaStatus}"; + } + phaseStarted = BeginGameplayPhaseTiming(); + _gameplayHudPresenter.Update( + _judgement, + _gauge, + renderSongTime, + _gearPosition, + _gameplaySkin, + bgaStatus, + _timingMeterEnabled, + _activeCourse != null ? _courseProgress : null); + if (_resultSession.GameplayOutroActive) + { + var completion = _resultSession.GameplayCompletion; + _gameplayHudPresenter.UpdateCompletion( + completion.PrimaryLabel, + completion.SecondaryLabel, + Math.Max( + 0.0, + Time.realtimeSinceStartupAsDouble - + _resultSession.GameplayOutroEnteredRealtime), + _gameplaySkin); + } + else + { + _gameplayHudPresenter.HideCompletion(); + } + RecordGameplayPhase(GameplayWorkloadPhase.HudSubmit, phaseStarted); + if (_gameplayDiagnosticsEnabled && advanceNoteWindow) + _inputMonitor.CommitVisualFeedback(GameplayWorkloadMonitor.Timestamp()); + } + + private bool EnableLaneInput() + { + DisableLaneInput(); + if (_chart?.Metadata == null) + return false; + +#if UNITY_STANDALONE_OSX && !UNITY_EDITOR + if (!_nativeInputPoller.TryStart( + _settings, + _chart.Metadata.KeyCount, + _chart.Metadata.HasScratch, + _inputPollingRate, + Time.realtimeSinceStartupAsDouble, + out var nativeInputError)) + { + SetError("NATIVE INPUT UNAVAILABLE", nativeInputError); + return false; + } + _inputPollingStatus = + $"NATIVE macOS ACTIVE // TARGET {GameSettings.InputPollingRateHz(_inputPollingRate)} Hz"; + if (_gameplayDiagnosticsEnabled) + _inputMonitor.Reset($"macOS NATIVE {GameSettings.InputPollingRateHz(_inputPollingRate)} Hz"); + return true; +#else + var keyboard = Keyboard.current; + if (keyboard == null) + return false; + + var keyCount = _chart.Metadata.KeyCount; + var hasScratch = _chart.Metadata.HasScratch; + _laneActions = new InputAction[_chart.Metadata.TotalLaneCount]; + for (var lane = 0; lane < _laneActions.Length; lane++) + { + var laneIndex = lane; + var action = new InputAction($"Lane {lane + 1}", InputActionType.Button); + foreach (var key in _settings.GameplayKeysForLane(keyCount, hasScratch, lane)) + action.AddBinding($"/{keyboard[key].name}"); + action.performed += context => OnLanePressed(laneIndex, context.time); + action.canceled += context => OnLaneReleased(laneIndex, context.time); + action.Enable(); + _laneActions[lane] = action; + } + return true; +#endif + } + + private bool IsGameplayLanePhysicallyHeld( + Keyboard keyboard, + int keyCount, + bool hasScratch, + int lane) + { + foreach (var key in _settings.GameplayKeysForLane(keyCount, hasScratch, lane)) + if (keyboard[key].isPressed) + return true; + return false; + } + + private void DisableLaneInput() + { + if (_nativeInputPoller?.IsRunning == true) + { + var snapshot = _nativeInputPoller.CaptureSnapshot(); + _inputPollingStatus = + $"LAST {snapshot.EffectiveHz:0} Hz // TARGET {snapshot.TargetHz} Hz // " + + $"LATE {snapshot.LateWakeupCount:N0} // DROPPED {snapshot.DroppedEventCount:N0}"; + Debug.Log($"[ProjectQuint Input Polling] {_inputPollingStatus}"); + _nativeInputPoller.Stop(); + } + if (_laneActions == null) + return; + foreach (var action in _laneActions) + { + action?.Disable(); + action?.Dispose(); + } + _laneActions = null; + } + + private void OnNativeLaneTransition( + int lane, + bool pressed, + double inputEventTime, + double currentInputTime) + { + if (pressed) + HandleLanePressed(lane, inputEventTime, currentInputTime); + else + HandleLaneReleased(lane, inputEventTime, currentInputTime); + } + + private void OnLanePressed(int lane, double inputEventTime) + => HandleLanePressed(lane, inputEventTime, InputState.currentTime); + + private void HandleLanePressed(int lane, double inputEventTime, double currentInputTime) + { + if (_mode != AppMode.Gameplay) + return; + + var callbackTimestamp = _gameplayDiagnosticsEnabled + ? GameplayWorkloadMonitor.Timestamp() + : 0; + var result = _gameplaySession.Press( + lane, + inputEventTime, + RecordInputTransition(lane, true, inputEventTime, currentInputTime), + CaptureGameplayClock(), + _judgementOffsetMilliseconds); + if (result.HasValue) + { + if (_gameplayDiagnosticsEnabled) + { + _inputMonitor.RecordJudgementDispatch( + callbackTimestamp, + GameplayWorkloadMonitor.Timestamp()); + _inputMonitor.QueueVisualFeedback(callbackTimestamp); + } + } + if (_reactiveKeySoundsEnabled && + _gameplaySession.TryResolvePressedKeySound(lane, result, out var soundId)) + { + if (_audio != null) + { + _audio.PlayKeySound(soundId, lane); + if (_gameplayDiagnosticsEnabled) + { + _inputMonitor.RecordReactiveAudioCommand( + callbackTimestamp, + GameplayWorkloadMonitor.Timestamp()); + } + } + } + } + + private void OnLaneReleased(int lane, double inputEventTime) + => HandleLaneReleased(lane, inputEventTime, InputState.currentTime); + + private void HandleLaneReleased(int lane, double inputEventTime, double currentInputTime) + { + if (_mode != AppMode.Gameplay) + return; + + // Tap calibration shifts note-on judgements only. Long-note tails have no + // timing grade, so applying the -25 ms tap offset made releases needlessly strict. + var callbackTimestamp = _gameplayDiagnosticsEnabled + ? GameplayWorkloadMonitor.Timestamp() + : 0; + var result = _gameplaySession.Release( + lane, + inputEventTime, + RecordInputTransition(lane, false, inputEventTime, currentInputTime), + CaptureGameplayClock()); + if (result.HasValue) + { + if (_gameplayDiagnosticsEnabled) + { + _inputMonitor.RecordJudgementDispatch( + callbackTimestamp, + GameplayWorkloadMonitor.Timestamp()); + _inputMonitor.QueueVisualFeedback(callbackTimestamp); + } + } + } + + private double RecordInputTransition( + int lane, + bool pressed, + double inputEventTime, + double currentInputTime) + { + // Keep InputState.currentTime as the judgement anchor. The diagnostic queue age + // uses realtime because InputAction timestamps share that timeline and Unity's + // own Input Debugger measures event age against realtimeSinceStartupAsDouble. + if (_gameplayDiagnosticsEnabled) + { + _inputMonitor.RecordTransition( + lane, + pressed, + inputEventTime, + Time.realtimeSinceStartupAsDouble, + Time.frameCount); + } + return currentInputTime; + } + + private void LogInputDiagnostics(string reason) + { + if (!_gameplayDiagnosticsEnabled) + return; + var snapshot = _inputMonitor.CaptureSnapshot(); + if (snapshot.EventCount <= 0) + return; + + Debug.Log( + $"[ProjectQuint Input] {reason}; device '{snapshot.DeviceName}', " + + $"events {snapshot.EventCount} ({snapshot.PressCount} press / " + + $"{snapshot.ReleaseCount} release), interval avg " + + $"{snapshot.AverageIntervalMilliseconds:0.###} ms, p50 " + + $"{snapshot.P50IntervalMilliseconds:0.###} ms, p95 " + + $"{snapshot.P95IntervalMilliseconds:0.###} ms, p99 " + + $"{snapshot.P99IntervalMilliseconds:0.###} ms, input queue avg " + + $"{snapshot.AverageQueueDelayMilliseconds:0.###} ms, p50/p95/p99/max " + + $"{snapshot.P50QueueDelayMilliseconds:0.###}/" + + $"{snapshot.P95QueueDelayMilliseconds:0.###}/" + + $"{snapshot.P99QueueDelayMilliseconds:0.###}/" + + $"{snapshot.MaximumQueueDelayMilliseconds:0.###} ms, pipeline " + + $"p50/p95/p99/max ms: judge {FormatInputPhase(snapshot.JudgementDispatchLatency)}, " + + $"reactive-audio {FormatInputPhase(snapshot.ReactiveAudioCommandLatency)}, " + + $"visual {FormatInputPhase(snapshot.VisualFeedbackLatency)}, visual drops " + + $"{snapshot.DroppedVisualFeedbackSamples}, max events/frame " + + $"{snapshot.MaximumEventsPerFrame}, out-of-order " + + $"{snapshot.OutOfOrderCount}, invalid transitions " + + $"{snapshot.InvalidTransitionCount}. This is observed event timing, not a hardware polling-rate setting."); + } + + private GameplayClockSample CaptureGameplayClock() => + new GameplayClockSample( + (_audio?.Clock ?? _audioClock)?.CurrentTime ?? 0, + _audio?.StartDspTime ?? 0, + Time.realtimeSinceStartupAsDouble, + _audioOutputLatencyMilliseconds); + + private void RefreshLibraryThumbnail() + { + var entry = _libraryController.State.SelectedSong; + if (entry == null) + { + ClearLibraryThumbnail(); + return; + } + + if (string.Equals(_thumbnailChartPath, entry.ChartPath, StringComparison.Ordinal)) + return; + + _thumbnailChartPath = entry.ChartPath; + var loadVersion = ++_thumbnailLoadVersion; + if (_libraryThumbnail != null) + { + Destroy(_libraryThumbnail); + _libraryThumbnail = null; + } + + var declared = !string.IsNullOrWhiteSpace(entry.ThumbnailFile) + ? entry.ThumbnailFile + : !string.IsNullOrWhiteSpace(entry.Metadata.StageFile) + ? entry.Metadata.StageFile + : entry.Metadata.BackBmp; + var imagePath = BmsResourceResolver.ResolveImage(entry.ChartPath, declared); + if (!string.IsNullOrEmpty(imagePath)) + StartCoroutine(LoadLibraryThumbnail(imagePath, loadVersion)); + } + + private IEnumerator LoadLibraryThumbnail(string imagePath, int loadVersion) + { + Texture2D loadedTexture = null; + yield return BmsImageLoader.Load(imagePath, texture => loadedTexture = texture); + if (loadVersion != _thumbnailLoadVersion) + { + BmsImageLoader.DestroyTexture(loadedTexture); + yield break; + } + + _libraryThumbnail = loadedTexture; + } + + private void ClearLibraryThumbnail() + { + _thumbnailLoadVersion++; + _thumbnailChartPath = string.Empty; + if (_libraryThumbnail == null) + return; + Destroy(_libraryThumbnail); + _libraryThumbnail = null; + } + + private void ClearLoadedSongState() + { + _songLoadGeneration++; + _audio?.Resume(); + DisableLaneInput(); + _preparedSong?.Dispose(); + _preparedSong = null; + _audio = null; + _bga = null; + _stageTexture = null; + _loadingEntry = null; + _playingEntry = null; + _gameplaySession.Clear(); + _resultSession.Clear(); + _gameplayNoteWindow.Reset(null); + _gameplayPlayfieldPresenter?.SetVisible(false); + _gameplayHudPresenter?.SetVisible(false); + } + + private void ReturnToLibrary() + { + ClearLoadedSongState(); + _courseSession.ClearRun(); + _mode = AppMode.Library; + RefreshLibraryThumbnail(); + } + + private void ReturnToCourseSelect() + { + ClearLoadedSongState(); + _courseSession.ClearRun(); + _mode = AppMode.CourseSelect; + } + + private void SetError(string title, string detail) + { + _audio?.Resume(); + DisableLaneInput(); + _audio?.Stop(); + _bga?.Stop(); + _flow.ShowError(title, detail); + Debug.LogError($"[ProjectQuint] {title}: {detail}"); + } + + private void OnApplicationFocus(bool hasFocus) + { + if (_nativeInputPoller?.IsRunning != true) + return; + if (!hasFocus) + { + _nativeInputPoller.Suspend(); + return; + } + if (_mode != AppMode.Gameplay) + return; + var physicallyHeld = _nativeInputPoller.ResumeAndCaptureHeldLanes(); + _gameplaySession?.Resume(CaptureGameplayClock(), physicallyHeld); + } + + private void OnDestroy() + { + CloseAudioSyncCalibration(); + FlushPendingLibraryViewState(force: true); + _audio?.Resume(); + _scanCancellation?.Cancel(); + _scanCancellation?.Dispose(); + _packImportCancellation?.Cancel(); + _packImportCancellation?.Dispose(); + _pinnedPackCacheCancellation?.Cancel(); + _pinnedPackCacheCancellation?.Dispose(); + DisableLaneInput(); + if (_gameplaySession != null) + { + _gameplaySession.Judged -= OnJudged; + _gameplaySession.AutoPlayNotePressed -= OnAutoPlayNotePressed; + _gameplaySession.Dispose(); + } + _preparedSong?.Dispose(); + _preparedSong = null; + _audio = null; + if (_audioBackendFactory is IDisposable disposableAudioBackendFactory) + disposableAudioBackendFactory.Dispose(); + _audioBackendFactory = null; + _audioClock = null; + _audioDspBufferController?.Dispose(); + _audioDspBufferController = null; + _nativeInputPoller?.Dispose(); + _nativeInputPoller = null; + _bga = null; + _stageTexture = null; + _homeScreenPresenter?.Dispose(); + _homeScreenPresenter = null; + _updateNoticePresenter?.Dispose(); + _updateNoticePresenter = null; + _sparkleUpdater?.Dispose(); + _sparkleUpdater = null; + _packMusicScreenPresenter?.Dispose(); + _packMusicScreenPresenter = null; + _optionsProfileCoursePresenter?.Dispose(); + _optionsProfileCoursePresenter = null; + if (_profileImageTexture != null) + Destroy(_profileImageTexture); + _profileImageTexture = null; + _sessionScreensPresenter?.Dispose(); + _sessionScreensPresenter = null; + _fpsCounterPresenter?.Dispose(); + _fpsCounterPresenter = null; + _gameplayHudPresenter?.Dispose(); + _gameplayHudPresenter = null; + _gameplayPlayfieldPresenter?.Dispose(); + _gameplayPlayfieldPresenter = null; + _startupBrandingPresenter?.Dispose(); + _startupBrandingPresenter = null; + if (_settingsRepository != null && _settings != null) + PersistSettings(); + ClearLibraryThumbnail(); + s_Created = false; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintApp.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintApp.cs.meta new file mode 100644 index 0000000..3366617 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintApp.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bbc67d4ac70d6498d9a2abb194bba739 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintSparkleUpdater.cs b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintSparkleUpdater.cs new file mode 100644 index 0000000..ce88832 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintSparkleUpdater.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.InteropServices; +using UnityEngine; + +namespace ProjectQuint.Bootstrap +{ + internal sealed class ProjectQuintSparkleUpdater : IDisposable + { + private const string NativeLibrary = "ProjectQuintUpdater"; + private bool _initialized; + + public bool IsReady => _initialized && CanCheckSafely(); + + public bool TryInitialize(out string error) + { + error = string.Empty; + if (!IsSupportedRuntime(Application.platform, Application.isEditor)) + { + error = "Sparkle is available only in the built macOS application."; + return false; + } + + try + { + _initialized = PQUpdaterInitialize(); + if (!_initialized) + error = ReadNativeError("Sparkle updater could not be initialized."); + return _initialized; + } + catch (Exception exception) when ( + exception is DllNotFoundException || + exception is EntryPointNotFoundException || + exception is BadImageFormatException) + { + error = exception.Message; + _initialized = false; + return false; + } + } + + public bool TryCheckForUpdates(out string error) + { + error = string.Empty; + if (!_initialized) + { + error = "Sparkle updater is not ready."; + return false; + } + + try + { + if (!PQUpdaterCanCheckForUpdates()) + { + error = ReadNativeError("Sparkle is already checking for an update."); + return false; + } + if (!PQUpdaterCheckForUpdates()) + { + error = ReadNativeError("Sparkle could not start the update."); + return false; + } + return true; + } + catch (Exception exception) when ( + exception is DllNotFoundException || + exception is EntryPointNotFoundException || + exception is BadImageFormatException) + { + error = exception.Message; + _initialized = false; + return false; + } + } + + public void Dispose() + { + if (!_initialized) + return; + try + { + PQUpdaterShutdown(); + } + catch (Exception exception) when ( + exception is DllNotFoundException || + exception is EntryPointNotFoundException || + exception is BadImageFormatException) + { + Debug.LogWarning($"[ProjectQuint] Sparkle shutdown fallback: {exception.Message}"); + } + _initialized = false; + } + + internal static bool IsSupportedRuntime(RuntimePlatform platform, bool isEditor) => + !isEditor && platform == RuntimePlatform.OSXPlayer; + + private bool CanCheckSafely() + { + try + { + return PQUpdaterCanCheckForUpdates(); + } + catch (Exception exception) when ( + exception is DllNotFoundException || + exception is EntryPointNotFoundException || + exception is BadImageFormatException) + { + _initialized = false; + return false; + } + } + + private static string ReadNativeError(string fallback) + { + var pointer = PQUpdaterGetLastError(); + var message = pointer == IntPtr.Zero ? string.Empty : Marshal.PtrToStringUTF8(pointer); + return string.IsNullOrWhiteSpace(message) ? fallback : message; + } + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool PQUpdaterInitialize(); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool PQUpdaterCanCheckForUpdates(); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool PQUpdaterCheckForUpdates(); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern void PQUpdaterShutdown(); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr PQUpdaterGetLastError(); + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintSparkleUpdater.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintSparkleUpdater.cs.meta new file mode 100644 index 0000000..b37cdaf --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintSparkleUpdater.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4e2085009a0f0439fbb15066dcaedd70 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintUpdateService.cs b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintUpdateService.cs new file mode 100644 index 0000000..c8de287 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintUpdateService.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections; +using System.Globalization; +using System.Text.RegularExpressions; +using ProjectQuint.Core.Release; +using UnityEngine; +using UnityEngine.Networking; + +namespace ProjectQuint.Bootstrap +{ + [Serializable] + public sealed class ProjectQuintUpdateManifest + { + public int schemaVersion; + public string channel = string.Empty; + public bool available; + public string version = string.Empty; + public int buildNumber; + public string publishedAt = string.Empty; + public string downloadUrl = string.Empty; + public string notesUrl = string.Empty; + public string sha256 = string.Empty; + public long sizeBytes; + } + + public readonly struct ProjectQuintUpdateCheckResult + { + public ProjectQuintUpdateCheckResult( + bool succeeded, + ProjectQuintUpdateManifest manifest, + string error) + { + Succeeded = succeeded; + Manifest = manifest; + Error = error ?? string.Empty; + } + + public bool Succeeded { get; } + public ProjectQuintUpdateManifest Manifest { get; } + public string Error { get; } + } + + public static class ProjectQuintUpdateService + { + public const string ManifestUrl = + "https://projectquint.duckdns.org/updates/latest.json"; + public const string DownloadPageUrl = + "https://projectquint.duckdns.org/download/"; + public const string ExpectedHost = "projectquint.duckdns.org"; + public static readonly TimeSpan AutomaticCheckInterval = TimeSpan.FromHours(24); + + private static readonly Regex AlphaVersionPattern = new Regex( + @"^\d+\.\d+\.\d+-alpha\.\d+$", + RegexOptions.CultureInvariant); + private static readonly Regex Sha256Pattern = new Regex( + "^[0-9a-fA-F]{64}$", + RegexOptions.CultureInvariant); + + public static IEnumerator Check( + Action completed) + { + if (completed == null) + throw new ArgumentNullException(nameof(completed)); + + using var request = UnityWebRequest.Get(ManifestUrl); + request.timeout = 10; + request.SetRequestHeader("Accept", "application/json"); + yield return request.SendWebRequest(); + + if (request.result != UnityWebRequest.Result.Success) + { + completed(new ProjectQuintUpdateCheckResult( + false, + null, + string.IsNullOrWhiteSpace(request.error) + ? "UPDATE SERVER UNAVAILABLE" + : request.error)); + yield break; + } + + if (!TryParseManifest(request.downloadHandler.text, out var manifest, out var error)) + { + completed(new ProjectQuintUpdateCheckResult(false, null, error)); + yield break; + } + + completed(new ProjectQuintUpdateCheckResult(true, manifest, string.Empty)); + } + + public static bool TryParseManifest( + string json, + out ProjectQuintUpdateManifest manifest, + out string error) + { + manifest = null; + error = string.Empty; + if (string.IsNullOrWhiteSpace(json)) + return Fail("UPDATE MANIFEST IS EMPTY", out error); + + try + { + manifest = JsonUtility.FromJson(json); + } + catch (Exception exception) + { + return Fail($"UPDATE MANIFEST JSON IS INVALID: {exception.Message}", out error); + } + + if (manifest == null) + return Fail("UPDATE MANIFEST JSON IS INVALID", out error); + if (manifest.schemaVersion != 1) + return Fail("UPDATE MANIFEST SCHEMA IS UNSUPPORTED", out error); + if (!string.Equals( + manifest.channel, + ProjectQuintReleaseVersion.ReleaseChannel, + StringComparison.Ordinal)) + return Fail("UPDATE CHANNEL DOES NOT MATCH THIS BUILD", out error); + + if (!manifest.available) + return true; + if (!AlphaVersionPattern.IsMatch(manifest.version ?? string.Empty)) + return Fail("UPDATE VERSION IS INVALID", out error); + if (manifest.buildNumber <= 0) + return Fail("UPDATE BUILD NUMBER IS INVALID", out error); + if (!DateTimeOffset.TryParse( + manifest.publishedAt, + CultureInfo.InvariantCulture, + DateTimeStyles.RoundtripKind, + out _)) + return Fail("UPDATE PUBLISH DATE IS INVALID", out error); + if (!TryValidateProjectQuintUri( + manifest.downloadUrl, + requireDownloadPath: true, + out _)) + return Fail("UPDATE DOWNLOAD URL IS NOT TRUSTED", out error); + if (!TryValidateNotesUrl(manifest.notesUrl)) + return Fail("UPDATE NOTES URL IS NOT TRUSTED", out error); + if (!Sha256Pattern.IsMatch(manifest.sha256 ?? string.Empty)) + return Fail("UPDATE SHA-256 IS INVALID", out error); + if (manifest.sizeBytes < 1024 * 1024 || + manifest.sizeBytes > 1024L * 1024L * 1024L) + return Fail("UPDATE FILE SIZE IS INVALID", out error); + + manifest.version = manifest.version.Trim(); + manifest.sha256 = manifest.sha256.ToLowerInvariant(); + return true; + } + + public static bool IsUpdateAvailable(ProjectQuintUpdateManifest manifest) => + manifest != null && + manifest.available && + manifest.buildNumber > ProjectQuintReleaseVersion.BuildNumber; + + public static bool ShouldOffer( + ProjectQuintUpdateManifest manifest, + string skippedVersion, + bool manual) => + IsUpdateAvailable(manifest) && + (manual || + !string.Equals( + manifest.version, + skippedVersion?.Trim(), + StringComparison.OrdinalIgnoreCase)); + + public static bool ShouldCheckAutomatically(long lastSuccessfulCheckUtcTicks, long nowUtcTicks) + { + if (lastSuccessfulCheckUtcTicks <= 0 || nowUtcTicks <= lastSuccessfulCheckUtcTicks) + return true; + + return new TimeSpan(nowUtcTicks - lastSuccessfulCheckUtcTicks) >= + AutomaticCheckInterval; + } + + private static bool TryValidateNotesUrl(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return false; + if (value.StartsWith("/", StringComparison.Ordinal) && + !value.StartsWith("//", StringComparison.Ordinal)) + return true; + return TryValidateProjectQuintUri(value, requireDownloadPath: false, out _); + } + + private static bool TryValidateProjectQuintUri( + string value, + bool requireDownloadPath, + out Uri uri) + { + uri = null; + if (!Uri.TryCreate(value, UriKind.Absolute, out var parsed) || + !string.Equals(parsed.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) || + !string.Equals(parsed.Host, ExpectedHost, StringComparison.OrdinalIgnoreCase) || + !parsed.IsDefaultPort || + !string.IsNullOrEmpty(parsed.UserInfo)) + return false; + + if (requireDownloadPath && + (!parsed.AbsolutePath.StartsWith("/downloads/", StringComparison.Ordinal) || + !parsed.AbsolutePath.EndsWith(".dmg", StringComparison.OrdinalIgnoreCase))) + return false; + + uri = parsed; + return true; + } + + private static bool Fail(string message, out string error) + { + error = message; + return false; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintUpdateService.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintUpdateService.cs.meta new file mode 100644 index 0000000..9bc2043 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/ProjectQuintUpdateService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0a88f05bf2db44482bf8558fdbffd3e0 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/StandaloneProfileOptions.cs b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/StandaloneProfileOptions.cs new file mode 100644 index 0000000..370b729 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/StandaloneProfileOptions.cs @@ -0,0 +1,159 @@ +using System; +using ProjectQuint.Settings; + +namespace ProjectQuint.Bootstrap +{ + internal readonly struct StandaloneProfileOptions + { + internal const string ChartArgument = "-projectquint-profile-chart"; + internal const string DurationArgument = "-projectquint-profile-seconds"; + internal const string VSyncArgument = "-projectquint-profile-vsync"; + internal const string BgaArgument = "-projectquint-profile-bga"; + internal const string WindowArgument = "-projectquint-profile-window"; + internal const double DefaultDurationSeconds = 48.0; + + public StandaloneProfileOptions( + string chartPath, + double durationSeconds, + bool? vSyncEnabled = null, + BgaPlaybackMode? bgaPlayback = null, + WindowMode? windowMode = null) + { + ChartPath = chartPath ?? string.Empty; + DurationSeconds = durationSeconds; + VSyncEnabled = vSyncEnabled; + BgaPlayback = bgaPlayback; + WindowMode = windowMode; + } + + public string ChartPath { get; } + public double DurationSeconds { get; } + public bool? VSyncEnabled { get; } + public BgaPlaybackMode? BgaPlayback { get; } + public WindowMode? WindowMode { get; } + public bool Enabled => !string.IsNullOrWhiteSpace(ChartPath); + + public static bool TryParse( + string[] arguments, + out StandaloneProfileOptions options, + out string error) + { + options = default; + error = string.Empty; + if (arguments == null || arguments.Length == 0) + return true; + + var chartPath = string.Empty; + var durationSeconds = DefaultDurationSeconds; + bool? vSyncEnabled = null; + BgaPlaybackMode? bgaPlayback = null; + WindowMode? windowMode = null; + for (var index = 0; index < arguments.Length; index++) + { + var argument = arguments[index]; + if (string.Equals(argument, ChartArgument, StringComparison.OrdinalIgnoreCase)) + { + if (++index >= arguments.Length || string.IsNullOrWhiteSpace(arguments[index])) + { + error = $"{ChartArgument} requires a BMS chart path."; + return false; + } + chartPath = arguments[index]; + continue; + } + + if (string.Equals(argument, VSyncArgument, StringComparison.OrdinalIgnoreCase)) + { + if (++index >= arguments.Length || + !TryParseToggle(arguments[index], out var parsedVSync)) + { + error = $"{VSyncArgument} must be 'on' or 'off'."; + return false; + } + vSyncEnabled = parsedVSync; + continue; + } + + if (string.Equals(argument, BgaArgument, StringComparison.OrdinalIgnoreCase)) + { + if (++index >= arguments.Length || + !TryParseBga(arguments[index], out var parsedBga)) + { + error = $"{BgaArgument} must be 'full', 'static', or 'black'."; + return false; + } + bgaPlayback = parsedBga; + continue; + } + + if (string.Equals(argument, WindowArgument, StringComparison.OrdinalIgnoreCase)) + { + if (++index >= arguments.Length || + !TryParseWindow(arguments[index], out var parsedWindow)) + { + error = $"{WindowArgument} must be 'fullscreen' or 'windowed'."; + return false; + } + windowMode = parsedWindow; + continue; + } + + if (!string.Equals(argument, DurationArgument, StringComparison.OrdinalIgnoreCase)) + continue; + if (++index >= arguments.Length || + !double.TryParse( + arguments[index], + System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, + out durationSeconds) || + durationSeconds < 6.0 || durationSeconds > 600.0) + { + error = $"{DurationArgument} must be between 6 and 600 seconds."; + return false; + } + } + + options = new StandaloneProfileOptions( + chartPath, + durationSeconds, + vSyncEnabled, + bgaPlayback, + windowMode); + return true; + } + + private static bool TryParseToggle(string value, out bool enabled) + { + if (string.Equals(value, "on", StringComparison.OrdinalIgnoreCase)) + { + enabled = true; + return true; + } + if (string.Equals(value, "off", StringComparison.OrdinalIgnoreCase)) + { + enabled = false; + return true; + } + enabled = false; + return false; + } + + private static bool TryParseBga(string value, out BgaPlaybackMode mode) + { + if (Enum.TryParse(value, true, out mode) && + Enum.IsDefined(typeof(BgaPlaybackMode), mode)) + return true; + mode = default; + return false; + } + + private static bool TryParseWindow(string value, out WindowMode mode) + { + if (Enum.TryParse(value, true, out mode) && + Enum.IsDefined(typeof(WindowMode), mode)) + return true; + mode = default; + return false; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Bootstrap/StandaloneProfileOptions.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/StandaloneProfileOptions.cs.meta new file mode 100644 index 0000000..ef77216 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Bootstrap/StandaloneProfileOptions.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4bc596ece525d4e5e82487fb62e0e38f \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features.meta b/Assets/ProjectQuint/Runtime/Unity/Features.meta new file mode 100644 index 0000000..f376132 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7416e3e446dc044e1a08a8720780fe39 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/AssemblyInfo.cs b/Assets/ProjectQuint/Runtime/Unity/Features/AssemblyInfo.cs new file mode 100644 index 0000000..12d104f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ProjectQuint.Bootstrap")] +[assembly: InternalsVisibleTo("ProjectQuint.Presentation")] +[assembly: InternalsVisibleTo("ProjectQuint.Tests.EditMode")] diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/AssemblyInfo.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/AssemblyInfo.cs.meta new file mode 100644 index 0000000..ec1a36e --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/AssemblyInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 77f06534c4f3b4fa8836f1b805ba368c \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Course.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Course.meta new file mode 100644 index 0000000..f537d5b --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Course.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5f7e08a14be014a85ae8de300614d18b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Course/CourseSessionController.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Course/CourseSessionController.cs new file mode 100644 index 0000000..e02e104 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Course/CourseSessionController.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Course; +using ProjectQuint.Core.Gauge; +using ProjectQuint.Core.Judgement; +using ProjectQuint.Library; + +namespace ProjectQuint.Features.Course +{ + /// + /// Owns the state that survives between stages of one BMSTable course run. + /// Stage-local chart, audio and BGA resources remain outside this controller. + /// + internal sealed class CourseSessionController + { + public AeryCourseDefinition ActiveCourse { get; private set; } + public CourseProgress Progress { get; private set; } + public AeryCourseTrophy Trophy { get; private set; } + public double GaugeValue { get; private set; } = PlayGauge.ClassInitialValue; + public int SelectedIndex { get; private set; } + public int StageIndex { get; private set; } + public int ResultSelection { get; set; } = 1; + public bool StageAccumulated { get; private set; } + public bool Passed { get; private set; } + public bool CertificationBadgeEarned { get; private set; } + public string Status { get; set; } = string.Empty; + private bool HadCertificationAtStart { get; set; } + public bool IsActive => ActiveCourse != null; + + public void Select(int requestedIndex, int courseCount) + { + SelectedIndex = ClampIndex(requestedIndex, courseCount); + } + + public void MoveSelection(int delta, int courseCount) + { + Select(SelectedIndex + delta, courseCount); + } + + public void Begin(AeryCourseDefinition course) + { + ActiveCourse = course ?? throw new ArgumentNullException(nameof(course)); + if (!course.IsPlayable) + throw new InvalidOperationException(course.UnavailableReason); + Progress = new CourseProgress(); + Trophy = null; + GaugeValue = PlayGauge.ClassInitialValue; + StageIndex = 0; + StageAccumulated = false; + Passed = false; + HadCertificationAtStart = course.Record?.Cleared == true; + CertificationBadgeEarned = false; + ResultSelection = 1; + } + + public bool TryBeginStage(int stageIndex, out SongLibraryEntry entry) + { + entry = null; + if (ActiveCourse == null || !ActiveCourse.IsPlayable || + stageIndex < 0 || stageIndex >= ActiveCourse.StageCount) + return false; + + StageIndex = stageIndex; + StageAccumulated = false; + entry = ActiveCourse.Stages[stageIndex]; + return entry != null; + } + + public void CaptureGauge(double value) + { + GaugeValue = Math.Max(0.0, Math.Min(100.0, value)); + } + + public bool TryAccumulateStage(RhythmJudgementEngine judgement, bool cleared) + { + if (StageAccumulated || Progress == null || judgement == null) + return false; + + StageAccumulated = true; + Progress.AddStage(judgement, cleared); + return true; + } + + public AeryCourseResult Complete(bool passed, string modifier) + { + Passed = passed; + CertificationBadgeEarned = passed && !HadCertificationAtStart; + Trophy = passed + ? ActiveCourse?.ResolveTrophy( + Progress?.MissRate ?? 100.0, + Progress?.ExScoreRate ?? 0.0) + : null; + ResultSelection = 1; + return new AeryCourseResult( + passed, + StageIndex + 1, + Progress, + Trophy, + modifier); + } + + public void ClearRun() + { + ActiveCourse = null; + Progress = null; + Trophy = null; + GaugeValue = PlayGauge.ClassInitialValue; + StageIndex = 0; + StageAccumulated = false; + Passed = false; + HadCertificationAtStart = false; + CertificationBadgeEarned = false; + ResultSelection = 1; + } + + private static int ClampIndex(int requestedIndex, int count) + { + if (count <= 0) + return 0; + return Math.Max(0, Math.Min(count - 1, requestedIndex)); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Course/CourseSessionController.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Course/CourseSessionController.cs.meta new file mode 100644 index 0000000..533ebbb --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Course/CourseSessionController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f475f77270de4458a8446485d0fb2dd2 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Flow.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Flow.meta new file mode 100644 index 0000000..aac5d0d --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Flow.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7679479d3e559492ba25c0269c891213 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Flow/AppFlowController.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/AppFlowController.cs new file mode 100644 index 0000000..4f1904b --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/AppFlowController.cs @@ -0,0 +1,121 @@ +using System; + +namespace ProjectQuint.Features.Flow +{ + public enum OptionsCategory + { + Gameplay, + Input, + Latency, + Audio, + Display, + Data, + } + + internal enum AppMode + { + StartupAttribution, + TeamDeniaBranding, + Home, + PackSelect, + PackImport, + DifficultyTableSetup, + Options, + Scanning, + Library, + CourseSelect, + QuickOptions, + Loading, + Gameplay, + Paused, + GameOver, + Results, + CourseResults, + Error, + } + + /// + /// Owns navigation and screen-local presentation state. Unity lifecycle code may request + /// transitions, but it no longer stores a second copy of the active screen or menu cursor. + /// + internal sealed class AppFlowController + { + public AppMode Mode { get; private set; } = AppMode.StartupAttribution; + public string Status { get; set; } = string.Empty; + public string ErrorTitle { get; private set; } = string.Empty; + public string ErrorDetail { get; private set; } = string.Empty; + + public static bool ShouldShowFpsCounter(AppMode mode, bool enabled) => + enabled && mode != AppMode.StartupAttribution && mode != AppMode.TeamDeniaBranding; + public string LoadingTitle { get; private set; } = string.Empty; + public string LoadingDetail { get; private set; } = string.Empty; + public int LoadingCurrent { get; private set; } + public int LoadingTotal { get; private set; } + public double LoadingEnteredRealtime { get; private set; } + public int HomeSelection { get; set; } + public int PackSelection { get; set; } + public OptionsCategory OptionsCategory { get; set; } = OptionsCategory.Gameplay; + public int OptionsSelectedRow { get; set; } + public int OptionsInputKeyCount { get; set; } = 4; + public int QuickOptionsSelectedRow { get; set; } + public int PauseSelection { get; set; } + public int GameOverSelection { get; set; } + public int RebindLane { get; set; } = -1; + + public void TransitionTo(AppMode mode) + { + Mode = mode; + } + + public void BeginOptions() + { + RebindLane = -1; + OptionsCategory = OptionsCategory.Gameplay; + OptionsSelectedRow = 0; + OptionsInputKeyCount = 4; + TransitionTo(AppMode.Options); + } + + public void BeginQuickOptions() + { + QuickOptionsSelectedRow = 0; + TransitionTo(AppMode.QuickOptions); + } + + public void BeginPause() + { + PauseSelection = 0; + TransitionTo(AppMode.Paused); + } + + public void BeginGameOver() + { + GameOverSelection = 0; + TransitionTo(AppMode.GameOver); + } + + public void BeginLoading(string title, string detail, double enteredRealtime = 0.0) + { + LoadingTitle = title ?? string.Empty; + LoadingDetail = detail ?? string.Empty; + LoadingCurrent = 0; + LoadingTotal = 0; + LoadingEnteredRealtime = Math.Max(0.0, enteredRealtime); + TransitionTo(AppMode.Loading); + } + + public void ReportLoading(string detail, int current, int total) + { + LoadingDetail = detail ?? string.Empty; + LoadingCurrent = current; + LoadingTotal = total; + } + + public void ShowError(string title, string detail) + { + ErrorTitle = title ?? string.Empty; + ErrorDetail = detail ?? string.Empty; + TransitionTo(AppMode.Error); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Flow/AppFlowController.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/AppFlowController.cs.meta new file mode 100644 index 0000000..119657d --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/AppFlowController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 69bb1e435a6e34004acdfff2ae582e41 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Flow/DirectionalKeyRepeat.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/DirectionalKeyRepeat.cs new file mode 100644 index 0000000..3cf366e --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/DirectionalKeyRepeat.cs @@ -0,0 +1,61 @@ +using System; + +namespace ProjectQuint.Features.Flow +{ + public sealed class DirectionalKeyRepeat + { + private readonly double _initialDelaySeconds; + private readonly double _repeatIntervalSeconds; + private readonly int _maximumRepeatsPerUpdate; + private int _direction; + private double _nextRepeatTime; + + public DirectionalKeyRepeat( + double initialDelaySeconds = 0.28, + double repeatIntervalSeconds = 0.0125, + int maximumRepeatsPerUpdate = 16) + { + if (initialDelaySeconds < 0.0) + throw new ArgumentOutOfRangeException(nameof(initialDelaySeconds)); + if (repeatIntervalSeconds <= 0.0) + throw new ArgumentOutOfRangeException(nameof(repeatIntervalSeconds)); + if (maximumRepeatsPerUpdate <= 0) + throw new ArgumentOutOfRangeException(nameof(maximumRepeatsPerUpdate)); + + _initialDelaySeconds = initialDelaySeconds; + _repeatIntervalSeconds = repeatIntervalSeconds; + _maximumRepeatsPerUpdate = maximumRepeatsPerUpdate; + } + + public int Update(int direction, bool pressedThisFrame, double now) + { + direction = Math.Sign(direction); + if (direction == 0) + { + Reset(); + return 0; + } + + if (pressedThisFrame || direction != _direction) + { + _direction = direction; + _nextRepeatTime = now + _initialDelaySeconds; + return direction; + } + + if (now < _nextRepeatTime) + return 0; + + var repeats = 1 + (int)Math.Floor((now - _nextRepeatTime) / _repeatIntervalSeconds); + repeats = Math.Min(repeats, _maximumRepeatsPerUpdate); + _nextRepeatTime += repeats * _repeatIntervalSeconds; + return direction * repeats; + } + + public void Reset() + { + _direction = 0; + _nextRepeatTime = 0.0; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Flow/DirectionalKeyRepeat.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/DirectionalKeyRepeat.cs.meta new file mode 100644 index 0000000..7532f32 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/DirectionalKeyRepeat.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 81afde7d0602f459f8d1cf8d453387b5 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Flow/StartupAttributionTimeline.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/StartupAttributionTimeline.cs new file mode 100644 index 0000000..7913398 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/StartupAttributionTimeline.cs @@ -0,0 +1,72 @@ +using System; + +namespace ProjectQuint.Features.Flow +{ + /// + /// Defines the unskippable startup interval used for required middleware attribution. + /// Keeping the timing independent from rendering makes the legal splash deterministic. + /// + internal static class StartupAttributionTimeline + { + public const double FadeInSeconds = 0.35; + public const double HoldSeconds = 1.80; + public const double FadeOutSeconds = 0.45; + public const double DurationSeconds = FadeInSeconds + HoldSeconds + FadeOutSeconds; + + public static bool IsComplete(double elapsedSeconds) => + elapsedSeconds >= DurationSeconds; + + public static float OpacityAt(double elapsedSeconds) + { + var elapsed = Math.Max(0.0, elapsedSeconds); + if (elapsed < FadeInSeconds) + return SmoothStep((float)(elapsed / FadeInSeconds)); + + var fadeOutStart = FadeInSeconds + HoldSeconds; + if (elapsed <= fadeOutStart) + return 1f; + if (elapsed >= DurationSeconds) + return 0f; + + return 1f - SmoothStep((float)((elapsed - fadeOutStart) / FadeOutSeconds)); + } + + private static float SmoothStep(float value) + { + var clamped = Math.Max(0f, Math.Min(1f, value)); + return clamped * clamped * (3f - 2f * clamped); + } + } + + internal static class TeamDeniaBrandingTimeline + { + public const double FadeInSeconds = 0.30; + public const double HoldSeconds = 1.20; + public const double FadeOutSeconds = 0.35; + public const double DurationSeconds = FadeInSeconds + HoldSeconds + FadeOutSeconds; + + public static bool IsComplete(double elapsedSeconds) => + elapsedSeconds >= DurationSeconds; + + public static float OpacityAt(double elapsedSeconds) + { + var elapsed = Math.Max(0.0, elapsedSeconds); + if (elapsed < FadeInSeconds) + return SmoothStep((float)(elapsed / FadeInSeconds)); + + var fadeOutStart = FadeInSeconds + HoldSeconds; + if (elapsed <= fadeOutStart) + return 1f; + if (elapsed >= DurationSeconds) + return 0f; + + return 1f - SmoothStep((float)((elapsed - fadeOutStart) / FadeOutSeconds)); + } + + private static float SmoothStep(float value) + { + var clamped = Math.Max(0f, Math.Min(1f, value)); + return clamped * clamped * (3f - 2f * clamped); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Flow/StartupAttributionTimeline.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/StartupAttributionTimeline.cs.meta new file mode 100644 index 0000000..c0d8dc7 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Flow/StartupAttributionTimeline.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2e771a20dd9bc42ea84beb283e3b11ab \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay.meta new file mode 100644 index 0000000..6a17402 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 204b7558ea2d248bc81cb1e94843272f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplayFramePacingMonitor.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplayFramePacingMonitor.cs new file mode 100644 index 0000000..ec81c38 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplayFramePacingMonitor.cs @@ -0,0 +1,481 @@ +using System; +using UnityEngine; + +namespace ProjectQuint.Features.Gameplay +{ + /// + /// Allocation-free rolling frame-time sampler sized for a two-second 240 Hz window. + /// A 50 ms-only hitch logger cannot see the 5-15 ms spikes that make high-refresh + /// rhythm gameplay look discontinuous, so this reports percentiles instead. + /// + internal sealed class GameplayFramePacingMonitor + { + internal const int SampleCapacity = 512; + internal const double ReportingWindowSeconds = 2.0; + internal const double DiscontinuityThresholdSeconds = 0.25; + private readonly double[] _frameMilliseconds = new double[SampleCapacity]; + private readonly double[] _sortedMilliseconds = new double[SampleCapacity]; + private int _sampleCount; + private double _lastRealtime = double.NegativeInfinity; + private double _windowStartedRealtime = double.NegativeInfinity; + private double _frameTotalMilliseconds; + private long _lastAllocatedBytes; + private long _maximumAllocatedBytes; + private int _missedRefreshFrameCount; + private int _estimatedMissedRefreshCount; + + public void Reset(double realtime, long allocatedBytes) + { + _sampleCount = 0; + _lastRealtime = realtime; + _windowStartedRealtime = realtime; + _frameTotalMilliseconds = 0; + _lastAllocatedBytes = allocatedBytes; + _maximumAllocatedBytes = 0; + _missedRefreshFrameCount = 0; + _estimatedMissedRefreshCount = 0; + } + + public bool AddSample( + double realtime, + long allocatedBytes, + int targetFramesPerSecond, + out GameplayFramePacingSnapshot snapshot) + { + snapshot = default; + if (double.IsNegativeInfinity(_lastRealtime)) + { + Reset(realtime, allocatedBytes); + return false; + } + + var frameMilliseconds = Math.Max(0, (realtime - _lastRealtime) * 1000.0); + _lastRealtime = realtime; + if (frameMilliseconds >= DiscontinuityThresholdSeconds * 1000.0) + { + Reset(realtime, allocatedBytes); + return false; + } + if (_sampleCount < SampleCapacity) + { + _frameMilliseconds[_sampleCount++] = frameMilliseconds; + _frameTotalMilliseconds += frameMilliseconds; + var sampleTarget = targetFramesPerSecond > 0 ? targetFramesPerSecond : 240; + var sampleBudget = 1000.0 / sampleTarget; + if (frameMilliseconds >= sampleBudget * 1.5) + { + _missedRefreshFrameCount++; + _estimatedMissedRefreshCount += Math.Max( + 1, + (int)Math.Round(frameMilliseconds / sampleBudget) - 1); + } + } + + if (allocatedBytes >= _lastAllocatedBytes) + _maximumAllocatedBytes = Math.Max(_maximumAllocatedBytes, allocatedBytes - _lastAllocatedBytes); + _lastAllocatedBytes = allocatedBytes; + + if (_sampleCount < SampleCapacity && + realtime - _windowStartedRealtime < ReportingWindowSeconds) + return false; + + Array.Copy(_frameMilliseconds, _sortedMilliseconds, _sampleCount); + Array.Sort(_sortedMilliseconds, 0, _sampleCount); + var averageMilliseconds = _sampleCount == 0 ? 0 : _frameTotalMilliseconds / _sampleCount; + var p50 = Percentile(_sortedMilliseconds, _sampleCount, 0.50); + var p95 = Percentile(_sortedMilliseconds, _sampleCount, 0.95); + var p99 = Percentile(_sortedMilliseconds, _sampleCount, 0.99); + var maximum = _sampleCount == 0 ? 0 : _sortedMilliseconds[_sampleCount - 1]; + var target = targetFramesPerSecond > 0 ? targetFramesPerSecond : 240; + var frameBudget = 1000.0 / target; + var warningThreshold = Math.Max(5.0, frameBudget * 1.5); + snapshot = new GameplayFramePacingSnapshot( + _sampleCount, + averageMilliseconds, + p50, + p95, + p99, + maximum, + _maximumAllocatedBytes, + frameBudget, + _missedRefreshFrameCount, + _estimatedMissedRefreshCount, + _missedRefreshFrameCount > 0 || p99 > warningThreshold); + + _sampleCount = 0; + _windowStartedRealtime = realtime; + _frameTotalMilliseconds = 0; + _maximumAllocatedBytes = 0; + _missedRefreshFrameCount = 0; + _estimatedMissedRefreshCount = 0; + return true; + } + + private static double Percentile(double[] sortedValues, int count, double percentile) + { + if (count <= 0) + return 0; + var index = (int)Math.Ceiling(percentile * count) - 1; + return sortedValues[Math.Max(0, Math.Min(count - 1, index))]; + } + } + + /// + /// Collects Unity's platform frame timings without allocating on gameplay frames. + /// The samples expose work and Present wait separately from Update-to-Update pacing. + /// + internal sealed class UnityFrameTimingMonitor + { + private readonly FrameTiming[] _latestTimings = new FrameTiming[1]; + private readonly GameplayPhaseHistogram _cpuFrame = new GameplayPhaseHistogram(); + private readonly GameplayPhaseHistogram _cpuMainThread = new GameplayPhaseHistogram(); + private readonly GameplayPhaseHistogram _cpuPresentWait = new GameplayPhaseHistogram(); + private readonly GameplayPhaseHistogram _gpuFrame = new GameplayPhaseHistogram(); + private ulong _lastFrameStartTimestamp; + private bool _hasFrameStartTimestamp; + private bool _featureEnabledObserved; + private float _vsyncsPerSecond; + private uint _latestSyncInterval; + + public void Reset() + { + ResetSamples(); + _lastFrameStartTimestamp = 0; + _hasFrameStartTimestamp = false; + _featureEnabledObserved = false; + _vsyncsPerSecond = 0f; + _latestSyncInterval = 0; + } + + public void CaptureLatest() + { + FrameTimingManager.CaptureFrameTimings(); + if (!FrameTimingManager.IsFeatureEnabled()) + return; + + _featureEnabledObserved = true; + _vsyncsPerSecond = FrameTimingManager.GetVSyncsPerSecond(); + if (float.IsNaN(_vsyncsPerSecond) || + float.IsInfinity(_vsyncsPerSecond) || + _vsyncsPerSecond < 0f) + _vsyncsPerSecond = 0f; + if (FrameTimingManager.GetLatestTimings(1, _latestTimings) == 0) + return; + AddSample(_latestTimings[0]); + } + + internal void AddSample(FrameTiming timing) + { + if (_hasFrameStartTimestamp && + timing.frameStartTimestamp == _lastFrameStartTimestamp) + return; + + _hasFrameStartTimestamp = true; + _lastFrameStartTimestamp = timing.frameStartTimestamp; + _latestSyncInterval = timing.syncInterval; + _cpuFrame.Add(timing.cpuFrameTime); + _cpuMainThread.Add(timing.cpuMainThreadFrameTime); + _cpuPresentWait.Add(timing.cpuMainThreadPresentWaitTime); + _gpuFrame.Add(timing.gpuFrameTime); + } + + public UnityFrameTimingSnapshot CaptureAndReset() + { + var snapshot = new UnityFrameTimingSnapshot( + _featureEnabledObserved, + _vsyncsPerSecond, + _latestSyncInterval, + _cpuFrame.Capture(), + _cpuMainThread.Capture(), + _cpuPresentWait.Capture(), + _gpuFrame.Capture()); + ResetSamples(); + return snapshot; + } + + private void ResetSamples() + { + _cpuFrame.Reset(); + _cpuMainThread.Reset(); + _cpuPresentWait.Reset(); + _gpuFrame.Reset(); + } + } + + internal enum GameplayWorkloadPhase + { + Audio, + Judgement, + Bga, + NoteWindow, + PlayfieldSubmit, + HudSubmit, + PlayfieldMesh, + } + + /// + /// Fixed-memory gameplay workload telemetry. Samples are accumulated into 0.01 ms + /// histogram buckets so the profiler itself never sorts or allocates on a play frame. + /// + internal sealed class GameplayWorkloadMonitor + { + private readonly GameplayPhaseHistogram[] _phases = + { + new GameplayPhaseHistogram(), + new GameplayPhaseHistogram(), + new GameplayPhaseHistogram(), + new GameplayPhaseHistogram(), + new GameplayPhaseHistogram(), + new GameplayPhaseHistogram(), + new GameplayPhaseHistogram(), + }; + private long _visibleNoteTotal; + private int _visibleNoteSamples; + private int _maximumVisibleNotes; + private long _meshVertexTotal; + private int _meshVertexSamples; + private int _maximumMeshVertices; + + public static long Timestamp() => System.Diagnostics.Stopwatch.GetTimestamp(); + + public void Record(GameplayWorkloadPhase phase, long startedTimestamp) + { + var elapsedMilliseconds = + (System.Diagnostics.Stopwatch.GetTimestamp() - startedTimestamp) * 1000.0 / + System.Diagnostics.Stopwatch.Frequency; + _phases[(int)phase].Add(elapsedMilliseconds); + } + + public void RecordDuration(GameplayWorkloadPhase phase, double elapsedMilliseconds) => + _phases[(int)phase].Add(elapsedMilliseconds); + + public void RecordVisibleNotes(int count) + { + count = Math.Max(0, count); + _visibleNoteTotal += count; + _visibleNoteSamples++; + _maximumVisibleNotes = Math.Max(_maximumVisibleNotes, count); + } + + public void RecordMeshVertices(int count) + { + count = Math.Max(0, count); + _meshVertexTotal += count; + _meshVertexSamples++; + _maximumMeshVertices = Math.Max(_maximumMeshVertices, count); + } + + public GameplayWorkloadSnapshot CaptureAndReset() + { + var snapshot = new GameplayWorkloadSnapshot( + _phases[(int)GameplayWorkloadPhase.Audio].Capture(), + _phases[(int)GameplayWorkloadPhase.Judgement].Capture(), + _phases[(int)GameplayWorkloadPhase.Bga].Capture(), + _phases[(int)GameplayWorkloadPhase.NoteWindow].Capture(), + _phases[(int)GameplayWorkloadPhase.PlayfieldSubmit].Capture(), + _phases[(int)GameplayWorkloadPhase.HudSubmit].Capture(), + _phases[(int)GameplayWorkloadPhase.PlayfieldMesh].Capture(), + _visibleNoteSamples == 0 ? 0 : _visibleNoteTotal / (double)_visibleNoteSamples, + _maximumVisibleNotes, + _meshVertexSamples == 0 ? 0 : _meshVertexTotal / (double)_meshVertexSamples, + _maximumMeshVertices); + foreach (var phase in _phases) + phase.Reset(); + _visibleNoteTotal = 0; + _visibleNoteSamples = 0; + _maximumVisibleNotes = 0; + _meshVertexTotal = 0; + _meshVertexSamples = 0; + _maximumMeshVertices = 0; + return snapshot; + } + } + + internal sealed class GameplayPhaseHistogram + { + internal const double BucketMilliseconds = 0.01; + internal const int BucketCount = 2002; + private readonly int[] _histogram = new int[BucketCount]; + private int _count; + private double _totalMilliseconds; + private double _maximumMilliseconds; + + public void Add(double milliseconds) + { + milliseconds = Math.Max(0, milliseconds); + var bucket = Math.Min(BucketCount - 1, (int)(milliseconds / BucketMilliseconds)); + _histogram[bucket]++; + _count++; + _totalMilliseconds += milliseconds; + _maximumMilliseconds = Math.Max(_maximumMilliseconds, milliseconds); + } + + public GameplayPhaseTimingSnapshot Capture() => new GameplayPhaseTimingSnapshot( + _count, + _count == 0 ? 0 : _totalMilliseconds / _count, + Percentile(0.50), + Percentile(0.95), + Percentile(0.99), + _maximumMilliseconds); + + public void Reset() + { + Array.Clear(_histogram, 0, _histogram.Length); + _count = 0; + _totalMilliseconds = 0; + _maximumMilliseconds = 0; + } + + private double Percentile(double percentile) + { + if (_count == 0) + return 0; + var target = (int)Math.Ceiling(_count * percentile); + var accumulated = 0; + for (var bucket = 0; bucket < _histogram.Length; bucket++) + { + accumulated += _histogram[bucket]; + if (accumulated >= target) + return bucket * BucketMilliseconds; + } + return (BucketCount - 1) * BucketMilliseconds; + } + } + + internal readonly struct GameplayPhaseTimingSnapshot + { + public GameplayPhaseTimingSnapshot( + int sampleCount, + double averageMilliseconds, + double p50Milliseconds, + double p95Milliseconds, + double p99Milliseconds, + double maximumMilliseconds) + { + SampleCount = sampleCount; + AverageMilliseconds = averageMilliseconds; + P50Milliseconds = p50Milliseconds; + P95Milliseconds = p95Milliseconds; + P99Milliseconds = p99Milliseconds; + MaximumMilliseconds = maximumMilliseconds; + } + + public int SampleCount { get; } + public double AverageMilliseconds { get; } + public double P50Milliseconds { get; } + public double P95Milliseconds { get; } + public double P99Milliseconds { get; } + public double MaximumMilliseconds { get; } + } + + internal readonly struct GameplayWorkloadSnapshot + { + public GameplayWorkloadSnapshot( + GameplayPhaseTimingSnapshot audio, + GameplayPhaseTimingSnapshot judgement, + GameplayPhaseTimingSnapshot bga, + GameplayPhaseTimingSnapshot noteWindow, + GameplayPhaseTimingSnapshot playfieldSubmit, + GameplayPhaseTimingSnapshot hudSubmit, + GameplayPhaseTimingSnapshot playfieldMesh, + double averageVisibleNotes, + int maximumVisibleNotes, + double averageMeshVertices, + int maximumMeshVertices) + { + Audio = audio; + Judgement = judgement; + Bga = bga; + NoteWindow = noteWindow; + PlayfieldSubmit = playfieldSubmit; + HudSubmit = hudSubmit; + PlayfieldMesh = playfieldMesh; + AverageVisibleNotes = averageVisibleNotes; + MaximumVisibleNotes = maximumVisibleNotes; + AverageMeshVertices = averageMeshVertices; + MaximumMeshVertices = maximumMeshVertices; + } + + public GameplayPhaseTimingSnapshot Audio { get; } + public GameplayPhaseTimingSnapshot Judgement { get; } + public GameplayPhaseTimingSnapshot Bga { get; } + public GameplayPhaseTimingSnapshot NoteWindow { get; } + public GameplayPhaseTimingSnapshot PlayfieldSubmit { get; } + public GameplayPhaseTimingSnapshot HudSubmit { get; } + public GameplayPhaseTimingSnapshot PlayfieldMesh { get; } + public double AverageVisibleNotes { get; } + public int MaximumVisibleNotes { get; } + public double AverageMeshVertices { get; } + public int MaximumMeshVertices { get; } + } + + internal readonly struct GameplayFramePacingSnapshot + { + public GameplayFramePacingSnapshot( + int sampleCount, + double averageMilliseconds, + double p50Milliseconds, + double p95Milliseconds, + double p99Milliseconds, + double maximumMilliseconds, + long maximumAllocatedBytes, + double frameBudgetMilliseconds, + int missedRefreshFrameCount, + int estimatedMissedRefreshCount, + bool isUnstable) + { + SampleCount = sampleCount; + AverageMilliseconds = averageMilliseconds; + P50Milliseconds = p50Milliseconds; + P95Milliseconds = p95Milliseconds; + P99Milliseconds = p99Milliseconds; + MaximumMilliseconds = maximumMilliseconds; + MaximumAllocatedBytes = maximumAllocatedBytes; + FrameBudgetMilliseconds = frameBudgetMilliseconds; + MissedRefreshFrameCount = missedRefreshFrameCount; + EstimatedMissedRefreshCount = estimatedMissedRefreshCount; + IsUnstable = isUnstable; + } + + public int SampleCount { get; } + public double AverageMilliseconds { get; } + public double P50Milliseconds { get; } + public double P95Milliseconds { get; } + public double P99Milliseconds { get; } + public double MaximumMilliseconds { get; } + public long MaximumAllocatedBytes { get; } + public double FrameBudgetMilliseconds { get; } + public int MissedRefreshFrameCount { get; } + public int EstimatedMissedRefreshCount { get; } + public bool IsUnstable { get; } + } + + internal readonly struct UnityFrameTimingSnapshot + { + public UnityFrameTimingSnapshot( + bool featureEnabled, + float vsyncsPerSecond, + uint syncInterval, + GameplayPhaseTimingSnapshot cpuFrame, + GameplayPhaseTimingSnapshot cpuMainThread, + GameplayPhaseTimingSnapshot cpuPresentWait, + GameplayPhaseTimingSnapshot gpuFrame) + { + FeatureEnabled = featureEnabled; + VSyncsPerSecond = vsyncsPerSecond; + SyncInterval = syncInterval; + CpuFrame = cpuFrame; + CpuMainThread = cpuMainThread; + CpuPresentWait = cpuPresentWait; + GpuFrame = gpuFrame; + } + + public bool FeatureEnabled { get; } + public float VSyncsPerSecond { get; } + public uint SyncInterval { get; } + public GameplayPhaseTimingSnapshot CpuFrame { get; } + public GameplayPhaseTimingSnapshot CpuMainThread { get; } + public GameplayPhaseTimingSnapshot CpuPresentWait { get; } + public GameplayPhaseTimingSnapshot GpuFrame { get; } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplayFramePacingMonitor.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplayFramePacingMonitor.cs.meta new file mode 100644 index 0000000..d02d3b9 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplayFramePacingMonitor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: db454f2fb72d74adaba344ce12fd9972 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplaySessionController.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplaySessionController.cs new file mode 100644 index 0000000..0dfbab0 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplaySessionController.cs @@ -0,0 +1,513 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Charts; +using ProjectQuint.Core.Gauge; +using ProjectQuint.Core.Judgement; +using ProjectQuint.Core.Timing; + +namespace ProjectQuint.Features.Gameplay +{ + [Flags] + internal enum GameplaySessionSignal + { + None = 0, + ClockFallbackActivated = 1 << 0, + GaugeFailed = 1 << 1, + SongFinished = 1 << 2, + GaugeShifted = 1 << 3, + } + + internal readonly struct GameplayClockSample + { + public GameplayClockSample( + double currentDspTime, + double startDspTime, + double currentRealtime, + int audioOutputLatencyMilliseconds) + { + CurrentDspTime = currentDspTime; + StartDspTime = startDspTime; + CurrentRealtime = currentRealtime; + AudioOutputLatencyMilliseconds = audioOutputLatencyMilliseconds; + } + + public double CurrentDspTime { get; } + public double StartDspTime { get; } + public double CurrentRealtime { get; } + public int AudioOutputLatencyMilliseconds { get; } + } + + internal readonly struct GameplaySessionStartRequest + { + public GameplaySessionStartRequest( + GaugeMode gaugeType, + double? restoredGaugeValue = null, + int restoredCombo = 0, + int restoredMaxCombo = 0, + CourseGaugeProfile courseGaugeProfile = CourseGaugeProfile.Lr2, + bool autoPlay = false, + bool gaugeFallbackEnabled = false, + bool autoScratch = false) + { + GaugeType = gaugeType; + RestoredGaugeValue = restoredGaugeValue; + RestoredCombo = restoredCombo; + RestoredMaxCombo = restoredMaxCombo; + CourseGaugeProfile = courseGaugeProfile; + AutoPlay = autoPlay; + GaugeFallbackEnabled = gaugeFallbackEnabled; + AutoScratch = autoScratch; + } + + public GaugeMode GaugeType { get; } + public double? RestoredGaugeValue { get; } + public int RestoredCombo { get; } + public int RestoredMaxCombo { get; } + public CourseGaugeProfile CourseGaugeProfile { get; } + public bool AutoPlay { get; } + public bool GaugeFallbackEnabled { get; } + public bool AutoScratch { get; } + } + + internal sealed class GameplaySessionController : IDisposable + { + private readonly ResilientSongClock _songClock = new ResilientSongClock(); + private IReadOnlyDictionary _notesById = + new Dictionary(); + private readonly List _autoPlayEvents = new List(); + private List[] _laneKeySounds = Array.Empty>(); + private int _autoPlayEventCursor; + private bool _previousClockFallbackState; + private GaugeFallbackChain _gaugeChain; + private bool _gaugeShiftPending; + + public GameplaySessionController() + { + LaneHeld = new bool[5]; + LaneFlashTimes = new double[5]; + ResetLaneState(); + } + + public event Action Judged; + public event Action AutoPlayNotePressed; + + public PlayableChart Chart { get; private set; } + public RhythmJudgementEngine Judgement { get; private set; } + public PlayGauge Gauge => _gaugeChain?.ActiveGauge; + public GaugeMode LastGaugeShiftFrom { get; private set; } + public GaugeMode LastGaugeShiftTo { get; private set; } + public bool[] LaneHeld { get; private set; } + public double[] LaneFlashTimes { get; private set; } + public double FrameSongTime { get; private set; } + public double LastPressSongTime { get; private set; } + public bool IsActive { get; private set; } + public bool IsClockFallbackActive => IsActive && _songClock.IsFallbackActive; + public bool IsAutoPlay { get; private set; } + public bool IsAutoScratch { get; private set; } + public bool AllowsRecordCommit => IsActive && !IsAutoPlay; + + public void LoadChart(PlayableChart chart, IReadOnlyDictionary notesById = null) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + + Clear(); + Chart = chart; + ConfigureLaneState(chart.Metadata?.TotalLaneCount ?? 5); + _laneKeySounds = LaneKeySoundResolver.Build(chart, LaneHeld.Length); + if (notesById != null) + { + _notesById = notesById; + return; + } + + var indexedNotes = new Dictionary(chart.Notes.Count); + foreach (var note in chart.Notes) + indexedNotes[note.Id] = note; + _notesById = indexedNotes; + } + + public void Start(GameplaySessionStartRequest request, GameplayClockSample clock) + { + if (Chart == null) + throw new InvalidOperationException("A chart must be loaded before gameplay starts."); + + ResetRuntimeState(); + Judgement = new RhythmJudgementEngine( + Chart.Notes, + laneCount: Chart.Metadata?.TotalLaneCount ?? 5, + tempoMap: Chart.TempoMap, + suppressMissPenalty: note => IsAutoScratch && note?.IsScratch == true); + Judgement.RestoreCourseCombo(request.RestoredCombo, request.RestoredMaxCombo); + _gaugeChain = new GaugeFallbackChain( + request.GaugeType, + Chart.Metadata.GaugeTotal, + Chart.Notes.Count, + request.GaugeFallbackEnabled, + request.RestoredGaugeValue, + request.CourseGaugeProfile); + Judgement.Judged += OnJudged; + _songClock.Reset(clock.CurrentDspTime, clock.StartDspTime, clock.CurrentRealtime); + _previousClockFallbackState = false; + IsAutoPlay = request.AutoPlay; + IsAutoScratch = + !IsAutoPlay && + request.AutoScratch && + Chart.Metadata?.HasScratch == true; + PrepareAutoPlayEvents(); + FrameSongTime = ResolveAudibleSongTime( + _songClock.CurrentSongTime, + clock.AudioOutputLatencyMilliseconds); + IsActive = true; + } + + public GameplaySessionSignal AdvanceClock(GameplayClockSample clock) + { + if (!IsActive) + return GameplaySessionSignal.None; + + FrameSongTime = SampleSongTime(clock); + var signal = !_previousClockFallbackState && _songClock.IsFallbackActive + ? GameplaySessionSignal.ClockFallbackActivated + : GameplaySessionSignal.None; + _previousClockFallbackState = _songClock.IsFallbackActive; + return signal; + } + + public GameplaySessionSignal EvaluateFrame(int judgementOffsetMilliseconds) + { + if (!IsActive || Judgement == null || Chart == null) + return GameplaySessionSignal.None; + + if (IsAutoPlay || IsAutoScratch) + ProcessAutoPlayEvents(FrameSongTime); + Judgement.Update( + FrameSongTime, + IsAutoPlay ? 0 : judgementOffsetMilliseconds * 0.001); + var signal = GameplaySessionSignal.None; + if (_gaugeShiftPending) + { + signal |= GameplaySessionSignal.GaugeShifted; + _gaugeShiftPending = false; + } + if (Gauge?.IsFailed == true) + signal |= GameplaySessionSignal.GaugeFailed; + if (FrameSongTime >= Chart.Duration + 0.5) + signal |= GameplaySessionSignal.SongFinished; + return signal; + } + + public JudgementResult? Press( + int lane, + double inputEventTime, + double currentInputTime, + GameplayClockSample clock, + int judgementOffsetMilliseconds) + { + if (!IsActive || Judgement == null || lane < 0 || lane >= LaneHeld.Length) + return null; + if (IsAutoScratchLane(lane)) + return null; + + LaneHeld[lane] = true; + var songTime = ResolveInputEventSongTime( + inputEventTime, + currentInputTime, + SampleSongTime(clock)); + LastPressSongTime = songTime; + var result = Judgement.Press( + lane, + songTime, + judgementOffsetMilliseconds * 0.001); + if (result.HasValue) + LaneFlashTimes[lane] = songTime; + return result; + } + + public JudgementResult? Release( + int lane, + double inputEventTime, + double currentInputTime, + GameplayClockSample clock) + { + if (!IsActive || Judgement == null || lane < 0 || lane >= LaneHeld.Length) + return null; + if (IsAutoScratchLane(lane)) + return null; + + LaneHeld[lane] = false; + return Judgement.Release( + lane, + ResolveInputEventSongTime( + inputEventTime, + currentInputTime, + SampleSongTime(clock))); + } + + public void Resume(GameplayClockSample clock, IReadOnlyList physicallyHeldLanes) + { + if (!IsActive || Judgement == null) + return; + + _songClock.Reanchor( + RestoreRawSongTime(FrameSongTime, clock.AudioOutputLatencyMilliseconds), + clock.CurrentDspTime, + clock.CurrentRealtime); + _previousClockFallbackState = false; + if (IsAutoPlay) + return; + if (physicallyHeldLanes == null) + return; + + for (var lane = 0; lane < LaneHeld.Length; lane++) + { + if (IsAutoScratchLane(lane)) + continue; + var physicallyHeld = lane < physicallyHeldLanes.Count && + physicallyHeldLanes[lane]; + if (LaneHeld[lane] && !physicallyHeld) + Judgement.Release(lane, FrameSongTime); + LaneHeld[lane] &= physicallyHeld; + } + } + + public double SampleSongTime(GameplayClockSample clock) + { + if (!IsActive || clock.StartDspTime <= 0) + return 0; + return ResolveAudibleSongTime( + _songClock.Sample( + clock.CurrentDspTime, + clock.StartDspTime, + clock.CurrentRealtime), + clock.AudioOutputLatencyMilliseconds); + } + + public bool TryGetLoadedNote(int noteId, out PlayableNote note) => + _notesById.TryGetValue(noteId, out note); + + public bool TryResolvePressedKeySound( + int lane, + JudgementResult? result, + out string soundId) + { + soundId = string.Empty; + if (IsAutoScratchLane(lane)) + return false; + if (result.HasValue && + _notesById.TryGetValue(result.Value.NoteId, out var hitNote) && + !string.IsNullOrWhiteSpace(hitNote.SoundId)) + { + soundId = hitNote.SoundId; + return true; + } + + return lane >= 0 && + lane < _laneKeySounds.Length && + LaneKeySoundResolver.TryResolve( + _laneKeySounds[lane], + LastPressSongTime, + out soundId); + } + + public void Clear() + { + ResetRuntimeState(); + Chart = null; + _notesById = new Dictionary(); + _laneKeySounds = Array.Empty>(); + } + + public void Dispose() + { + Clear(); + Judged = null; + AutoPlayNotePressed = null; + } + + internal static double ResolveInputEventSongTime( + double inputEventTime, + double currentInputTime, + double currentSongTime) => + ResilientSongClock.ResolveInputEventSongTime( + inputEventTime, + currentInputTime, + currentSongTime); + + internal static double ResolveAudibleSongTime( + double rawSongTime, + int audioOutputLatencyMilliseconds) => + rawSongTime - Math.Max(0, audioOutputLatencyMilliseconds) * 0.001; + + internal static double RestoreRawSongTime( + double audibleSongTime, + int audioOutputLatencyMilliseconds) => + audibleSongTime + Math.Max(0, audioOutputLatencyMilliseconds) * 0.001; + + internal static double ResolveGameplayRenderSongTime( + bool isGameplay, + double currentSongTime, + double frozenSongTime) => + isGameplay ? currentSongTime : frozenSongTime; + + private void OnJudged(JudgementResult result) + { + if (!result.IsHoldTick && !result.SuppressGameplayPenalty) + RegisterGaugeTransition(_gaugeChain?.Apply(result) ?? GaugeTransition.None); + else if (_notesById.TryGetValue(result.NoteId, out var note) && + note.Lane >= 0 && note.Lane < LaneFlashTimes.Length) + LaneFlashTimes[note.Lane] = FrameSongTime; + Judged?.Invoke(result); + } + + private void ResetRuntimeState() + { + if (Judgement != null) + Judgement.Judged -= OnJudged; + Judgement = null; + _gaugeChain = null; + _gaugeShiftPending = false; + LastGaugeShiftFrom = default; + LastGaugeShiftTo = default; + FrameSongTime = 0; + LastPressSongTime = 0; + IsActive = false; + IsAutoPlay = false; + IsAutoScratch = false; + _autoPlayEventCursor = 0; + _autoPlayEvents.Clear(); + _previousClockFallbackState = false; + ResetLaneState(); + } + + private void RegisterGaugeTransition(GaugeTransition transition) + { + if (!transition.Changed) + return; + + if (!_gaugeShiftPending) + LastGaugeShiftFrom = transition.From; + LastGaugeShiftTo = transition.To; + _gaugeShiftPending = true; + } + + private void ResetLaneState() + { + Array.Clear(LaneHeld, 0, LaneHeld.Length); + for (var lane = 0; lane < LaneFlashTimes.Length; lane++) + LaneFlashTimes[lane] = -10; + } + + private void ConfigureLaneState(int laneCount) + { + laneCount = Math.Max(1, Math.Min(8, laneCount)); + if (LaneHeld?.Length == laneCount && LaneFlashTimes?.Length == laneCount) + return; + LaneHeld = new bool[laneCount]; + LaneFlashTimes = new double[laneCount]; + ResetLaneState(); + } + + private void PrepareAutoPlayEvents() + { + _autoPlayEventCursor = 0; + _autoPlayEvents.Clear(); + if ((!IsAutoPlay && !IsAutoScratch) || Chart == null) + return; + + foreach (var note in Chart.Notes) + { + if (!IsAutoPlay && !note.IsScratch) + continue; + _autoPlayEvents.Add(new AutoPlayEvent(note.StartTime, note.Lane, note.Id, release: false)); + if (note.IsLong) + _autoPlayEvents.Add(new AutoPlayEvent(note.EndTime, note.Lane, note.Id, release: true)); + } + _autoPlayEvents.Sort(AutoPlayEvent.Compare); + } + + private void ProcessAutoPlayEvents(double songTime) + { + while (_autoPlayEventCursor < _autoPlayEvents.Count && + _autoPlayEvents[_autoPlayEventCursor].Time <= songTime + 0.000001) + { + var autoEvent = _autoPlayEvents[_autoPlayEventCursor++]; + if (autoEvent.Lane < 0 || autoEvent.Lane >= LaneHeld.Length) + continue; + + if (autoEvent.Release) + { + LaneHeld[autoEvent.Lane] = false; + if (IsAutoPlay) + Judgement.Release(autoEvent.Lane, autoEvent.Time); + continue; + } + + LaneHeld[autoEvent.Lane] = true; + if (IsAutoScratch) + { + LaneFlashTimes[autoEvent.Lane] = autoEvent.Time; + if (_notesById.TryGetValue(autoEvent.NoteId, out var scratchNote)) + { + AutoPlayNotePressed?.Invoke(scratchNote); + if (!scratchNote.IsLong) + LaneHeld[autoEvent.Lane] = false; + } + else + { + LaneHeld[autoEvent.Lane] = false; + } + continue; + } + + var result = Judgement.Press(autoEvent.Lane, autoEvent.Time, timingOffsetSeconds: 0); + if (result.HasValue) + { + LaneFlashTimes[autoEvent.Lane] = autoEvent.Time; + if (_notesById.TryGetValue(result.Value.NoteId, out var note)) + { + AutoPlayNotePressed?.Invoke(note); + if (!note.IsLong) + LaneHeld[autoEvent.Lane] = false; + } + } + else + { + LaneHeld[autoEvent.Lane] = false; + } + } + } + + private bool IsAutoScratchLane(int lane) => + IsAutoScratch && + Chart?.Metadata?.HasScratch == true && + lane == 0; + + private readonly struct AutoPlayEvent + { + public AutoPlayEvent(double time, int lane, int noteId, bool release) + { + Time = time; + Lane = lane; + NoteId = noteId; + Release = release; + } + + public double Time { get; } + public int Lane { get; } + public int NoteId { get; } + public bool Release { get; } + + public static int Compare(AutoPlayEvent left, AutoPlayEvent right) + { + var time = left.Time.CompareTo(right.Time); + if (time != 0) + return time; + if (left.Release != right.Release) + return left.Release ? -1 : 1; + var lane = left.Lane.CompareTo(right.Lane); + return lane != 0 ? lane : left.NoteId.CompareTo(right.NoteId); + } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplaySessionController.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplaySessionController.cs.meta new file mode 100644 index 0000000..8b94991 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/GameplaySessionController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f62ab48dbbfe84724bd269ffbfc21c86 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/HighRateInputMonitor.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/HighRateInputMonitor.cs new file mode 100644 index 0000000..7ec33ee --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/HighRateInputMonitor.cs @@ -0,0 +1,327 @@ +using System; + +namespace ProjectQuint.Features.Gameplay +{ + /// + /// Observes gameplay input timing without changing or resampling input events. + /// The hot path is allocation-free so high-report-rate keyboards do not create + /// additional frame pacing pressure. Reported rates are observations only; + /// keyboard polling remains owned by the device, driver and operating system. + /// + internal sealed class HighRateInputMonitor + { + private const int HistogramBucketCount = 15; + private const int PendingVisualCapacity = 512; + private readonly int[] _intervalHistogram = new int[HistogramBucketCount]; + private readonly int[] _queueDelayHistogram = new int[HistogramBucketCount]; + private readonly GameplayPhaseHistogram _judgementDispatchLatency = new GameplayPhaseHistogram(); + private readonly GameplayPhaseHistogram _reactiveAudioCommandLatency = new GameplayPhaseHistogram(); + private readonly GameplayPhaseHistogram _visualFeedbackLatency = new GameplayPhaseHistogram(); + private readonly long[] _pendingVisualTimestamps = new long[PendingVisualCapacity]; + private string _deviceName = "UNKNOWN KEYBOARD"; + private double _lastEventTime = double.NegativeInfinity; + private double _intervalTotalSeconds; + private double _minimumIntervalSeconds; + private double _maximumIntervalSeconds; + private double _queueDelayTotalSeconds; + private double _maximumQueueDelaySeconds; + private int _lastFrameIndex = int.MinValue; + private int _eventsInCurrentFrame; + private int _heldLaneMask; + private int _pendingVisualHead; + private int _pendingVisualCount; + + public int EventCount { get; private set; } + public int PressCount { get; private set; } + public int ReleaseCount { get; private set; } + public int IntervalCount { get; private set; } + public int OutOfOrderCount { get; private set; } + public int InvalidTransitionCount { get; private set; } + public int MaximumEventsPerFrame { get; private set; } + public int DroppedVisualFeedbackSamples { get; private set; } + + public void Reset(string deviceName) + { + Array.Clear(_intervalHistogram, 0, _intervalHistogram.Length); + Array.Clear(_queueDelayHistogram, 0, _queueDelayHistogram.Length); + _judgementDispatchLatency.Reset(); + _reactiveAudioCommandLatency.Reset(); + _visualFeedbackLatency.Reset(); + _deviceName = string.IsNullOrWhiteSpace(deviceName) ? "UNKNOWN KEYBOARD" : deviceName; + _lastEventTime = double.NegativeInfinity; + _intervalTotalSeconds = 0; + _minimumIntervalSeconds = double.PositiveInfinity; + _maximumIntervalSeconds = 0; + _queueDelayTotalSeconds = 0; + _maximumQueueDelaySeconds = 0; + _lastFrameIndex = int.MinValue; + _eventsInCurrentFrame = 0; + _heldLaneMask = 0; + _pendingVisualHead = 0; + _pendingVisualCount = 0; + EventCount = 0; + PressCount = 0; + ReleaseCount = 0; + IntervalCount = 0; + OutOfOrderCount = 0; + InvalidTransitionCount = 0; + MaximumEventsPerFrame = 0; + DroppedVisualFeedbackSamples = 0; + } + + public void RecordTransition( + int lane, + bool pressed, + double eventTime, + double observedInputTime, + int frameIndex) + { + EventCount++; + if (pressed) + PressCount++; + else + ReleaseCount++; + + if (frameIndex == _lastFrameIndex) + { + _eventsInCurrentFrame++; + } + else + { + _lastFrameIndex = frameIndex; + _eventsInCurrentFrame = 1; + } + if (_eventsInCurrentFrame > MaximumEventsPerFrame) + MaximumEventsPerFrame = _eventsInCurrentFrame; + + if (lane >= 0 && lane < 31) + { + var laneBit = 1 << lane; + var wasHeld = (_heldLaneMask & laneBit) != 0; + if (pressed) + { + if (wasHeld) + InvalidTransitionCount++; + _heldLaneMask |= laneBit; + } + else + { + if (!wasHeld) + InvalidTransitionCount++; + _heldLaneMask &= ~laneBit; + } + } + + if (!double.IsNegativeInfinity(_lastEventTime)) + { + var intervalSeconds = eventTime - _lastEventTime; + if (intervalSeconds < 0) + { + OutOfOrderCount++; + } + else + { + IntervalCount++; + _intervalTotalSeconds += intervalSeconds; + if (intervalSeconds < _minimumIntervalSeconds) + _minimumIntervalSeconds = intervalSeconds; + if (intervalSeconds > _maximumIntervalSeconds) + _maximumIntervalSeconds = intervalSeconds; + _intervalHistogram[HistogramBucket(intervalSeconds * 1000.0)]++; + } + } + _lastEventTime = eventTime; + + var queueDelaySeconds = Math.Max(0, observedInputTime - eventTime); + _queueDelayTotalSeconds += queueDelaySeconds; + if (queueDelaySeconds > _maximumQueueDelaySeconds) + _maximumQueueDelaySeconds = queueDelaySeconds; + _queueDelayHistogram[HistogramBucket(queueDelaySeconds * 1000.0)]++; + } + + public void RecordJudgementDispatch(long callbackTimestamp, long completedTimestamp) => + _judgementDispatchLatency.Add(ElapsedMilliseconds(callbackTimestamp, completedTimestamp)); + + public void RecordReactiveAudioCommand(long callbackTimestamp, long completedTimestamp) => + _reactiveAudioCommandLatency.Add(ElapsedMilliseconds(callbackTimestamp, completedTimestamp)); + + public void QueueVisualFeedback(long callbackTimestamp) + { + if (_pendingVisualCount >= PendingVisualCapacity) + { + DroppedVisualFeedbackSamples++; + return; + } + + var tail = (_pendingVisualHead + _pendingVisualCount) % PendingVisualCapacity; + _pendingVisualTimestamps[tail] = callbackTimestamp; + _pendingVisualCount++; + } + + public void CommitVisualFeedback(long committedTimestamp) + { + while (_pendingVisualCount > 0) + { + var callbackTimestamp = _pendingVisualTimestamps[_pendingVisualHead]; + _pendingVisualHead = (_pendingVisualHead + 1) % PendingVisualCapacity; + _pendingVisualCount--; + _visualFeedbackLatency.Add(ElapsedMilliseconds(callbackTimestamp, committedTimestamp)); + } + } + + public HighRateInputSnapshot CaptureSnapshot() => new HighRateInputSnapshot( + _deviceName, + EventCount, + PressCount, + ReleaseCount, + OutOfOrderCount, + InvalidTransitionCount, + MaximumEventsPerFrame, + IntervalCount > 0 ? _minimumIntervalSeconds * 1000.0 : 0, + IntervalCount > 0 ? _intervalTotalSeconds * 1000.0 / IntervalCount : 0, + IntervalCount > 0 ? _maximumIntervalSeconds * 1000.0 : 0, + HistogramPercentile(_intervalHistogram, IntervalCount, 0.50), + HistogramPercentile(_intervalHistogram, IntervalCount, 0.95), + HistogramPercentile(_intervalHistogram, IntervalCount, 0.99), + EventCount > 0 ? _queueDelayTotalSeconds * 1000.0 / EventCount : 0, + HistogramPercentile(_queueDelayHistogram, EventCount, 0.50), + HistogramPercentile(_queueDelayHistogram, EventCount, 0.95), + HistogramPercentile(_queueDelayHistogram, EventCount, 0.99), + _maximumQueueDelaySeconds * 1000.0, + _judgementDispatchLatency.Capture(), + _reactiveAudioCommandLatency.Capture(), + _visualFeedbackLatency.Capture(), + DroppedVisualFeedbackSamples); + + private static double ElapsedMilliseconds(long startedTimestamp, long completedTimestamp) => + Math.Max(0, completedTimestamp - startedTimestamp) * 1000.0 / + System.Diagnostics.Stopwatch.Frequency; + + private static int HistogramBucket(double milliseconds) + { + if (milliseconds <= 0.125) return 0; + if (milliseconds <= 0.250) return 1; + if (milliseconds <= 0.500) return 2; + if (milliseconds <= 1.000) return 3; + if (milliseconds <= 2.000) return 4; + if (milliseconds <= 4.000) return 5; + if (milliseconds <= 8.000) return 6; + if (milliseconds <= 16.000) return 7; + if (milliseconds <= 33.000) return 8; + if (milliseconds <= 50.000) return 9; + if (milliseconds <= 100.000) return 10; + if (milliseconds <= 250.000) return 11; + if (milliseconds <= 500.000) return 12; + if (milliseconds <= 1000.000) return 13; + return 14; + } + + private static double HistogramPercentile(int[] histogram, int count, double percentile) + { + if (count <= 0) + return 0; + var target = Math.Max(1, (int)Math.Ceiling(count * percentile)); + var accumulated = 0; + for (var bucket = 0; bucket < histogram.Length; bucket++) + { + accumulated += histogram[bucket]; + if (accumulated >= target) + return HistogramUpperBoundMilliseconds(bucket); + } + return HistogramUpperBoundMilliseconds(histogram.Length - 1); + } + + private static double HistogramUpperBoundMilliseconds(int bucket) => bucket switch + { + 0 => 0.125, + 1 => 0.250, + 2 => 0.500, + 3 => 1.000, + 4 => 2.000, + 5 => 4.000, + 6 => 8.000, + 7 => 16.000, + 8 => 33.000, + 9 => 50.000, + 10 => 100.000, + 11 => 250.000, + 12 => 500.000, + 13 => 1000.000, + _ => double.PositiveInfinity, + }; + } + + internal readonly struct HighRateInputSnapshot + { + public HighRateInputSnapshot( + string deviceName, + int eventCount, + int pressCount, + int releaseCount, + int outOfOrderCount, + int invalidTransitionCount, + int maximumEventsPerFrame, + double minimumIntervalMilliseconds, + double averageIntervalMilliseconds, + double maximumIntervalMilliseconds, + double p50IntervalMilliseconds, + double p95IntervalMilliseconds, + double p99IntervalMilliseconds, + double averageQueueDelayMilliseconds, + double p50QueueDelayMilliseconds, + double p95QueueDelayMilliseconds, + double p99QueueDelayMilliseconds, + double maximumQueueDelayMilliseconds, + GameplayPhaseTimingSnapshot judgementDispatchLatency, + GameplayPhaseTimingSnapshot reactiveAudioCommandLatency, + GameplayPhaseTimingSnapshot visualFeedbackLatency, + int droppedVisualFeedbackSamples) + { + DeviceName = deviceName; + EventCount = eventCount; + PressCount = pressCount; + ReleaseCount = releaseCount; + OutOfOrderCount = outOfOrderCount; + InvalidTransitionCount = invalidTransitionCount; + MaximumEventsPerFrame = maximumEventsPerFrame; + MinimumIntervalMilliseconds = minimumIntervalMilliseconds; + AverageIntervalMilliseconds = averageIntervalMilliseconds; + MaximumIntervalMilliseconds = maximumIntervalMilliseconds; + P50IntervalMilliseconds = p50IntervalMilliseconds; + P95IntervalMilliseconds = p95IntervalMilliseconds; + P99IntervalMilliseconds = p99IntervalMilliseconds; + AverageQueueDelayMilliseconds = averageQueueDelayMilliseconds; + P50QueueDelayMilliseconds = p50QueueDelayMilliseconds; + P95QueueDelayMilliseconds = p95QueueDelayMilliseconds; + P99QueueDelayMilliseconds = p99QueueDelayMilliseconds; + MaximumQueueDelayMilliseconds = maximumQueueDelayMilliseconds; + JudgementDispatchLatency = judgementDispatchLatency; + ReactiveAudioCommandLatency = reactiveAudioCommandLatency; + VisualFeedbackLatency = visualFeedbackLatency; + DroppedVisualFeedbackSamples = droppedVisualFeedbackSamples; + } + + public string DeviceName { get; } + public int EventCount { get; } + public int PressCount { get; } + public int ReleaseCount { get; } + public int OutOfOrderCount { get; } + public int InvalidTransitionCount { get; } + public int MaximumEventsPerFrame { get; } + public double MinimumIntervalMilliseconds { get; } + public double AverageIntervalMilliseconds { get; } + public double MaximumIntervalMilliseconds { get; } + public double P50IntervalMilliseconds { get; } + public double P95IntervalMilliseconds { get; } + public double P99IntervalMilliseconds { get; } + public double AverageQueueDelayMilliseconds { get; } + public double P50QueueDelayMilliseconds { get; } + public double P95QueueDelayMilliseconds { get; } + public double P99QueueDelayMilliseconds { get; } + public double MaximumQueueDelayMilliseconds { get; } + public GameplayPhaseTimingSnapshot JudgementDispatchLatency { get; } + public GameplayPhaseTimingSnapshot ReactiveAudioCommandLatency { get; } + public GameplayPhaseTimingSnapshot VisualFeedbackLatency { get; } + public int DroppedVisualFeedbackSamples { get; } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/HighRateInputMonitor.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/HighRateInputMonitor.cs.meta new file mode 100644 index 0000000..8152170 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/HighRateInputMonitor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cbc8a2d940617431b8d49e17e06fc5e8 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/LaneKeySoundResolver.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/LaneKeySoundResolver.cs new file mode 100644 index 0000000..f610b46 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/LaneKeySoundResolver.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Charts; + +namespace ProjectQuint.Features.Gameplay +{ + internal readonly struct LaneKeySoundCandidate + { + public LaneKeySoundCandidate(double time, string soundId, bool visibleNote) + { + Time = time; + SoundId = soundId ?? string.Empty; + VisibleNote = visibleNote; + } + + public double Time { get; } + public string SoundId { get; } + public bool VisibleNote { get; } + } + + /// + /// BMS empty-hit semantics: use the latest visible/invisible lane sound at the press time; + /// before any assignment has passed, use the first upcoming visible note sound. + /// + internal static class LaneKeySoundResolver + { + public static List[] Build(PlayableChart chart, int laneCount) + { + if (chart == null) + throw new ArgumentNullException(nameof(chart)); + laneCount = Math.Max(1, Math.Min(8, laneCount)); + var lanes = new List[laneCount]; + for (var lane = 0; lane < laneCount; lane++) + lanes[lane] = new List(); + + foreach (var note in chart.Notes) + { + if (note.Lane < 0 || note.Lane >= laneCount || string.IsNullOrWhiteSpace(note.SoundId)) + continue; + lanes[note.Lane].Add(new LaneKeySoundCandidate( + note.StartTime, + note.SoundId, + visibleNote: true)); + } + foreach (var item in chart.LaneSoundEvents) + { + if (item.Lane < 0 || item.Lane >= laneCount || string.IsNullOrWhiteSpace(item.SoundId)) + continue; + lanes[item.Lane].Add(new LaneKeySoundCandidate( + item.Time, + item.SoundId, + visibleNote: false)); + } + + foreach (var lane in lanes) + { + lane.Sort((left, right) => + { + var time = left.Time.CompareTo(right.Time); + if (time != 0) + return time; + // At the same timestamp, the actual playable note owns the lane sound. + return left.VisibleNote.CompareTo(right.VisibleNote); + }); + } + return lanes; + } + + public static bool TryResolve( + IReadOnlyList candidates, + double songTime, + out string soundId) + { + soundId = string.Empty; + if (candidates == null || candidates.Count == 0) + return false; + + var low = 0; + var high = candidates.Count - 1; + var latest = -1; + while (low <= high) + { + var middle = low + ((high - low) >> 1); + if (candidates[middle].Time <= songTime) + { + latest = middle; + low = middle + 1; + } + else + { + high = middle - 1; + } + } + + if (latest >= 0) + { + soundId = candidates[latest].SoundId; + return !string.IsNullOrWhiteSpace(soundId); + } + + for (var index = 0; index < candidates.Count; index++) + { + if (!candidates[index].VisibleNote) + continue; + soundId = candidates[index].SoundId; + return !string.IsNullOrWhiteSpace(soundId); + } + return false; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/LaneKeySoundResolver.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/LaneKeySoundResolver.cs.meta new file mode 100644 index 0000000..1351c15 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/LaneKeySoundResolver.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 092df6b498aaf4db0af4c3fcb17acf97 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/MacNativeKeyboardPoller.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/MacNativeKeyboardPoller.cs new file mode 100644 index 0000000..671f4b6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/MacNativeKeyboardPoller.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using ProjectQuint.Settings; +using UnityEngine; +using UnityEngine.InputSystem; + +namespace ProjectQuint.Features.Gameplay +{ + internal readonly struct NativePollingSnapshot + { + public NativePollingSnapshot( + int targetHz, + long sampleCount, + long droppedEventCount, + long lateWakeupCount, + double elapsedSeconds) + { + TargetHz = targetHz; + SampleCount = sampleCount; + DroppedEventCount = droppedEventCount; + LateWakeupCount = lateWakeupCount; + ElapsedSeconds = elapsedSeconds; + } + + public int TargetHz { get; } + public long SampleCount { get; } + public long DroppedEventCount { get; } + public long LateWakeupCount { get; } + public double ElapsedSeconds { get; } + public double EffectiveHz => ElapsedSeconds > 0 ? SampleCount / ElapsedSeconds : 0; + } + + internal sealed class MacNativeKeyboardPoller : IDisposable + { + private const string NativeLibrary = "ProjectQuintInput"; + + [StructLayout(LayoutKind.Sequential)] + private struct NativeEvent + { + public int BindingIndex; + public int Pressed; + public double TimestampSeconds; + } + + [StructLayout(LayoutKind.Sequential)] + private struct NativeSnapshot + { + public int TargetHz; + public int BindingCount; + public ulong SampleCount; + public ulong DroppedEventCount; + public ulong LateWakeupCount; + public double ElapsedSeconds; + } + + private readonly struct Binding + { + public Binding(int lane, Key key, int nativeKeyCode) + { + Lane = lane; + Key = key; + NativeKeyCode = nativeKeyCode; + } + + public int Lane { get; } + public Key Key { get; } + public int NativeKeyCode { get; } + } + + private Binding[] _bindings = Array.Empty(); + private bool[] _bindingHeld = Array.Empty(); + private int[] _laneHeldCounts = Array.Empty(); + private double _nativeToRealtimeOffset; + private bool _disposed; + + public bool IsRunning { get; private set; } + + public bool TryStart( + GameSettings settings, + int keyCount, + bool hasScratch, + InputPollingRate rate, + double currentRealtime, + out string error) + { + if (_disposed) + throw new ObjectDisposedException(nameof(MacNativeKeyboardPoller)); + Stop(); + if (settings == null) + { + error = "GAME SETTINGS ARE UNAVAILABLE"; + return false; + } + + var bindings = new List(GameSettings.BindingCount); + var nativeCodes = new List(GameSettings.BindingCount); + var totalLaneCount = keyCount + (hasScratch ? 1 : 0); + for (var lane = 0; lane < totalLaneCount; lane++) + { + foreach (var key in settings.GameplayKeysForLane(keyCount, hasScratch, lane)) + { + if (!MacVirtualKeyCode.TryFromUnityKey(key, out var nativeCode)) + { + error = $"{GameSettings.FormatLaneKeyLabel(key)} IS NOT SUPPORTED BY THE macOS NATIVE INPUT BACKEND"; + return false; + } + if (nativeCodes.Contains(nativeCode)) + { + error = $"{GameSettings.FormatLaneKeyLabel(key)} IS BOUND TO MORE THAN ONE GAMEPLAY LANE"; + return false; + } + nativeCodes.Add(nativeCode); + bindings.Add(new Binding(lane, key, nativeCode)); + } + } + + try + { + if (PQInputStart(GameSettings.InputPollingRateHz(rate), nativeCodes.ToArray(), nativeCodes.Count) == 0) + { + error = Marshal.PtrToStringAnsi(PQInputGetLastError()) ?? "NATIVE INPUT START FAILED"; + return false; + } + + _bindings = bindings.ToArray(); + _bindingHeld = new bool[_bindings.Length]; + _laneHeldCounts = new int[Math.Max(1, totalLaneCount)]; + _nativeToRealtimeOffset = currentRealtime - PQInputNowSeconds(); + RebaseHeldState(); + IsRunning = true; + error = string.Empty; + return true; + } + catch (Exception exception) when ( + exception is DllNotFoundException || + exception is EntryPointNotFoundException || + exception is BadImageFormatException) + { + error = $"NATIVE INPUT PLUGIN UNAVAILABLE // {exception.GetType().Name}"; + return false; + } + } + + public void Drain(Action dispatch, double currentRealtime) + { + if (!IsRunning || dispatch == null) + return; + while (PQInputTryDequeue(out var inputEvent) != 0) + { + if (inputEvent.BindingIndex < 0 || inputEvent.BindingIndex >= _bindings.Length) + continue; + var bindingIndex = inputEvent.BindingIndex; + var pressed = inputEvent.Pressed != 0; + if (_bindingHeld[bindingIndex] == pressed) + continue; + _bindingHeld[bindingIndex] = pressed; + var lane = _bindings[bindingIndex].Lane; + var previousLaneCount = _laneHeldCounts[lane]; + _laneHeldCounts[lane] = Math.Max(0, previousLaneCount + (pressed ? 1 : -1)); + if (pressed && previousLaneCount == 0 || !pressed && _laneHeldCounts[lane] == 0) + { + dispatch( + lane, + pressed, + inputEvent.TimestampSeconds + _nativeToRealtimeOffset, + currentRealtime); + } + } + } + + public void Suspend() + { + if (!IsRunning) + return; + PQInputSetEnabled(0); + } + + public bool[] ResumeAndCaptureHeldLanes() + { + if (!IsRunning) + return null; + RebaseHeldState(); + PQInputSetEnabled(1); + var lanes = new bool[_laneHeldCounts.Length]; + for (var lane = 0; lane < lanes.Length; lane++) + lanes[lane] = _laneHeldCounts[lane] > 0; + return lanes; + } + + public NativePollingSnapshot CaptureSnapshot() + { + if (!IsRunning) + return default; + PQInputGetSnapshot(out var snapshot); + return new NativePollingSnapshot( + snapshot.TargetHz, + checked((long)snapshot.SampleCount), + checked((long)snapshot.DroppedEventCount), + checked((long)snapshot.LateWakeupCount), + snapshot.ElapsedSeconds); + } + + public void Stop() + { + if (IsRunning) + { + PQInputStop(); + IsRunning = false; + } + _bindings = Array.Empty(); + _bindingHeld = Array.Empty(); + _laneHeldCounts = Array.Empty(); + } + + public void Dispose() + { + if (_disposed) + return; + Stop(); + _disposed = true; + } + + private void RebaseHeldState() + { + Array.Clear(_laneHeldCounts, 0, _laneHeldCounts.Length); + for (var index = 0; index < _bindings.Length; index++) + { + var held = PQInputIsKeyDown(_bindings[index].NativeKeyCode) != 0; + _bindingHeld[index] = held; + if (held) + _laneHeldCounts[_bindings[index].Lane]++; + } + } + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern int PQInputStart(int frequency, [In] int[] bindings, int bindingCount); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern void PQInputStop(); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern void PQInputSetEnabled(int enabled); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern int PQInputTryDequeue(out NativeEvent inputEvent); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern int PQInputIsKeyDown(int keyCode); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern double PQInputNowSeconds(); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern void PQInputGetSnapshot(out NativeSnapshot snapshot); + + [DllImport(NativeLibrary, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr PQInputGetLastError(); + } + + internal static class MacVirtualKeyCode + { + public static bool TryFromUnityKey(Key key, out int code) + { + code = key switch + { + Key.A => 0, Key.S => 1, Key.D => 2, Key.F => 3, Key.H => 4, Key.G => 5, + Key.Z => 6, Key.X => 7, Key.C => 8, Key.V => 9, Key.B => 11, + Key.Q => 12, Key.W => 13, Key.E => 14, Key.R => 15, Key.Y => 16, Key.T => 17, + Key.Digit1 => 18, Key.Digit2 => 19, Key.Digit3 => 20, Key.Digit4 => 21, + Key.Digit6 => 22, Key.Digit5 => 23, Key.Equals => 24, Key.Digit9 => 25, + Key.Digit7 => 26, Key.Minus => 27, Key.Digit8 => 28, Key.Digit0 => 29, + Key.RightBracket => 30, Key.O => 31, Key.U => 32, Key.LeftBracket => 33, + Key.I => 34, Key.P => 35, Key.Enter => 36, Key.L => 37, Key.J => 38, + Key.Quote => 39, Key.K => 40, Key.Semicolon => 41, Key.Backslash => 42, + Key.Comma => 43, Key.Slash => 44, Key.N => 45, Key.M => 46, Key.Period => 47, + Key.Tab => 48, Key.Space => 49, Key.Backquote => 50, Key.Backspace => 51, + Key.NumpadEnter => 52, Key.Escape => 53, Key.RightMeta => 54, Key.LeftMeta => 55, + Key.LeftShift => 56, Key.CapsLock => 57, Key.LeftAlt => 58, Key.LeftCtrl => 59, + Key.RightShift => 60, Key.RightAlt => 61, Key.RightCtrl => 62, + Key.NumpadPeriod => 65, Key.NumpadMultiply => 67, Key.NumpadPlus => 69, + Key.NumLock => 71, Key.NumpadDivide => 75, Key.NumpadEquals => 81, + Key.Numpad0 => 82, Key.Numpad1 => 83, Key.Numpad2 => 84, Key.Numpad3 => 85, + Key.Numpad4 => 86, Key.Numpad5 => 87, Key.Numpad6 => 88, Key.Numpad7 => 89, + Key.Numpad8 => 91, Key.Numpad9 => 92, + Key.F5 => 96, Key.F6 => 97, Key.F7 => 98, Key.F3 => 99, Key.F8 => 100, + Key.F9 => 101, Key.F11 => 103, Key.F10 => 109, Key.F12 => 111, + Key.Home => 115, Key.PageUp => 116, Key.Delete => 117, Key.F4 => 118, + Key.End => 119, Key.F2 => 120, Key.PageDown => 121, Key.F1 => 122, + Key.LeftArrow => 123, Key.RightArrow => 124, Key.DownArrow => 125, Key.UpArrow => 126, + _ => -1, + }; + return code >= 0; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/MacNativeKeyboardPoller.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/MacNativeKeyboardPoller.cs.meta new file mode 100644 index 0000000..ba1c93a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Gameplay/MacNativeKeyboardPoller.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f31a27b67569949bd88d9b1ca3ad0a9b \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Library.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Library.meta new file mode 100644 index 0000000..f112240 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Library.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2978d1a1c28364e6c90df3562fb08e38 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryController.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryController.cs new file mode 100644 index 0000000..6245b94 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryController.cs @@ -0,0 +1,572 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Library; +using UnityEngine; +using UnityEngine.InputSystem; + +namespace ProjectQuint.Features.Library +{ + public struct LibraryUiResult + { + public int SelectedIndex; + public string SearchQuery; + public string ToggleLevelFilter; + public bool ToggleLevelFilterPanel; + public bool ClearLevelFilters; + public bool CycleSort; + public bool ToggleSortPanel; + public SongSortMode? SortModeSelection; + public bool ToggleFavorite; + public bool ToggleFavoritesFilter; + public bool Play; + public bool Back; + public bool OpenCourses; + public bool OpenRanking; + public bool OpenOptions; + public bool OpenProfile; + public bool OpenQuickOptions; + public bool SearchFocused; + } + + internal enum LibraryCommand + { + None, + BackToHome, + ReloadLibrary, + OpenCourses, + OpenRanking, + OpenOptions, + OpenProfile, + OpenQuickOptions, + PlayRandomSong, + PlaySelectedSong, + ToggleFavorite, + } + + internal enum LibraryEnterAction + { + None, + ReleaseSearchFocus, + PlaySelectedSong, + } + + internal readonly struct LibraryControllerResult + { + public LibraryControllerResult( + LibraryCommand command, + bool refreshThumbnail = false) + { + Command = command; + RefreshThumbnail = refreshThumbnail; + } + + public LibraryCommand Command { get; } + public bool RefreshThumbnail { get; } + } + + internal sealed class LibraryController + { + internal const int SortShortcutNumber = 7; + internal const int FilterShortcutNumber = 8; + internal const int FavoriteShortcutNumber = 9; + + internal sealed class SelectionKeyRepeat + { + internal const double InitialDelaySeconds = 0.30; + internal const double RepeatIntervalSeconds = 0.055; + private const int MaximumRepeatsPerFrame = 8; + private int _direction; + private double _nextRepeatTime; + + public int Update(int direction, bool pressedThisFrame, double now) + { + direction = Math.Sign(direction); + if (direction == 0) + { + Reset(); + return 0; + } + + if (pressedThisFrame || direction != _direction) + { + _direction = direction; + _nextRepeatTime = now + InitialDelaySeconds; + return direction; + } + + if (now < _nextRepeatTime) + return 0; + + var repeats = 1 + (int)Math.Floor((now - _nextRepeatTime) / RepeatIntervalSeconds); + repeats = Math.Min(repeats, MaximumRepeatsPerFrame); + _nextRepeatTime += repeats * RepeatIntervalSeconds; + return direction * repeats; + } + + public void Reset() + { + _direction = 0; + _nextRepeatTime = 0.0; + } + } + + private bool _focusSearchRequested; + private bool _releaseSearchFocusRequested; + private readonly SelectionKeyRepeat _selectionKeyRepeat = new SelectionKeyRepeat(); + + public LibraryController(LibraryState state = null) + { + State = state ?? new LibraryState(); + } + + public LibraryState State { get; } + + public void Reset() + { + State.Reset(); + _focusSearchRequested = false; + _releaseSearchFocusRequested = false; + _selectionKeyRepeat.Reset(); + } + + public void ReplaceSongs(IEnumerable songs) + { + State.ReplaceSongs(songs); + _focusSearchRequested = false; + _releaseSearchFocusRequested = false; + _selectionKeyRepeat.Reset(); + } + + public void RestoreViewState( + IReadOnlyCollection selectedLevelFilters, + SongSortMode sortMode, + bool favoritesOnly, + string selectedChartMd5) + { + State.RestoreViewState( + selectedLevelFilters, + sortMode, + favoritesOnly, + selectedChartMd5); + _focusSearchRequested = false; + _releaseSearchFocusRequested = true; + _selectionKeyRepeat.Reset(); + } + + public void RefreshCatalogMetadata() => State.RefreshCatalogMetadata(); + + public LibraryControllerResult HandleUi(LibraryUiResult result, bool allowPlay) + { + var refreshThumbnail = false; + State.SetSearchFocused(result.SearchFocused); + refreshThumbnail |= State.SetSearchQuery(result.SearchQuery); + if (result.ToggleLevelFilterPanel) + State.ToggleLevelFilterPanel(); + if (result.ToggleSortPanel) + State.ToggleSortPanel(); + if (result.ClearLevelFilters) + refreshThumbnail |= State.ClearLevelFilters(); + if (!string.IsNullOrWhiteSpace(result.ToggleLevelFilter)) + refreshThumbnail |= State.ToggleLevelFilter(result.ToggleLevelFilter); + if (result.CycleSort) + { + State.CycleSort(); + refreshThumbnail = true; + } + if (result.SortModeSelection.HasValue) + refreshThumbnail |= State.SetSortMode(result.SortModeSelection.Value); + if (result.ToggleFavoritesFilter) + refreshThumbnail |= State.ToggleFavoritesOnly(); + if (result.SelectedIndex >= 0) + refreshThumbnail |= State.SetSelectedIndex(result.SelectedIndex); + + var command = LibraryCommand.None; + if (result.Play && allowPlay && State.VisibleSongs.Count > 0) + command = State.IsRandomSelection + ? LibraryCommand.PlayRandomSong + : LibraryCommand.PlaySelectedSong; + if (result.OpenCourses) + command = LibraryCommand.OpenCourses; + if (result.OpenRanking) + command = LibraryCommand.OpenRanking; + if (result.OpenOptions) + command = LibraryCommand.OpenOptions; + if (result.OpenProfile) + command = LibraryCommand.OpenProfile; + if (result.OpenQuickOptions) + command = LibraryCommand.OpenQuickOptions; + if (result.Back) + command = LibraryCommand.BackToHome; + if (result.ToggleFavorite && State.SelectedSong != null) + command = LibraryCommand.ToggleFavorite; + return new LibraryControllerResult( + command, + refreshThumbnail); + } + + public LibraryControllerResult HandleKeyboard(Keyboard keyboard) + { + if (keyboard == null) + return default; + var numberShortcut = State.SearchFocused + ? -1 + : ReadNumberShortcut(keyboard); + if (State.SortPanelOpen) + { + _selectionKeyRepeat.Reset(); + if (keyboard.escapeKey.wasPressedThisFrame || + numberShortcut == SortShortcutNumber) + { + State.CloseSortPanel(); + return default; + } + + if (keyboard.leftArrowKey.wasPressedThisFrame) + State.MoveSortCursor(-1, 0); + else if (keyboard.rightArrowKey.wasPressedThisFrame) + State.MoveSortCursor(1, 0); + else if (keyboard.upArrowKey.wasPressedThisFrame) + State.MoveSortCursor(0, -1); + else if (keyboard.downArrowKey.wasPressedThisFrame) + State.MoveSortCursor(0, 1); + var applied = + (keyboard.enterKey.wasPressedThisFrame || + keyboard.numpadEnterKey.wasPressedThisFrame) && + State.ApplySortAtCursor(); + return new LibraryControllerResult(LibraryCommand.None, applied); + } + if (State.LevelFilterPanelOpen) + { + _selectionKeyRepeat.Reset(); + if (keyboard.escapeKey.wasPressedThisFrame || + numberShortcut == FilterShortcutNumber) + { + State.CloseLevelFilterPanel(); + return default; + } + + var filterChanged = false; + if (keyboard.leftArrowKey.wasPressedThisFrame) + State.MoveLevelFilterCursor(-1, 0); + else if (keyboard.rightArrowKey.wasPressedThisFrame) + State.MoveLevelFilterCursor(1, 0); + else if (keyboard.upArrowKey.wasPressedThisFrame) + State.MoveLevelFilterCursor(0, -1); + else if (keyboard.downArrowKey.wasPressedThisFrame) + State.MoveLevelFilterCursor(0, 1); + else if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) + filterChanged = State.ToggleLevelFilterAtCursor(); + return new LibraryControllerResult(LibraryCommand.None, filterChanged); + } + if (numberShortcut == SortShortcutNumber) + { + _selectionKeyRepeat.Reset(); + State.ToggleSortPanel(); + return default; + } + if (numberShortcut == FilterShortcutNumber) + { + _selectionKeyRepeat.Reset(); + State.ToggleLevelFilterPanel(); + return default; + } + if ((numberShortcut == 1 || keyboard.f4Key.wasPressedThisFrame) && + RequestSearchFocus()) + return default; + if (keyboard.escapeKey.wasPressedThisFrame) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult(LibraryCommand.BackToHome); + } + if (keyboard.f5Key.wasPressedThisFrame) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult(LibraryCommand.ReloadLibrary); + } + if (!State.SearchFocused && + (ResolveNumberShortcutCommand(numberShortcut) == LibraryCommand.OpenRanking || + keyboard.f8Key.wasPressedThisFrame)) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult(LibraryCommand.OpenRanking); + } + if (!State.SearchFocused && + (numberShortcut == 3 || keyboard.f9Key.wasPressedThisFrame)) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult( + LibraryCommand.None, + State.ToggleFavoritesOnly()); + } + if (!State.SearchFocused && + (ResolveNumberShortcutCommand(numberShortcut) == LibraryCommand.OpenOptions || + keyboard.f10Key.wasPressedThisFrame)) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult(LibraryCommand.OpenOptions); + } + if (!State.SearchFocused && + (ResolveNumberShortcutCommand(numberShortcut) == LibraryCommand.OpenProfile || + keyboard.f11Key.wasPressedThisFrame)) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult(LibraryCommand.OpenProfile); + } + if (!State.SearchFocused && + (ResolveNumberShortcutCommand(numberShortcut) == LibraryCommand.OpenCourses || + keyboard.f12Key.wasPressedThisFrame)) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult(LibraryCommand.OpenCourses); + } + if (!State.SearchFocused && keyboard.spaceKey.wasPressedThisFrame) + { + _selectionKeyRepeat.Reset(); + _releaseSearchFocusRequested = true; + return new LibraryControllerResult(LibraryCommand.OpenQuickOptions); + } + + var enterPressed = keyboard.enterKey.wasPressedThisFrame || + keyboard.numpadEnterKey.wasPressedThisFrame; + var enterAction = ResolveEnterAction(State.SearchFocused, State.VisibleSongs.Count > 0, enterPressed); + if (enterAction == LibraryEnterAction.ReleaseSearchFocus) + { + _selectionKeyRepeat.Reset(); + _focusSearchRequested = false; + State.SetSearchFocused(false); + _releaseSearchFocusRequested = true; + return default; + } + if (State.SearchFocused) + { + _selectionKeyRepeat.Reset(); + return default; + } + + var shiftLevelDelta = ResolveLevelFilterDelta( + keyboard.leftShiftKey.wasPressedThisFrame, + keyboard.rightShiftKey.wasPressedThisFrame); + if (shiftLevelDelta != 0) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult( + LibraryCommand.None, + OverrideLevelFilterByShift(shiftLevelDelta)); + } + + if (State.VisibleSongs.Count == 0) + { + _selectionKeyRepeat.Reset(); + return default; + } + + if (numberShortcut == FavoriteShortcutNumber) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult(LibraryCommand.ToggleFavorite); + } + + var changed = false; + var verticalDirection = ResolveVerticalSelectionDirection( + keyboard.upArrowKey.isPressed, + keyboard.downArrowKey.isPressed, + keyboard.upArrowKey.wasPressedThisFrame, + keyboard.downArrowKey.wasPressedThisFrame); + var repeatedSelectionDelta = _selectionKeyRepeat.Update( + verticalDirection, + keyboard.upArrowKey.wasPressedThisFrame || keyboard.downArrowKey.wasPressedThisFrame, + Time.realtimeSinceStartupAsDouble); + if (repeatedSelectionDelta != 0) + changed |= State.MoveSelection(repeatedSelectionDelta); + if (keyboard.pageDownKey.wasPressedThisFrame) + changed |= State.MoveSelection(10); + if (keyboard.pageUpKey.wasPressedThisFrame) + changed |= State.MoveSelection(-10); + if (enterAction == LibraryEnterAction.PlaySelectedSong) + { + _selectionKeyRepeat.Reset(); + return new LibraryControllerResult( + State.IsRandomSelection + ? LibraryCommand.PlayRandomSong + : LibraryCommand.PlaySelectedSong, + changed); + } + return new LibraryControllerResult(LibraryCommand.None, changed); + } + + internal static LibraryCommand ResolveNumberShortcutCommand(int number) => + number switch + { + 2 => LibraryCommand.OpenRanking, + 4 => LibraryCommand.OpenOptions, + 5 => LibraryCommand.OpenProfile, + 6 => LibraryCommand.OpenCourses, + _ => LibraryCommand.None, + }; + + private static int ReadNumberShortcut(Keyboard keyboard) + { + if (keyboard.digit1Key.wasPressedThisFrame || keyboard.numpad1Key.wasPressedThisFrame) + return 1; + if (keyboard.digit2Key.wasPressedThisFrame || keyboard.numpad2Key.wasPressedThisFrame) + return 2; + if (keyboard.digit3Key.wasPressedThisFrame || keyboard.numpad3Key.wasPressedThisFrame) + return 3; + if (keyboard.digit4Key.wasPressedThisFrame || keyboard.numpad4Key.wasPressedThisFrame) + return 4; + if (keyboard.digit5Key.wasPressedThisFrame || keyboard.numpad5Key.wasPressedThisFrame) + return 5; + if (keyboard.digit6Key.wasPressedThisFrame || keyboard.numpad6Key.wasPressedThisFrame) + return 6; + if (keyboard.digit7Key.wasPressedThisFrame || keyboard.numpad7Key.wasPressedThisFrame) + return 7; + if (keyboard.digit8Key.wasPressedThisFrame || keyboard.numpad8Key.wasPressedThisFrame) + return 8; + if (keyboard.digit9Key.wasPressedThisFrame || keyboard.numpad9Key.wasPressedThisFrame) + return 9; + if (keyboard.digit0Key.wasPressedThisFrame || keyboard.numpad0Key.wasPressedThisFrame) + return 0; + return -1; + } + + internal static int ResolveVerticalSelectionDirection( + bool upHeld, + bool downHeld, + bool upPressedThisFrame, + bool downPressedThisFrame) + { + if (upPressedThisFrame != downPressedThisFrame) + return upPressedThisFrame ? -1 : 1; + if (upHeld == downHeld) + return 0; + return upHeld ? -1 : 1; + } + + public bool ConsumeReleaseSearchFocusRequest() + { + if (!_releaseSearchFocusRequested) + return false; + _releaseSearchFocusRequested = false; + return true; + } + + public bool ConsumeFocusSearchRequest() + { + if (!_focusSearchRequested) + return false; + _focusSearchRequested = false; + return true; + } + + internal bool RequestSearchFocus() + { + if (State.SearchFocused || State.LevelFilterPanelOpen || State.SortPanelOpen) + return false; + + _selectionKeyRepeat.Reset(); + _releaseSearchFocusRequested = false; + _focusSearchRequested = true; + State.SetSearchFocused(true); + return true; + } + + public void ClearSearchFocus() + { + _focusSearchRequested = false; + State.SetSearchFocused(false); + _releaseSearchFocusRequested = true; + } + + public bool OverrideLevelFilterByShift(int delta) + { + var nextLevel = ResolveShiftLevelFilter( + State.LevelFilters, + State.SelectedLevelFilters, + State.SelectedSong?.DifficultyLabel, + delta); + return State.OverrideLevelFilter(nextLevel); + } + + internal static LibraryEnterAction ResolveEnterAction( + bool searchFocused, + bool hasSongs, + bool enterPressed) + { + if (!enterPressed) + return LibraryEnterAction.None; + if (searchFocused) + return LibraryEnterAction.ReleaseSearchFocus; + return hasSongs ? LibraryEnterAction.PlaySelectedSong : LibraryEnterAction.None; + } + + internal static int ResolveLevelFilterDelta(bool leftShiftPressed, bool rightShiftPressed) + { + if (leftShiftPressed == rightShiftPressed) + return 0; + return leftShiftPressed ? -1 : 1; + } + + internal static string ResolveShiftLevelFilter( + IReadOnlyList levelFilters, + IReadOnlyCollection selectedLevelFilters, + string currentSongLevel, + int delta) + { + if (levelFilters == null || levelFilters.Count == 0 || delta == 0) + return string.Empty; + + var rankedLevels = new List(); + foreach (var level in levelFilters) + { + if (string.IsNullOrWhiteSpace(level) || + string.Equals(level, SongLibraryView.AllLevelsFilter, StringComparison.OrdinalIgnoreCase) || + string.Equals(level, SongLibraryView.UnrankedFilter, StringComparison.OrdinalIgnoreCase)) + continue; + rankedLevels.Add(level); + } + if (rankedLevels.Count == 0) + return string.Empty; + + string anchor = null; + if (selectedLevelFilters?.Count == 1) + foreach (var selected in selectedLevelFilters) + anchor = selected; + if (IndexOfLevel(rankedLevels, anchor) < 0) + anchor = currentSongLevel; + if (IndexOfLevel(rankedLevels, anchor) < 0 && selectedLevelFilters?.Count > 0) + { + var edgeIndex = delta > 0 ? int.MaxValue : int.MinValue; + foreach (var selected in selectedLevelFilters) + { + var selectedIndex = IndexOfLevel(rankedLevels, selected); + if (selectedIndex < 0) + continue; + edgeIndex = delta > 0 + ? Math.Min(edgeIndex, selectedIndex) + : Math.Max(edgeIndex, selectedIndex); + } + if (edgeIndex >= 0 && edgeIndex < rankedLevels.Count) + anchor = rankedLevels[edgeIndex]; + } + + var currentIndex = IndexOfLevel(rankedLevels, anchor); + if (currentIndex < 0) + return delta > 0 ? rankedLevels[0] : rankedLevels[rankedLevels.Count - 1]; + + var nextIndex = (currentIndex + (delta > 0 ? 1 : -1)) % rankedLevels.Count; + if (nextIndex < 0) + nextIndex += rankedLevels.Count; + return rankedLevels[nextIndex]; + } + + private static int IndexOfLevel(IReadOnlyList levels, string target) + { + if (string.IsNullOrWhiteSpace(target)) + return -1; + for (var index = 0; index < levels.Count; index++) + if (string.Equals(levels[index], target, StringComparison.OrdinalIgnoreCase)) + return index; + return -1; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryController.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryController.cs.meta new file mode 100644 index 0000000..f07f074 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 004cb9457f87d4d1f8954eb71e3cc3e2 diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryState.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryState.cs new file mode 100644 index 0000000..b9b314a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryState.cs @@ -0,0 +1,395 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProjectQuint.Library; +using UnityEngine; + +namespace ProjectQuint.Features.Library +{ + internal sealed class LibraryState + { + private readonly List _allSongs = new List(); + private readonly List _visibleSongs = new List(); + private readonly HashSet _selectedLevelFilters = + new HashSet(StringComparer.OrdinalIgnoreCase); + private List _levelFilters = new List { SongLibraryView.AllLevelsFilter }; + + public IReadOnlyList AllSongs => _allSongs; + public IReadOnlyList VisibleSongs => _visibleSongs; + public IReadOnlyList LevelFilters => _levelFilters; + public IReadOnlyCollection SelectedLevelFilters => _selectedLevelFilters; + public int SongsRevision { get; private set; } + public int ViewRevision { get; private set; } + public int DisplayItemCount => _visibleSongs.Count == 0 ? 0 : _visibleSongs.Count + 1; + public int SelectedIndex { get; private set; } + public bool LevelFilterPanelOpen { get; private set; } + public int LevelFilterCursorIndex { get; private set; } + public bool SortPanelOpen { get; private set; } + public int SortCursorIndex { get; private set; } + public SongSortMode SortMode { get; private set; } = SongSortMode.DifficultyAscending; + public string SearchQuery { get; private set; } = string.Empty; + public bool SearchFocused { get; private set; } + public bool FavoritesOnly { get; private set; } + public bool IsRandomSelection => _visibleSongs.Count > 0 && SelectedIndex == 0; + public SongLibraryEntry SelectedSong => SelectedIndex <= 0 || _visibleSongs.Count == 0 + ? null + : _visibleSongs[Mathf.Clamp(SelectedIndex - 1, 0, _visibleSongs.Count - 1)]; + + public void Reset() + { + _allSongs.Clear(); + _visibleSongs.Clear(); + _selectedLevelFilters.Clear(); + _levelFilters = new List { SongLibraryView.AllLevelsFilter }; + SelectedIndex = 0; + LevelFilterPanelOpen = false; + LevelFilterCursorIndex = 0; + SortPanelOpen = false; + SortCursorIndex = 0; + SortMode = SongSortMode.DifficultyAscending; + SearchQuery = string.Empty; + SearchFocused = false; + FavoritesOnly = false; + unchecked + { + SongsRevision++; + ViewRevision++; + } + } + + public void ReplaceSongs(IEnumerable songs) + { + _allSongs.Clear(); + if (songs != null) + _allSongs.AddRange(songs); + _levelFilters = SongLibraryView.BuildLevelFilters(_allSongs); + _selectedLevelFilters.Clear(); + LevelFilterPanelOpen = false; + LevelFilterCursorIndex = 0; + SortPanelOpen = false; + SortCursorIndex = 0; + SortMode = SongSortMode.DifficultyAscending; + SearchQuery = string.Empty; + FavoritesOnly = false; + SelectedIndex = 0; + unchecked + { + SongsRevision++; + } + ApplyView(); + } + + public void RestoreViewState( + IReadOnlyCollection selectedLevelFilters, + SongSortMode sortMode, + bool favoritesOnly, + string selectedChartMd5) + { + _selectedLevelFilters.Clear(); + foreach (var level in selectedLevelFilters ?? Array.Empty()) + { + if (string.IsNullOrWhiteSpace(level) || + string.Equals( + level, + SongLibraryView.AllLevelsFilter, + StringComparison.OrdinalIgnoreCase) || + !_levelFilters.Contains(level, StringComparer.OrdinalIgnoreCase)) + continue; + _selectedLevelFilters.Add(level); + } + SortMode = Enum.IsDefined(typeof(SongSortMode), sortMode) + ? sortMode + : SongSortMode.DifficultyAscending; + FavoritesOnly = favoritesOnly; + SearchQuery = string.Empty; + SearchFocused = false; + LevelFilterPanelOpen = false; + SortPanelOpen = false; + SortCursorIndex = (int)SortMode; + LevelFilterCursorIndex = ResolveInitialLevelFilterCursor( + _levelFilters, + _selectedLevelFilters); + SelectedIndex = 0; + ApplyView(); + + var normalizedMd5 = (selectedChartMd5 ?? string.Empty).Trim(); + if (normalizedMd5.Length == 0) + return; + var selectedIndex = _visibleSongs.FindIndex(song => + string.Equals( + song?.Metadata?.Md5, + normalizedMd5, + StringComparison.OrdinalIgnoreCase)); + if (selectedIndex >= 0) + SelectedIndex = selectedIndex + 1; + } + + public void RefreshCatalogMetadata() + { + var selectedPath = SelectedSong?.ChartPath; + _levelFilters = SongLibraryView.BuildLevelFilters(_allSongs); + _selectedLevelFilters.RemoveWhere(level => + !_levelFilters.Contains(level, StringComparer.OrdinalIgnoreCase)); + LevelFilterCursorIndex = Mathf.Clamp( + LevelFilterCursorIndex, + 0, + Math.Max(0, _levelFilters.Count - 1)); + ApplyView(); + if (string.IsNullOrWhiteSpace(selectedPath)) + return; + + var selected = _visibleSongs.FindIndex(song => + string.Equals(song.ChartPath, selectedPath, StringComparison.OrdinalIgnoreCase)); + if (selected >= 0) + SelectedIndex = selected + 1; + } + + public bool SetSearchQuery(string query) + { + query ??= string.Empty; + if (string.Equals(SearchQuery, query, StringComparison.Ordinal)) + return false; + SearchQuery = query; + ApplyView(); + return true; + } + + public void SetSearchFocused(bool focused) => SearchFocused = focused; + + public void ToggleLevelFilterPanel() + { + LevelFilterPanelOpen = !LevelFilterPanelOpen; + if (!LevelFilterPanelOpen) + return; + + SortPanelOpen = false; + LevelFilterCursorIndex = ResolveInitialLevelFilterCursor( + _levelFilters, + _selectedLevelFilters); + } + + public void CloseLevelFilterPanel() => LevelFilterPanelOpen = false; + + public void ToggleSortPanel() + { + SortPanelOpen = !SortPanelOpen; + if (!SortPanelOpen) + return; + + LevelFilterPanelOpen = false; + SortCursorIndex = (int)SortMode; + } + + public void CloseSortPanel() => SortPanelOpen = false; + + public bool MoveSortCursor(int horizontalDelta, int verticalDelta) + { + var optionCount = Enum.GetValues(typeof(SongSortMode)).Length; + if (optionCount <= 0 || horizontalDelta == 0 && verticalDelta == 0) + return false; + var next = ResolveLevelFilterCursor( + SortCursorIndex, + optionCount, + 2, + horizontalDelta, + verticalDelta); + if (next == SortCursorIndex) + return false; + SortCursorIndex = next; + return true; + } + + public bool ApplySortAtCursor() => + SetSortMode((SongSortMode)Mathf.Clamp( + SortCursorIndex, + 0, + Enum.GetValues(typeof(SongSortMode)).Length - 1)); + + public bool SetSortMode(SongSortMode sortMode) + { + SortPanelOpen = false; + if (!Enum.IsDefined(typeof(SongSortMode), sortMode) || SortMode == sortMode) + return false; + + var selectedPath = SelectedSong?.ChartPath; + SortMode = sortMode; + SortCursorIndex = (int)sortMode; + ApplyView(); + if (string.IsNullOrWhiteSpace(selectedPath)) + return true; + var selected = _visibleSongs.FindIndex(song => + string.Equals(song.ChartPath, selectedPath, StringComparison.OrdinalIgnoreCase)); + if (selected >= 0) + SelectedIndex = selected + 1; + return true; + } + + public bool MoveLevelFilterCursor(int horizontalDelta, int verticalDelta, int columns = 5) + { + var next = ResolveLevelFilterCursor( + LevelFilterCursorIndex, + _levelFilters.Count, + columns, + horizontalDelta, + verticalDelta); + if (next == LevelFilterCursorIndex) + return false; + LevelFilterCursorIndex = next; + return true; + } + + public bool ToggleLevelFilterAtCursor() + { + if (_levelFilters.Count == 0) + return false; + + LevelFilterCursorIndex = Mathf.Clamp(LevelFilterCursorIndex, 0, _levelFilters.Count - 1); + var level = _levelFilters[LevelFilterCursorIndex]; + return string.Equals(level, SongLibraryView.AllLevelsFilter, StringComparison.OrdinalIgnoreCase) + ? ClearLevelFilters() + : ToggleLevelFilter(level); + } + + internal static int ResolveInitialLevelFilterCursor( + IReadOnlyList filters, + IReadOnlyCollection selectedLevels) + { + if (filters == null || filters.Count == 0 || selectedLevels == null || selectedLevels.Count == 0) + return 0; + + for (var index = 0; index < filters.Count; index++) + foreach (var selected in selectedLevels) + if (string.Equals(filters[index], selected, StringComparison.OrdinalIgnoreCase)) + return index; + return 0; + } + + internal static int ResolveLevelFilterCursor( + int current, + int optionCount, + int columns, + int horizontalDelta, + int verticalDelta) + { + if (optionCount <= 0) + return 0; + + columns = Math.Max(1, columns); + current = Mathf.Clamp(current, 0, optionCount - 1); + if (horizontalDelta != 0) + return (current + Math.Sign(horizontalDelta) + optionCount) % optionCount; + if (verticalDelta == 0) + return current; + + var column = current % columns; + var rows = (optionCount + columns - 1) / columns; + var row = current / columns; + for (var attempt = 0; attempt < rows; attempt++) + { + row = (row + Math.Sign(verticalDelta) + rows) % rows; + var candidate = row * columns + column; + if (candidate < optionCount) + return candidate; + } + return current; + } + + public bool ClearLevelFilters() + { + if (_selectedLevelFilters.Count == 0) + return false; + _selectedLevelFilters.Clear(); + ApplyView(); + return true; + } + + public bool ToggleLevelFilter(string level) + { + if (string.IsNullOrWhiteSpace(level)) + return false; + if (!_selectedLevelFilters.Add(level)) + _selectedLevelFilters.Remove(level); + ApplyView(); + return true; + } + + public void CycleSort() + { + SetSortMode( + (SongSortMode)(((int)SortMode + 1) % + Enum.GetValues(typeof(SongSortMode)).Length)); + } + + public bool ToggleFavoritesOnly() + { + FavoritesOnly = !FavoritesOnly; + SelectedIndex = 0; + ApplyView(); + return true; + } + + public void RefreshFavoriteStatus() + { + var selectedPath = SelectedSong?.ChartPath; + ApplyView(); + if (string.IsNullOrWhiteSpace(selectedPath)) + return; + + var selected = _visibleSongs.FindIndex(song => + string.Equals(song.ChartPath, selectedPath, StringComparison.OrdinalIgnoreCase)); + if (selected >= 0) + SelectedIndex = selected + 1; + } + + public bool SetSelectedIndex(int index) + { + var clamped = Mathf.Clamp(index, 0, Math.Max(0, DisplayItemCount - 1)); + if (SelectedIndex == clamped) + return false; + SelectedIndex = clamped; + return true; + } + + public bool MoveSelection(int delta) + { + if (DisplayItemCount == 0 || delta == 0) + return false; + + var next = ((SelectedIndex + delta) % DisplayItemCount + DisplayItemCount) % DisplayItemCount; + if (SelectedIndex == next) + return false; + SelectedIndex = next; + return true; + } + + public bool OverrideLevelFilter(string level) + { + if (string.IsNullOrWhiteSpace(level)) + return false; + _selectedLevelFilters.Clear(); + _selectedLevelFilters.Add(level); + LevelFilterPanelOpen = false; + SortPanelOpen = false; + LevelFilterCursorIndex = 0; + SelectedIndex = 0; + ApplyView(); + return true; + } + + private void ApplyView() + { + _visibleSongs.Clear(); + _visibleSongs.AddRange(SongLibraryView.Apply( + _allSongs, + SearchQuery, + _selectedLevelFilters, + SortMode, + FavoritesOnly)); + SelectedIndex = Mathf.Clamp(SelectedIndex, 0, Math.Max(0, DisplayItemCount - 1)); + unchecked + { + ViewRevision++; + } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryState.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryState.cs.meta new file mode 100644 index 0000000..b725569 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Library/LibraryState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9f82783c7cd2e4806b35fe94350ddd7c diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Loading.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Loading.meta new file mode 100644 index 0000000..c9b6407 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Loading.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0a46f58b2b49c4db581345f5086a0b9e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Loading/PreparedSong.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Loading/PreparedSong.cs new file mode 100644 index 0000000..9b91214 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Loading/PreparedSong.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Audio; +using ProjectQuint.Core.Charts; +using ProjectQuint.Library; +using ProjectQuint.Video; +using UnityEngine; + +namespace ProjectQuint.Features.Loading +{ + internal enum PreparedSongState + { + Prepared, + Committed, + Disposed, + } + + /// + /// Owns every Unity and managed resource needed by one playable song. A candidate may be + /// committed exactly once; disposing an uncommitted candidate is the loading rollback path. + /// + internal sealed class PreparedSong : IDisposable + { + private GameObject _resourceRoot; + + public PreparedSong( + SongLibraryEntry entry, + PlayableChart chart, + IReadOnlyDictionary notesById, + int[] laneMap, + ISongAudioBackend audio, + BmsBgaSystem bga, + GameObject resourceRoot) + { + Entry = entry ?? throw new ArgumentNullException(nameof(entry)); + Chart = chart ?? throw new ArgumentNullException(nameof(chart)); + NotesById = notesById ?? throw new ArgumentNullException(nameof(notesById)); + LaneMap = laneMap ?? throw new ArgumentNullException(nameof(laneMap)); + Audio = audio ?? throw new ArgumentNullException(nameof(audio)); + Bga = bga ?? throw new ArgumentNullException(nameof(bga)); + _resourceRoot = resourceRoot ?? throw new ArgumentNullException(nameof(resourceRoot)); + } + + public SongLibraryEntry Entry { get; } + public PlayableChart Chart { get; } + public IReadOnlyDictionary NotesById { get; } + public int[] LaneMap { get; } + public ISongAudioBackend Audio { get; private set; } + public BmsBgaSystem Bga { get; private set; } + public Texture2D StageTexture { get; private set; } + public PreparedSongState State { get; private set; } = PreparedSongState.Prepared; + public bool IsDisposed => State == PreparedSongState.Disposed; + + public void AttachStageTexture(Texture2D texture) + { + if (State != PreparedSongState.Prepared) + throw new InvalidOperationException("Stage texture can only be attached while a song is being prepared."); + if (StageTexture != null && StageTexture != texture) + DestroyUnityObject(StageTexture); + StageTexture = texture; + } + + public bool TryCommit() + { + if (State != PreparedSongState.Prepared) + return false; + + State = PreparedSongState.Committed; + if (_resourceRoot != null) + _resourceRoot.name = $"Active Song Resources - {Chart.Metadata.Title}"; + return true; + } + + public void Dispose() + { + if (State == PreparedSongState.Disposed) + return; + + State = PreparedSongState.Disposed; + var audio = Audio; + var bga = Bga; + var stageTexture = StageTexture; + var resourceRoot = _resourceRoot; + Audio = null; + Bga = null; + StageTexture = null; + _resourceRoot = null; + + try + { + audio?.Dispose(); + } + finally + { + try + { + bga?.Dispose(); + } + finally + { + DestroyUnityObject(stageTexture); + DestroyUnityObject(resourceRoot); + } + } + } + + internal GameObject ResourceRoot => _resourceRoot; + + private static void DestroyUnityObject(UnityEngine.Object target) + { + if (target == null) + return; + if (Application.isPlaying) + UnityEngine.Object.Destroy(target); + else + UnityEngine.Object.DestroyImmediate(target); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Loading/PreparedSong.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Loading/PreparedSong.cs.meta new file mode 100644 index 0000000..0f29b3c --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Loading/PreparedSong.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 47793599139954525923d3f67ba25a6c \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Loading/SongLoadCoordinator.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Loading/SongLoadCoordinator.cs new file mode 100644 index 0000000..84290da --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Loading/SongLoadCoordinator.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using ProjectQuint.Audio; +using ProjectQuint.Core.Bms; +using ProjectQuint.Core.Charts; +using ProjectQuint.Infrastructure.Images; +using ProjectQuint.Library; +using ProjectQuint.Settings; +using ProjectQuint.Video; +using UnityEngine; + +namespace ProjectQuint.Features.Loading +{ + internal readonly struct SongLoadProgress + { + public SongLoadProgress(string detail, int current = 0, int total = 0) + { + Detail = detail ?? string.Empty; + Current = current; + Total = total; + } + + public string Detail { get; } + public int Current { get; } + public int Total { get; } + } + + internal sealed class SongLoadRequest + { + public SongLibraryEntry Entry { get; set; } + public LaneModifier LaneModifier { get; set; } + public int LaneRandomSeed { get; set; } + public bool ReactiveKeySoundsEnabled { get; set; } + public BgaPlaybackMode BgaPlaybackMode { get; set; } = BgaPlaybackMode.Full; + public bool BgaEnabled => BgaPlaybackMode == ProjectQuint.Settings.BgaPlaybackMode.Full; + public bool StageImageEnabled => BgaPlaybackMode != ProjectQuint.Settings.BgaPlaybackMode.Black; + public Func CancellationRequested { get; set; } + } + + internal sealed class SongLoadResult : IDisposable + { + private SongLoadResult() { } + + public bool IsSuccess { get; private set; } + public bool IsCancelled { get; private set; } + public string ErrorTitle { get; private set; } = string.Empty; + public string ErrorDetail { get; private set; } = string.Empty; + public PreparedSong PreparedSong { get; private set; } + public SongLibraryEntry Entry => PreparedSong?.Entry; + public PlayableChart Chart => PreparedSong?.Chart; + public Texture2D StageTexture => PreparedSong?.StageTexture; + public IReadOnlyDictionary NotesById => PreparedSong?.NotesById; + public int[] LaneMap => PreparedSong?.LaneMap ?? Array.Empty(); + + public static SongLoadResult Success(PreparedSong preparedSong) => + new SongLoadResult + { + IsSuccess = true, + PreparedSong = preparedSong ?? throw new ArgumentNullException(nameof(preparedSong)), + }; + + public static SongLoadResult Cancelled() => new SongLoadResult { IsCancelled = true }; + + public static SongLoadResult Failed(string title, string detail) => new SongLoadResult + { + ErrorTitle = title ?? "SONG LOAD FAILED", + ErrorDetail = detail ?? string.Empty, + }; + + public PreparedSong TakePreparedSong() + { + var preparedSong = PreparedSong; + PreparedSong = null; + return preparedSong; + } + + public void Dispose() + { + PreparedSong?.Dispose(); + PreparedSong = null; + } + } + + internal sealed class SongLoadCoordinator + { + private readonly GameObject _owner; + private readonly ISongAudioBackendFactory _audioBackendFactory; + + public SongLoadCoordinator( + GameObject owner, + ISongAudioBackendFactory audioBackendFactory = null) + { + _owner = owner ?? throw new ArgumentNullException(nameof(owner)); + _audioBackendFactory = audioBackendFactory ?? new UnitySongAudioBackendFactory(); + } + + public IEnumerator Load( + SongLoadRequest request, + Action progress, + Action completed) + { + if (request?.Entry == null) + { + completed?.Invoke(SongLoadResult.Failed("SONG LOAD FAILED", "No chart was selected.")); + yield break; + } + + progress?.Invoke(new SongLoadProgress("Parsing BMS timeline")); + var parseTask = Task.Run(() => ParseAndTransform( + request.Entry, + request.LaneModifier, + request.LaneRandomSeed)); + while (!parseTask.IsCompleted) + { + if (IsCancelled(request)) + { + ObserveFaultAfterCancellation(parseTask); + completed?.Invoke(SongLoadResult.Cancelled()); + yield break; + } + yield return null; + } + if (IsCancelled(request)) + { + completed?.Invoke(SongLoadResult.Cancelled()); + yield break; + } + if (parseTask.IsFaulted) + { + completed?.Invoke(SongLoadResult.Failed( + "CHART PARSE FAILED", + parseTask.Exception?.GetBaseException().Message ?? request.Entry.ChartPath)); + yield break; + } + + var prepared = parseTask.Result; + var chart = prepared.Chart; + GameObject resourceRoot = null; + PreparedSong candidate = null; + try + { + resourceRoot = new GameObject($"Prepared Song Resources - {chart.Metadata.Title}"); + resourceRoot.transform.SetParent(_owner.transform, false); + candidate = new PreparedSong( + request.Entry, + chart, + prepared.NotesById, + prepared.LaneMap, + _audioBackendFactory.Create(resourceRoot), + new BmsBgaSystem(resourceRoot), + resourceRoot); + resourceRoot = null; + + progress?.Invoke(new SongLoadProgress( + $"Loading referenced keysounds // {chart.Notes.Count:N0} notes")); + yield return candidate.Audio.Load( + chart, + (current, total, detail) => + { + if (IsCancelled(request)) + return; + progress?.Invoke(new SongLoadProgress( + string.IsNullOrEmpty(detail) ? "Keysounds loaded" : $"Loading keysound {detail}", + current, + total)); + }, + () => IsCancelled(request), + request.ReactiveKeySoundsEnabled); + if (IsCancelled(request)) + { + completed?.Invoke(SongLoadResult.Cancelled()); + yield break; + } + + if (!string.IsNullOrWhiteSpace(candidate.Audio.LoadError)) + { + completed?.Invoke(SongLoadResult.Failed( + "AUDIO LOAD FAILED", + candidate.Audio.LoadError)); + yield break; + } + + if (!request.ReactiveKeySoundsEnabled) + { + progress?.Invoke(new SongLoadProgress("Preparing single-track smooth playback")); + yield return candidate.Audio.PrepareSmoothPlaybackMix( + (current, total, detail) => + { + if (!IsCancelled(request)) + progress?.Invoke(new SongLoadProgress(detail, current, total)); + }, + () => IsCancelled(request)); + if (IsCancelled(request)) + { + completed?.Invoke(SongLoadResult.Cancelled()); + yield break; + } + if (!candidate.Audio.SmoothMixReady) + { + completed?.Invoke(SongLoadResult.Failed( + "AUDIO MIX FAILED", + string.IsNullOrWhiteSpace(candidate.Audio.SmoothMixError) + ? "The single-track smooth playback mix could not be prepared." + : candidate.Audio.SmoothMixError)); + yield break; + } + } + else + { + progress?.Invoke(new SongLoadProgress("Preparing reactive keysound source pool")); + string audioPrepareError = null; + try + { + candidate.Audio.PrepareReactivePlayback(); + } + catch (Exception exception) + { + audioPrepareError = exception.Message; + } + if (!string.IsNullOrWhiteSpace(audioPrepareError)) + { + completed?.Invoke(SongLoadResult.Failed("AUDIO PREPARE FAILED", audioPrepareError)); + yield break; + } + } + + if (candidate.Audio.MissingClipCount > 0 || + candidate.Audio.DecodeFailedClipCount > 0 || + candidate.Audio.SilentPlaceholderCount > 0) + { + Debug.LogWarning( + $"[ProjectQuint Audio] Keysound summary for {request.Entry.ChartPath}: " + + $"{candidate.Audio.MissingClipCount} missing, " + + $"{candidate.Audio.DecodeFailedClipCount} decode failed, " + + $"{candidate.Audio.SilentPlaceholderCount} intentional silent placeholders."); + } + + if (request.BgaEnabled) + { + progress?.Invoke(new SongLoadProgress("Preparing BGA timeline")); + yield return candidate.Bga.Load( + chart, + detail => + { + if (!IsCancelled(request) && !string.IsNullOrEmpty(detail)) + progress?.Invoke(new SongLoadProgress(detail)); + }, + () => IsCancelled(request)); + } + else + { + candidate.Bga.Clear(); + progress?.Invoke(new SongLoadProgress("BGA video disabled")); + } + if (IsCancelled(request)) + { + completed?.Invoke(SongLoadResult.Cancelled()); + yield break; + } + + if (request.StageImageEnabled) + { + Texture2D stageTexture = null; + yield return LoadStageTexture( + chart, + texture => stageTexture = texture, + request.CancellationRequested); + candidate.AttachStageTexture(stageTexture); + } + else + { + progress?.Invoke(new SongLoadProgress("Background image disabled")); + } + if (IsCancelled(request)) + { + completed?.Invoke(SongLoadResult.Cancelled()); + yield break; + } + + var result = SongLoadResult.Success(candidate); + completed?.Invoke(result); + if (completed != null) + candidate = null; + } + finally + { + // Any early exit, thrown exception or stopped coroutine rolls back all + // clips, textures, video players and AudioSources created by this load. + candidate?.Dispose(); + DestroyResourceRoot(resourceRoot); + } + } + + internal static PreparedChart ParseAndTransform( + SongLibraryEntry entry, + LaneModifier laneModifier, + int randomSeed) + { + if (entry == null) + throw new ArgumentNullException(nameof(entry)); + + var document = BmsParser.ParseFile( + entry.ChartPath, + forcedKeyCount: entry.EffectiveKeyCount); + var chart = BmsTimelineCompiler.Compile(document); + chart.Metadata.Md5 = entry.Metadata.Md5; + chart.Metadata.Sha256 = entry.Metadata.Sha256; + if (!string.IsNullOrWhiteSpace(entry.DisplayTitle)) + chart.Metadata.Title = entry.DisplayTitle; + if (!string.IsNullOrWhiteSpace(entry.DisplayArtist)) + chart.Metadata.Artist = entry.DisplayArtist; + + var conversion = BmsPlayableChartConverter.Convert(chart, laneModifier, randomSeed); + var playableChart = conversion.Chart; + playableChart.Metadata.DifficultyLabel = entry.DifficultyLabel; + var notesById = new Dictionary(playableChart.Notes.Count); + foreach (var note in playableChart.Notes) + notesById[note.Id] = note; + return new PreparedChart(playableChart, notesById, conversion.LaneMap); + } + + internal static bool ShouldCancel(int loadGeneration, int currentGeneration, bool isLoadingMode) => + loadGeneration != currentGeneration || !isLoadingMode; + + public string BuildDiagnostics(PreparedSong song, SongLoadRequest request, string gaugeLabel) + { + if (song?.Chart == null || request == null) + return string.Empty; + var chart = song.Chart; + return $"[ProjectQuint] Playing {chart.Metadata.Title}: {chart.Notes.Count} notes, " + + $"{chart.BackgroundAudioEvents.Count} BGM events, " + + $"{ChartKeyMode.FormatLabel(chart.Metadata.KeyCount, chart.Metadata.HasScratch)}, " + + $"{chart.ScratchObjectCount} scratch objects, " + + $"{song.Audio.LoadedClipCount} clips loaded, {song.Audio.MissingClipCount} missing, " + + $"{song.Audio.DecodeFailedClipCount} decode failed, " + + $"{song.Audio.SilentPlaceholderCount} silent placeholders, " + + $"audio PCM {FormatMiB(song.Audio.EstimatedDecodedPcmBytes)} decoded + " + + $"{FormatMiB(song.Audio.EstimatedPremixedPcmBytes)} premix, " + + $"premix cache {(request.ReactiveKeySoundsEnabled ? "N/A" : song.Audio.SmoothMixCacheStatus)}, " + + $"BGA events {song.Bga.BaseEventCount}/{song.Bga.LayerEventCount}/{song.Bga.PoorEventCount} base/layer/poor, " + + $"{song.Bga.StaticImageCount} images and {song.Bga.VideoCount} videos, " + + $"BGA {request.BgaPlaybackMode.ToString().ToUpperInvariant()}, " + + $"reactive keysounds {(request.ReactiveKeySoundsEnabled ? "ON" : "OFF")}, " + + $"audio backend {song.Audio.BackendId}/{song.Audio.Clock.ClockId}, " + + $"gauge {gaugeLabel}, modifier {request.LaneModifier}, lane map {string.Join("-", song.LaneMap)}."; + } + + private static IEnumerator LoadStageTexture( + PlayableChart chart, + Action completed, + Func cancellationRequested) + { + var declared = !string.IsNullOrWhiteSpace(chart.Metadata.StageImage) + ? chart.Metadata.StageImage + : chart.Metadata.BackgroundImage; + var path = BmsResourceResolver.ResolveImage(chart.Metadata.SourcePath, declared); + if (string.IsNullOrEmpty(path)) + { + completed?.Invoke(null); + yield break; + } + yield return BmsImageLoader.Load(path, completed, cancellationRequested); + } + + private static bool IsCancelled(SongLoadRequest request) => + request.CancellationRequested?.Invoke() == true; + + private static string FormatMiB(long bytes) => $"{bytes / (1024d * 1024d):0.#} MiB"; + + private static void DestroyResourceRoot(GameObject resourceRoot) + { + if (resourceRoot == null) + return; + if (Application.isPlaying) + UnityEngine.Object.Destroy(resourceRoot); + else + UnityEngine.Object.DestroyImmediate(resourceRoot); + } + + private static void ObserveFaultAfterCancellation(Task task) + { + task.ContinueWith( + completed => { _ = completed.Exception; }, + TaskContinuationOptions.OnlyOnFaulted); + } + + internal sealed class PreparedChart + { + public PreparedChart( + PlayableChart chart, + IReadOnlyDictionary notesById, + int[] laneMap) + { + Chart = chart; + NotesById = notesById; + LaneMap = laneMap; + } + + public PlayableChart Chart { get; } + public IReadOnlyDictionary NotesById { get; } + public int[] LaneMap { get; } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Loading/SongLoadCoordinator.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Loading/SongLoadCoordinator.cs.meta new file mode 100644 index 0000000..bc12801 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Loading/SongLoadCoordinator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3b890350b1da44e198a83bad7aad7ce2 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/ProjectQuint.Application.asmdef b/Assets/ProjectQuint/Runtime/Unity/Features/ProjectQuint.Application.asmdef new file mode 100644 index 0000000..5325f05 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/ProjectQuint.Application.asmdef @@ -0,0 +1,21 @@ +{ + "name": "ProjectQuint.Application", + "rootNamespace": "ProjectQuint.Features", + "references": [ + "ProjectQuint.Core", + "ProjectQuint.Infrastructure", + "ProjectQuint.Media.Audio", + "ProjectQuint.Media.Images", + "ProjectQuint.Media.Video", + "Unity.InputSystem" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/ProjectQuint.Application.asmdef.meta b/Assets/ProjectQuint/Runtime/Unity/Features/ProjectQuint.Application.asmdef.meta new file mode 100644 index 0000000..16cbf65 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/ProjectQuint.Application.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4df1c926a82d44ad3874c8098dcc6fb8 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Results.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Results.meta new file mode 100644 index 0000000..f00e2b5 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Results.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a4c02bbd16daa4fb097eb4bd238812af +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Results/ResultSessionController.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Results/ResultSessionController.cs new file mode 100644 index 0000000..c09fb96 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Results/ResultSessionController.cs @@ -0,0 +1,231 @@ +using System; +using System.IO; +using ProjectQuint.Core.Progression; +using ProjectQuint.Library; +using ProjectQuint.Persistence; + +namespace ProjectQuint.Features.Results +{ + internal readonly struct GameplayCompletionOutcome + { + public GameplayCompletionOutcome(bool perfect, bool fullCombo, bool newPersonalBest) + { + Perfect = perfect; + FullCombo = fullCombo; + NewPersonalBest = newPersonalBest; + } + + public bool Perfect { get; } + public bool FullCombo { get; } + public bool NewPersonalBest { get; } + public bool HasPresentation => Perfect || FullCombo || NewPersonalBest; + public string PrimaryLabel => Perfect + ? "PERFECT" + : FullCombo ? "FULL COMBO" : NewPersonalBest ? "BEST" : string.Empty; + public string SecondaryLabel => + NewPersonalBest && (Perfect || FullCombo) ? "BEST" : string.Empty; + + public static GameplayCompletionOutcome Evaluate( + SongPlayRecord previousRecord, + bool cleared, + bool autoPlay, + bool saveFailed, + int score, + double accuracyPercent, + int maxCombo, + int missCount, + int purePerfectCount, + int totalScoringUnits) + { + var hasScoringUnits = totalScoringUnits > 0; + var perfect = cleared && + hasScoringUnits && + purePerfectCount >= totalScoringUnits; + var fullCombo = cleared && hasScoringUnits && missCount == 0; + var newPersonalBest = IsNewPersonalBest( + previousRecord, + autoPlay, + saveFailed, + score, + accuracyPercent, + maxCombo); + return new GameplayCompletionOutcome(perfect, fullCombo, newPersonalBest); + } + + public static bool IsNewPersonalBest( + SongPlayRecord previousRecord, + bool autoPlay, + bool saveFailed, + int score, + double accuracyPercent, + int maxCombo) + { + if (autoPlay || saveFailed) + return false; + if (previousRecord?.HasRecord != true) + return true; + return score > previousRecord.BestScore || + accuracyPercent > previousRecord.BestAccuracyPercent + 0.000001 || + maxCombo > previousRecord.BestMaxCombo; + } + } + + /// + /// Owns one normal-song result lifecycle independently from gameplay resources. + /// + internal sealed class ResultSessionController + { + public const double GameplayOutroDurationSeconds = 0.9; + + public bool SongCleared { get; private set; } + public bool CurrentPlayRecorded { get; private set; } + public string CurrentPlayEventId { get; private set; } = string.Empty; + public SongPlayRecord PreviousRecord { get; private set; } + public ProfileProgressUpdate ProfileUpdate { get; private set; } + public RatingUpdate RatingUpdate { get; private set; } + public string RecordSaveError { get; private set; } = string.Empty; + public double EnteredRealtime { get; private set; } + public long PlayedUtcTicks { get; private set; } + public int Selection { get; set; } = 1; + public bool GameplayOutroActive { get; private set; } + public double GameplayOutroEnteredRealtime { get; private set; } + public GameplayCompletionOutcome GameplayCompletion { get; private set; } + + public void BeginPlayback() + { + SongCleared = false; + CurrentPlayRecorded = false; + CurrentPlayEventId = Guid.NewGuid().ToString("N"); + ProfileUpdate = default; + RatingUpdate = default; + RecordSaveError = string.Empty; + GameplayOutroActive = false; + GameplayOutroEnteredRealtime = 0.0; + GameplayCompletion = default; + } + + public void BeginResults(SongPlayRecord previousRecord, bool songCleared, double enteredRealtime) + { + PreviousRecord = previousRecord; + SongCleared = songCleared; + EnteredRealtime = enteredRealtime; + PlayedUtcTicks = DateTime.UtcNow.Ticks; + Selection = 1; + } + + public void BeginGameplayOutro( + SongPlayRecord previousRecord, + bool songCleared, + GameplayCompletionOutcome completion, + double enteredRealtime) + { + BeginResults(previousRecord, songCleared, enteredRealtime); + GameplayCompletion = completion; + GameplayOutroEnteredRealtime = Math.Max(0.0, enteredRealtime); + GameplayOutroActive = completion.HasPresentation; + } + + public bool GameplayOutroComplete(double realtime) => + GameplayOutroActive && + realtime - GameplayOutroEnteredRealtime >= GameplayOutroDurationSeconds; + + public void CompleteGameplayOutro(double resultsEnteredRealtime) + { + GameplayOutroActive = false; + GameplayOutroEnteredRealtime = 0.0; + GameplayCompletion = default; + EnteredRealtime = Math.Max(0.0, resultsEnteredRealtime); + } + + public void BeginGameOver() + { + SongCleared = false; + } + + public bool TryMarkRecorded() + { + if (CurrentPlayRecorded) + return false; + + CurrentPlayRecorded = true; + return true; + } + + public void SetCommitOutcome(PlayCommitOutcome outcome) + { + ProfileUpdate = outcome.Profile; + RatingUpdate = outcome.Rating; + RecordSaveError = string.Empty; + } + + public void SetCommitFailure(string error) + { + ProfileUpdate = default; + RatingUpdate = default; + RecordSaveError = string.IsNullOrWhiteSpace(error) + ? "LOCAL RECORD COULD NOT BE SAVED" + : error.Trim(); + } + + public void ToggleSelection() + { + Selection = 1 - Selection; + } + + public void Clear() + { + SongCleared = false; + CurrentPlayRecorded = false; + CurrentPlayEventId = string.Empty; + PreviousRecord = null; + ProfileUpdate = default; + RatingUpdate = default; + RecordSaveError = string.Empty; + EnteredRealtime = 0.0; + PlayedUtcTicks = 0; + Selection = 1; + GameplayOutroActive = false; + GameplayOutroEnteredRealtime = 0.0; + GameplayCompletion = default; + } + } + + internal static class PlayResultCommitGuard + { + public static bool TryCommit( + Func commit, + out PlayCommitOutcome outcome, + out string userFacingError, + out Exception exception) + { + if (commit == null) + throw new ArgumentNullException(nameof(commit)); + + try + { + outcome = commit(); + userFacingError = string.Empty; + exception = null; + return true; + } + catch (Exception caught) + { + outcome = default; + exception = caught; + userFacingError = FormatUserFacingError(caught); + return false; + } + } + + internal static string FormatUserFacingError(Exception exception) + { + if (exception is IOException && + exception.Message.IndexOf("disk full", StringComparison.OrdinalIgnoreCase) >= 0) + { + return "STORAGE FULL // FREE DISK SPACE AND RETRY"; + } + + return "LOCAL RECORD COULD NOT BE SAVED // RETRY AFTER CHECKING STORAGE"; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Results/ResultSessionController.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Results/ResultSessionController.cs.meta new file mode 100644 index 0000000..4ab27af --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Results/ResultSessionController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b63e3c75914e04356abc0ba60f153338 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Settings.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Settings.meta new file mode 100644 index 0000000..b2e7f9a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45c451b4a0e85426a83550d79223839b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Settings/AudioSyncCalibrationSession.cs b/Assets/ProjectQuint/Runtime/Unity/Features/Settings/AudioSyncCalibrationSession.cs new file mode 100644 index 0000000..d1c01f6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Settings/AudioSyncCalibrationSession.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Audio; +using ProjectQuint.Core.Timing; + +namespace ProjectQuint.Features.Settings +{ + internal enum AudioSyncCalibrationPhase + { + Intro, + Running, + Result, + Error, + } + + internal sealed class AudioSyncCalibrationSession : IDisposable + { + private const double ClickIntervalSeconds = 0.75; + private const double InitialLeadSeconds = 1.0; + private const int MaximumExpiredMeasurementClicks = 5; + private readonly FmodAudioSyncMetronome _metronome; + private readonly IAudioClock _clock; + private readonly List _samples = new List( + AudioSyncCalibrationEstimator.MeasurementClickCount); + private double[] _clickTimes = Array.Empty(); + private int _nextExpectedClickIndex; + private int _expiredMeasurementClicks; + private bool _disposed; + + internal AudioSyncCalibrationSession( + FmodAudioSyncMetronome metronome, + IAudioClock clock, + string deviceName) + { + _metronome = metronome ?? throw new ArgumentNullException(nameof(metronome)); + _clock = clock ?? throw new ArgumentNullException(nameof(clock)); + DeviceName = string.IsNullOrWhiteSpace(deviceName) ? "UNKNOWN DEVICE" : deviceName.Trim(); + } + + internal AudioSyncCalibrationPhase Phase { get; private set; } = + AudioSyncCalibrationPhase.Intro; + internal string DeviceName { get; } + internal int PracticeProgress { get; private set; } + internal int MeasurementProgress { get; private set; } + internal AudioSyncCalibrationResult Result { get; private set; } + internal string Error { get; private set; } = string.Empty; + + internal void Begin() + { + ThrowIfDisposed(); + _samples.Clear(); + PracticeProgress = 0; + MeasurementProgress = 0; + Result = default; + Error = string.Empty; + _nextExpectedClickIndex = 0; + _expiredMeasurementClicks = 0; + try + { + _clickTimes = _metronome.ScheduleClicks( + AudioSyncCalibrationEstimator.PracticeClickCount + + AudioSyncCalibrationEstimator.MeasurementClickCount, + ClickIntervalSeconds, + InitialLeadSeconds); + Phase = AudioSyncCalibrationPhase.Running; + } + catch (Exception exception) + { + Error = exception.Message; + Phase = AudioSyncCalibrationPhase.Error; + } + } + + internal void Update() + { + if (Phase != AudioSyncCalibrationPhase.Running || _clickTimes.Length == 0) + return; + var now = _clock.CurrentTime; + while (AudioSyncTapMatcher.HasExpired( + _clickTimes, + _nextExpectedClickIndex, + now)) + { + if (_nextExpectedClickIndex >= AudioSyncCalibrationEstimator.PracticeClickCount) + _expiredMeasurementClicks++; + _nextExpectedClickIndex++; + } + + if (_expiredMeasurementClicks >= MaximumExpiredMeasurementClicks && + _samples.Count < AudioSyncCalibrationEstimator.MinimumAcceptedSamples) + { + Error = + "TAPS WERE NOT DETECTED WITHIN 650 ms. " + + "THE OUTPUT LATENCY MAY EXCEED THE SUPPORTED RANGE."; + Phase = AudioSyncCalibrationPhase.Error; + return; + } + + if (now <= _clickTimes[_clickTimes.Length - 1] + + AudioSyncTapMatcher.MaximumOffsetSeconds) + return; + + Result = AudioSyncCalibrationEstimator.Estimate(_samples); + Phase = AudioSyncCalibrationPhase.Result; + } + + internal bool RecordTap(double inputEventTime, double currentInputTime) + { + if (Phase != AudioSyncCalibrationPhase.Running || _clickTimes.Length == 0) + return false; + + var logicalTapTime = _clock.CurrentTime + inputEventTime - currentInputTime; + if (!AudioSyncTapMatcher.TryMatchExpected( + _clickTimes, + _nextExpectedClickIndex, + logicalTapTime, + out var offsetSeconds)) + return false; + + var matchedIndex = _nextExpectedClickIndex++; + if (matchedIndex >= AudioSyncCalibrationEstimator.PracticeClickCount) + { + _samples.Add(offsetSeconds * 1000.0); + MeasurementProgress = _samples.Count; + } + else + { + PracticeProgress++; + } + return true; + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + _metronome.Dispose(); + } + + private void ThrowIfDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(AudioSyncCalibrationSession)); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Features/Settings/AudioSyncCalibrationSession.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Features/Settings/AudioSyncCalibrationSession.cs.meta new file mode 100644 index 0000000..976bc2a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Features/Settings/AudioSyncCalibrationSession.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2b5a7c14bbb64555977d58f68b6396cf diff --git a/Assets/ProjectQuint/Runtime/Unity/Infrastructure.meta b/Assets/ProjectQuint/Runtime/Unity/Infrastructure.meta new file mode 100644 index 0000000..0ca3c84 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Infrastructure.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a60cd3633e88f4969a39a5a7e3c33009 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images.meta b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images.meta new file mode 100644 index 0000000..f073142 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: faec87aabb896407589e6ba72b2ae6ae +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/AssemblyInfo.cs b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/AssemblyInfo.cs new file mode 100644 index 0000000..62a8163 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ProjectQuint.Application")] +[assembly: InternalsVisibleTo("ProjectQuint.Bootstrap")] +[assembly: InternalsVisibleTo("ProjectQuint.Presentation")] +[assembly: InternalsVisibleTo("ProjectQuint.Tests.EditMode")] diff --git a/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/AssemblyInfo.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/AssemblyInfo.cs.meta new file mode 100644 index 0000000..1c041ad --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/AssemblyInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c780c1f3801da4314b596c96df564f45 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/BmsImageLoader.cs b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/BmsImageLoader.cs new file mode 100644 index 0000000..b788314 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/BmsImageLoader.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections; +using System.IO; +using System.Threading.Tasks; +using ProjectQuint.Library; +using UnityEngine; + +namespace ProjectQuint.Infrastructure.Images +{ + internal static class BmsImageLoader + { + public static IEnumerator Load( + string path, + Action completed, + Func cancellationRequested = null) + { + if (string.IsNullOrWhiteSpace(path)) + { + completed?.Invoke(null); + yield break; + } + + if (string.Equals(Path.GetExtension(path), ".bmp", StringComparison.OrdinalIgnoreCase)) + { + var decodeTask = Task.Run(() => BitmapImageDecoder.DecodeFile(path)); + while (!decodeTask.IsCompleted) + { + if (cancellationRequested?.Invoke() == true) + { + ObserveFaultAfterCancellation(decodeTask); + completed?.Invoke(null); + yield break; + } + yield return null; + } + if (cancellationRequested?.Invoke() == true) + { + completed?.Invoke(null); + yield break; + } + if (decodeTask.IsFaulted) + { + Debug.LogWarning( + $"[ProjectQuint] Could not decode BMP image '{path}': " + + decodeTask.Exception?.GetBaseException().Message); + completed?.Invoke(null); + yield break; + } + + var decoded = decodeTask.Result; + var bitmapTexture = new Texture2D(decoded.Width, decoded.Height, TextureFormat.RGBA32, false) + { + name = $"BMS Image {Path.GetFileName(path)}", + }; + bitmapTexture.SetPixels32(decoded.Pixels); + bitmapTexture.Apply(false, true); + completed?.Invoke(bitmapTexture); + yield break; + } + + var readTask = Task.Run(() => File.ReadAllBytes(path)); + while (!readTask.IsCompleted) + { + if (cancellationRequested?.Invoke() == true) + { + ObserveFaultAfterCancellation(readTask); + completed?.Invoke(null); + yield break; + } + yield return null; + } + if (cancellationRequested?.Invoke() == true) + { + completed?.Invoke(null); + yield break; + } + if (readTask.IsFaulted) + { + Debug.LogWarning( + $"[ProjectQuint] Could not read image '{path}': " + + readTask.Exception?.GetBaseException().Message); + completed?.Invoke(null); + yield break; + } + + var texture = new Texture2D(2, 2, TextureFormat.RGBA32, false) + { + name = $"BMS Image {Path.GetFileName(path)}", + }; + if (!ImageConversion.LoadImage(texture, readTask.Result, true)) + { + DestroyTexture(texture); + Debug.LogWarning($"[ProjectQuint] Could not decode image '{path}'."); + completed?.Invoke(null); + yield break; + } + completed?.Invoke(texture); + } + + public static void DestroyTexture(Texture2D texture) + { + if (texture == null) + return; + if (Application.isPlaying) + UnityEngine.Object.Destroy(texture); + else + UnityEngine.Object.DestroyImmediate(texture); + } + + private static void ObserveFaultAfterCancellation(Task task) + { + task.ContinueWith( + completed => { _ = completed.Exception; }, + TaskContinuationOptions.OnlyOnFaulted); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/BmsImageLoader.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/BmsImageLoader.cs.meta new file mode 100644 index 0000000..52009fe --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/BmsImageLoader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eb21859db5fa74522a92d7a1dc55f23c \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/ProjectQuint.Media.Images.asmdef b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/ProjectQuint.Media.Images.asmdef new file mode 100644 index 0000000..01fb6ef --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/ProjectQuint.Media.Images.asmdef @@ -0,0 +1,16 @@ +{ + "name": "ProjectQuint.Media.Images", + "rootNamespace": "ProjectQuint.Infrastructure.Images", + "references": [ + "ProjectQuint.Infrastructure" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/ProjectQuint.Media.Images.asmdef.meta b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/ProjectQuint.Media.Images.asmdef.meta new file mode 100644 index 0000000..5e0673f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Infrastructure/Images/ProjectQuint.Media.Images.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 744952c04594d4e45831ad78f362ae6d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Library.meta b/Assets/ProjectQuint/Runtime/Unity/Library.meta new file mode 100644 index 0000000..5b3a3a7 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 71c85534fc33a44a59fa589e99aba8e7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseIndex.cs b/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseIndex.cs new file mode 100644 index 0000000..5eebde9 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseIndex.cs @@ -0,0 +1,424 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using ProjectQuint.Core.Gauge; +using UnityEngine; + +namespace ProjectQuint.Library +{ + public sealed class AeryCourseIndex + { + public const string HeaderUrl = "https://asumatoki.kr/table/aery/header.json"; + private readonly List _courses; + + private AeryCourseIndex( + string providerId, + string providerDisplayName, + string tableName, + string symbol, + string lastUpdate, + List courses) + { + ProviderId = NormalizeProviderId(providerId); + ProviderDisplayName = string.IsNullOrWhiteSpace(providerDisplayName) + ? tableName ?? "BMS TABLE" + : providerDisplayName.Trim(); + TableName = tableName ?? string.Empty; + Symbol = symbol ?? string.Empty; + LastUpdate = lastUpdate ?? string.Empty; + _courses = courses ?? new List(); + foreach (var course in _courses) + { + course.ProviderId = ProviderId; + course.ProviderDisplayName = ProviderDisplayName; + course.Rules = BmsCourseRules.Analyze(course.constraint); + } + } + + public string ProviderId { get; } + public string ProviderDisplayName { get; } + public string TableName { get; } + public string Symbol { get; } + public string LastUpdate { get; } + public ExternalDataOrigin Origin { get; private set; } + public IReadOnlyList Courses => _courses; + + public void ResolveSongs(IReadOnlyList songs) + { + var byHash = (songs ?? Array.Empty()) + .SelectMany(song => new[] + { + new { Hash = song?.Metadata?.Md5, Song = song }, + new { Hash = song?.Metadata?.Sha256, Song = song }, + }) + .Where(item => !string.IsNullOrWhiteSpace(item.Hash) && item.Song != null) + .GroupBy(item => item.Hash.Trim(), StringComparer.OrdinalIgnoreCase) + .ToDictionary(group => group.Key, group => group.First(), StringComparer.OrdinalIgnoreCase); + + foreach (var course in _courses) + { + var hashes = course.StageHashes; + course.Stages = new SongLibraryEntry[hashes.Length]; + for (var stage = 0; stage < hashes.Length; stage++) + { + if (byHash.TryGetValue(hashes[stage] ?? string.Empty, out var match)) + course.Stages[stage] = match.Song; + } + course.EvaluateStageCompatibility(); + course.Record = AeryCourseRecordStore.Load(course); + } + } + + internal static ExternalDataResult LoadLocal( + string cachePath, + string bundledJson) + { + var result = ExternalJsonDataSource.LoadLocal( + "AERY courses", + cachePath, + bundledJson, + json => ParseValidated("aery", "AERY", json)); + result.Data ??= Empty("aery", "AERY"); + result.Data.Origin = result.Origin; + return result; + } + + internal static ExternalDataResult LoadTableLocal( + string providerId, + string providerDisplayName, + string headerCachePath) + { + var result = ExternalJsonDataSource.LoadLocal( + $"{providerDisplayName} courses", + headerCachePath, + string.Empty, + json => ParseValidated(providerId, providerDisplayName, json)); + result.Data ??= Empty(providerId, providerDisplayName); + result.Data.Origin = result.Origin; + return result; + } + + internal static IEnumerator LoadOnline( + string cachePath, + string bundledJson, + Action> completed) + { + yield return ExternalJsonDataSource.LoadOnline( + "AERY courses", + HeaderUrl, + cachePath, + bundledJson, + json => ParseValidated("aery", "AERY", json), + result => + { + result.Data ??= Empty("aery", "AERY"); + result.Data.Origin = result.Origin; + completed?.Invoke(result); + }); + } + + internal static AeryCourseIndex Parse(string json) + => Parse("aery", "AERY", json); + + internal static AeryCourseIndex Parse(string providerId, string providerDisplayName, string json) + { + try + { + return ParseValidated(providerId, providerDisplayName, json) ?? Empty(providerId, providerDisplayName); + } + catch (Exception exception) + { + Debug.LogWarning($"[ProjectQuint] Failed to parse BMSTable course data for '{providerId}': {exception.Message}"); + return Empty(providerId, providerDisplayName); + } + } + + private static AeryCourseIndex ParseValidated(string providerId, string providerDisplayName, string json) + { + if (string.IsNullOrWhiteSpace(json)) + return null; + + var metadata = JsonUtility.FromJson(json) ?? new AeryCourseHeaderMetadata(); + var courseArray = ExtractArrayProperty(json, "course"); + var courses = ExtractCourseObjects(courseArray) + .Select(item => + { + var course = JsonUtility.FromJson(item.Json); + if (course != null) + course.GroupIndex = item.GroupIndex; + return course; + }) + .Where(course => course != null && course.StageHashes.Length > 0) + .ToList(); + return courses.Count > 0 + ? new AeryCourseIndex( + providerId, + string.IsNullOrWhiteSpace(metadata.name) ? providerDisplayName : metadata.name, + metadata.name, + metadata.symbol, + metadata.last_update, + courses) + : null; + } + + private static AeryCourseIndex Empty(string providerId, string providerDisplayName) => + new AeryCourseIndex( + providerId, + providerDisplayName, + providerDisplayName, + string.Empty, + string.Empty, + new List()); + + private static string ExtractArrayProperty(string json, string propertyName) + { + var propertyIndex = json.IndexOf($"\"{propertyName}\"", StringComparison.Ordinal); + if (propertyIndex < 0) + throw new FormatException($"JSON property '{propertyName}' was not found."); + + var start = json.IndexOf('[', propertyIndex); + if (start < 0) + throw new FormatException($"JSON property '{propertyName}' is not an array."); + var end = FindMatchingContainerEnd(json, start, '[', ']'); + return json.Substring(start, end - start + 1); + } + + private static IEnumerable ExtractCourseObjects(string courseArray) + { + var arrayDepth = 0; + var groupIndex = -1; + var inString = false; + var escaped = false; + for (var index = 0; index < courseArray.Length; index++) + { + var character = courseArray[index]; + if (inString) + { + if (escaped) + escaped = false; + else if (character == '\\') + escaped = true; + else if (character == '"') + inString = false; + continue; + } + if (character == '"') + { + inString = true; + continue; + } + if (character == '[') + { + arrayDepth++; + if (arrayDepth == 2) + groupIndex++; + continue; + } + if (character == ']') + { + arrayDepth--; + continue; + } + if (character != '{') + continue; + var end = FindMatchingContainerEnd(courseArray, index, '{', '}'); + yield return new CourseJsonItem( + Math.Max(0, groupIndex), + courseArray.Substring(index, end - index + 1)); + index = end; + } + } + + private static int FindMatchingContainerEnd(string json, int start, char open, char close) + { + var depth = 0; + var inString = false; + var escaped = false; + for (var index = start; index < json.Length; index++) + { + var character = json[index]; + if (inString) + { + if (escaped) + escaped = false; + else if (character == '\\') + escaped = true; + else if (character == '"') + inString = false; + continue; + } + + if (character == '"') + inString = true; + else if (character == open) + depth++; + else if (character == close && --depth == 0) + return index; + } + throw new FormatException($"Unterminated JSON container '{open}'."); + } + + private static string NormalizeProviderId(string providerId) + { + var normalized = (providerId ?? string.Empty).Trim().ToLowerInvariant(); + return string.IsNullOrWhiteSpace(normalized) ? "bmstable" : normalized; + } + + [Serializable] + private sealed class AeryCourseHeaderMetadata + { + public string name; + public string symbol; + public string last_update; + } + + private readonly struct CourseJsonItem + { + public CourseJsonItem(int groupIndex, string json) + { + GroupIndex = groupIndex; + Json = json; + } + + public int GroupIndex { get; } + public string Json { get; } + } + + } + + [Serializable] + public sealed class AeryCourseDefinition + { + public string name; + public string[] constraint; + public AeryCourseTrophy[] trophy; + public string[] md5; + public string[] sha256; + + [NonSerialized] public string ProviderId = "aery"; + [NonSerialized] public string ProviderDisplayName = "AERY"; + [NonSerialized] public int GroupIndex; + [NonSerialized] public BmsCourseRules Rules = BmsCourseRules.Analyze(Array.Empty()); + [NonSerialized] public CourseGaugeProfile ResolvedGaugeProfile = CourseGaugeProfile.Lr2; + [NonSerialized] public int ResolvedKeyCount; + [NonSerialized] public bool ResolvedHasScratch; + [NonSerialized] public string StageCompatibilityError = string.Empty; + [NonSerialized] public SongLibraryEntry[] Stages = Array.Empty(); + [NonSerialized] public AeryCourseRecord Record = new AeryCourseRecord(); + + public string[] StageHashes => md5 != null && md5.Length > 0 + ? md5 + : sha256 ?? Array.Empty(); + public int StageCount => StageHashes.Length; + public int MissingStageCount => Stages == null || Stages.Length != StageCount + ? StageCount + : Stages.Count(stage => stage == null); + public bool IsAvailable => StageCount > 0 && MissingStageCount == 0; + public bool AllowsMirror => Rules?.AllowsMirror == true; + public bool IsPlayable => IsAvailable && Rules?.IsConstraintSupported == true && + string.IsNullOrWhiteSpace(StageCompatibilityError); + public string UnavailableReason + { + get + { + if (Rules?.IsConstraintSupported == false) + return Rules.UnsupportedReason; + if (!string.IsNullOrWhiteSpace(StageCompatibilityError)) + return StageCompatibilityError; + return MissingStageCount > 0 + ? $"{MissingStageCount} LOCAL CHART(S) MISSING" + : StageCount == 0 ? "COURSE HAS NO STAGES" : string.Empty; + } + } + + internal void EvaluateStageCompatibility() + { + StageCompatibilityError = string.Empty; + ResolvedKeyCount = 0; + ResolvedHasScratch = false; + if (!IsAvailable) + return; + + var keyCounts = Stages + .Select(stage => stage?.EffectiveKeyCount ?? 0) + .Distinct() + .ToArray(); + if (keyCounts.Length != 1 || keyCounts[0] < 4 || keyCounts[0] > 7) + { + StageCompatibilityError = "MIXED OR UNSUPPORTED KEY MODES"; + return; + } + ResolvedKeyCount = keyCounts[0]; + // Scratch object presence can legitimately vary between stages of the + // same key mode. The stage loader configures the scratch lane per chart. + ResolvedHasScratch = Stages.Any(stage => stage?.Metadata?.HasScratch == true); + if (Rules.GaugeRule == BmsCourseGaugeRule.FiveKeys && ResolvedKeyCount != 5) + { + StageCompatibilityError = $"5KEY GAUGE CANNOT RUN A {ResolvedKeyCount}KEY COURSE"; + return; + } + if (Rules.GaugeRule == BmsCourseGaugeRule.SevenKeys && ResolvedKeyCount != 7) + { + StageCompatibilityError = $"7KEY GAUGE CANNOT RUN A {ResolvedKeyCount}KEY COURSE"; + return; + } + ResolvedGaugeProfile = Rules.ResolveGaugeProfile(ResolvedKeyCount); + } + + public string DisplayName + { + get + { + const string marker = "段位認定 "; + var source = name ?? string.Empty; + var start = source.IndexOf(marker, StringComparison.Ordinal); + if (start >= 0) + source = source.Substring(start + marker.Length); + var version = source.IndexOf(" /", StringComparison.Ordinal); + return (version >= 0 ? source.Substring(0, version) : source).Trim(); + } + } + + public string Version + { + get + { + var source = name ?? string.Empty; + var marker = source.LastIndexOf("ver.", StringComparison.OrdinalIgnoreCase); + return marker >= 0 ? source.Substring(marker).Trim() : string.Empty; + } + } + + public AeryCourseTrophy ResolveTrophy(double missRate, double scoreRate) + { + AeryCourseTrophy resolved = null; + foreach (var candidate in trophy ?? Array.Empty()) + { + if (missRate <= candidate.missrate + 0.000001 && + scoreRate + 0.000001 >= candidate.scorerate) + resolved = candidate; + } + return resolved; + } + } + + [Serializable] + public sealed class AeryCourseTrophy + { + public string name; + public float missrate; + public float scorerate; + + public int Rank => string.Equals(name, "goldmedal", StringComparison.OrdinalIgnoreCase) ? 3 : + string.Equals(name, "silvermedal", StringComparison.OrdinalIgnoreCase) ? 2 : + string.Equals(name, "bronzemedal", StringComparison.OrdinalIgnoreCase) ? 1 : 0; + public string DisplayName => Rank switch + { + 3 => "GOLD", + 2 => "SILVER", + 1 => "BRONZE", + _ => string.Empty, + }; + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseIndex.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseIndex.cs.meta new file mode 100644 index 0000000..2c2672a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseIndex.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a890f24c14734b119e16ab7ce0fdfacb diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseRecordStore.cs b/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseRecordStore.cs new file mode 100644 index 0000000..cff28e9 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseRecordStore.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Course; +using ProjectQuint.Persistence; + +namespace ProjectQuint.Library +{ + [Serializable] + public sealed class AeryCourseRecord + { + public bool HasRecord; + public bool Cleared; + public int PlayCount; + public int ClearCount; + public int BestStageReached; + public int ScoreFormatVersion; + public int BestScore; + public double BestAccuracyPercent; + public int BestExScore; + public double BestExScoreRate; + public double BestMissRate; + public int BestMaxCombo; + public int BestTrophyRank; + public string BestTrophyName = string.Empty; + public string LastModifier = string.Empty; + } + + internal readonly struct AeryCourseResult + { + public AeryCourseResult( + bool cleared, + int stageReached, + CourseProgress progress, + AeryCourseTrophy trophy, + string modifier) + { + Cleared = cleared; + StageReached = stageReached; + Progress = progress; + Trophy = trophy; + Modifier = modifier ?? string.Empty; + } + + public bool Cleared { get; } + public int StageReached { get; } + public CourseProgress Progress { get; } + public AeryCourseTrophy Trophy { get; } + public string Modifier { get; } + } + + internal static class AeryCourseRecordStore + { + private const string PreferencePrefix = "ProjectQuint.AeryCourseRecordV3."; + + public static AeryCourseRecord Load(AeryCourseDefinition course) + => CourseRecordRepository.Shared.Load(course); + + public static AeryCourseRecord Record(AeryCourseDefinition course, AeryCourseResult result) + { + return CourseRecordRepository.Shared.Record(course, result); + } + + internal static AeryCourseRecord Merge(AeryCourseRecord record, AeryCourseResult result) + { + record ??= new AeryCourseRecord(); + NormalizeScoreFormat(record); + var firstRecord = !record.HasRecord; + record.HasRecord = true; + record.PlayCount = Math.Max(0, record.PlayCount) + 1; + record.BestStageReached = Math.Max(record.BestStageReached, Math.Max(0, result.StageReached)); + record.BestScore = Math.Max(record.BestScore, result.Progress.Score); + record.BestAccuracyPercent = Math.Max(record.BestAccuracyPercent, result.Progress.AccuracyPercent); + record.BestExScore = Math.Max(record.BestExScore, result.Progress.ExScore); + record.BestExScoreRate = Math.Max(record.BestExScoreRate, result.Progress.ExScoreRate); + record.BestMaxCombo = Math.Max(record.BestMaxCombo, result.Progress.MaxCombo); + if (firstRecord || result.Progress.MissRate < record.BestMissRate) + record.BestMissRate = result.Progress.MissRate; + if (result.Cleared) + { + record.Cleared = true; + record.ClearCount = Math.Max(0, record.ClearCount) + 1; + } + if ((result.Trophy?.Rank ?? 0) > record.BestTrophyRank) + { + record.BestTrophyRank = result.Trophy.Rank; + record.BestTrophyName = result.Trophy.DisplayName; + } + record.LastModifier = result.Modifier; + return record; + } + + internal static bool NormalizeScoreFormat(AeryCourseRecord record) + { + if (record == null || + record.ScoreFormatVersion == CourseProgress.ScoreFormatVersion) + return false; + + // The legacy score normalized the whole course to one million. + // The current score sums one independent one-million score per + // stage, so the old numeric best cannot be compared or scaled + // exactly. Preserve certification/trophy/progression and retire + // only the incompatible score. + record.BestScore = 0; + record.ScoreFormatVersion = CourseProgress.ScoreFormatVersion; + return true; + } + + internal static string BuildPreferenceKey(AeryCourseDefinition course) + => PreferencePrefix + CourseRecordRepository.BuildLegacySuffix(course); + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseRecordStore.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseRecordStore.cs.meta new file mode 100644 index 0000000..f9ed1cd --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/AeryCourseRecordStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 66d857e744274b5591d15d979dfc59ef diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/AeryTableIndex.cs b/Assets/ProjectQuint/Runtime/Unity/Library/AeryTableIndex.cs new file mode 100644 index 0000000..0f2e24a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/AeryTableIndex.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace ProjectQuint.Library +{ + public sealed class AeryTableIndex : IChartCatalog + { + public const string DataUrl = "https://asumatoki.kr/table/aery/data.json"; + private static readonly string[] LevelOrder = + { + "LEVEL 1", "LEVEL 2", "LEVEL 3", "LEVEL 4", "LEVEL 5", "LEVEL 6", "LEVEL 7", "LEVEL 8", + "LEVEL 9", "LEVEL 10", "LEVEL 11", "LEVEL 12", "LEVEL 13", "LEVEL 14", "LEVEL 15", "LEVEL 15+", + "LEVEL 16", "LEVEL 16+", "LEVEL 17", "LEVEL 17+", "LEVEL 18", "LEVEL 18+", "LEVEL 19", "LEVEL 19+", + "LEVEL 20", "LEVEL 20+", "LEVEL 99", "LEVEL GIMMICK", "LEVEL SPECIAL", "LEVEL DUMMY", "OLD CHARTS", + }; + + private readonly Dictionary _entries; + + private AeryTableIndex(Dictionary entries) + { + _entries = entries ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public string ProviderId => "aery"; + public string DisplayName => "AERY"; + public ExternalDataOrigin Origin { get; private set; } + public int Count => _entries.Count; + + public bool TryGet(string chartHash, out ChartCatalogEntry entry) => + _entries.TryGetValue((chartHash ?? string.Empty).Trim(), out entry); + + internal static ExternalDataResult LoadLocal(string cachePath, string bundledJson) + { + var result = ExternalJsonDataSource.LoadLocal( + "AERY table", + cachePath, + bundledJson, + ParseValidated); + result.Data ??= Empty(); + result.Data.Origin = result.Origin; + return result; + } + + internal static IEnumerator LoadOnline( + string cachePath, + string bundledJson, + Action> completed) + { + yield return ExternalJsonDataSource.LoadOnline( + "AERY table", + DataUrl, + cachePath, + bundledJson, + ParseValidated, + result => + { + result.Data ??= Empty(); + result.Data.Origin = result.Origin; + completed?.Invoke(result); + }); + } + + internal static AeryTableIndex Parse(string json) => + ParseValidated(json) ?? Empty(); + + private static AeryTableIndex ParseValidated(string json) + { + var wrapper = JsonUtility.FromJson($"{{\"items\":{json}}}"); + if (wrapper?.items == null || wrapper.items.Length == 0) + return null; + + var levelOrder = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var index = 0; index < LevelOrder.Length; index++) + levelOrder[LevelOrder[index]] = index; + + var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var item in wrapper.items) + { + if (item == null || string.IsNullOrWhiteSpace(item.md5)) + continue; + var level = item.level ?? string.Empty; + entries[item.md5.Trim()] = new ChartCatalogEntry + { + DifficultyLabel = level, + DifficultyValue = level, + DifficultySortIndex = levelOrder.TryGetValue(level, out var sortIndex) + ? sortIndex + : int.MaxValue, + TitleOverride = item.title ?? string.Empty, + ArtistOverride = item.artist ?? string.Empty, + }; + } + return entries.Count > 0 ? new AeryTableIndex(entries) : null; + } + + private static AeryTableIndex Empty() => + new AeryTableIndex(new Dictionary(StringComparer.OrdinalIgnoreCase)); + + [Serializable] + private sealed class AeryTableWrapper + { + public AeryTableEntry[] items; + } + + [Serializable] + private sealed class AeryTableEntry + { + public string level; + public string title; + public string artist; + public string md5; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/AeryTableIndex.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/AeryTableIndex.cs.meta new file mode 100644 index 0000000..791228c --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/AeryTableIndex.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: af68e030dde784779b15919621a9d264 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BitmapImageDecoder.cs b/Assets/ProjectQuint/Runtime/Unity/Library/BitmapImageDecoder.cs new file mode 100644 index 0000000..0da6f83 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BitmapImageDecoder.cs @@ -0,0 +1,150 @@ +using System; +using System.IO; +using UnityEngine; + +namespace ProjectQuint.Library +{ + internal sealed class DecodedBitmap + { + public DecodedBitmap(int width, int height, Color32[] pixels) + { + Width = width; + Height = height; + Pixels = pixels; + } + + public int Width { get; } + public int Height { get; } + public Color32[] Pixels { get; } + } + + internal static class BitmapImageDecoder + { + private const uint BitmapInfoHeaderSize = 40; + private const uint UncompressedRgb = 0; + private const int MaximumPixelCount = 64 * 1024 * 1024; + + public static DecodedBitmap DecodeFile(string path) => Decode(File.ReadAllBytes(path)); + + public static DecodedBitmap Decode(byte[] data) + { + if (data == null || data.Length < 54 || data[0] != (byte)'B' || data[1] != (byte)'M') + throw new InvalidDataException("The file is not a Windows BMP image."); + + var pixelOffset = ReadUInt32(data, 10); + var headerSize = ReadUInt32(data, 14); + if (headerSize < BitmapInfoHeaderSize) + throw new InvalidDataException("Only Windows BITMAPINFOHEADER images are supported."); + + var width = ReadInt32(data, 18); + var signedHeight = ReadInt32(data, 22); + var planes = ReadUInt16(data, 26); + var bitsPerPixel = ReadUInt16(data, 28); + var compression = ReadUInt32(data, 30); + if (width <= 0 || signedHeight == 0 || planes != 1) + throw new InvalidDataException("The BMP image dimensions or plane count are invalid."); + if (bitsPerPixel != 1 && bitsPerPixel != 4 && bitsPerPixel != 8 && + bitsPerPixel != 24 && bitsPerPixel != 32) + throw new InvalidDataException($"Unsupported BMP bit depth: {bitsPerPixel}."); + if (compression != UncompressedRgb) + throw new InvalidDataException($"Unsupported compressed BMP format: {compression}."); + + var height = Math.Abs(signedHeight); + var pixelCountLong = checked((long)width * height); + if (pixelCountLong > MaximumPixelCount) + throw new InvalidDataException("The BMP image is too large to load safely."); + var pixelCount = (int)pixelCountLong; + + var rowStride = checked((int)(((long)width * bitsPerPixel + 31) / 32 * 4)); + var pixelBytes = checked((long)rowStride * height); + if (pixelOffset > data.Length || pixelOffset + pixelBytes > data.Length) + throw new InvalidDataException("The BMP pixel data is truncated."); + + Color32[] palette = null; + if (bitsPerPixel <= 8) + { + var colorsUsed = headerSize >= 36 ? ReadUInt32(data, 46) : 0; + var maximumPaletteCount = 1 << bitsPerPixel; + var paletteCount = colorsUsed == 0 + ? maximumPaletteCount + : checked((int)colorsUsed); + var paletteOffset = checked(14 + (int)headerSize); + if (paletteCount <= 0 || paletteCount > maximumPaletteCount || + paletteOffset + paletteCount * 4 > pixelOffset) + throw new InvalidDataException("The BMP color palette is invalid."); + + palette = new Color32[paletteCount]; + for (var index = 0; index < paletteCount; index++) + { + var entry = paletteOffset + index * 4; + palette[index] = new Color32(data[entry + 2], data[entry + 1], data[entry], 255); + } + } + + var pixels = new Color32[pixelCount]; + var topDown = signedHeight < 0; + for (var sourceRow = 0; sourceRow < height; sourceRow++) + { + var destinationRow = topDown ? height - 1 - sourceRow : sourceRow; + var source = checked((int)pixelOffset + sourceRow * rowStride); + var destination = destinationRow * width; + for (var x = 0; x < width; x++) + { + if (bitsPerPixel <= 8) + { + var paletteIndex = ReadPaletteIndex(data, source, x, bitsPerPixel); + if (paletteIndex >= palette.Length) + throw new InvalidDataException("A BMP pixel references a missing palette color."); + pixels[destination + x] = palette[paletteIndex]; + } + else + { + var bytesPerPixel = bitsPerPixel / 8; + var pixel = source + x * bytesPerPixel; + pixels[destination + x] = new Color32( + data[pixel + 2], + data[pixel + 1], + data[pixel], + 255); + } + } + } + + return new DecodedBitmap(width, height, pixels); + } + + private static int ReadPaletteIndex(byte[] data, int rowOffset, int x, int bitsPerPixel) + { + return bitsPerPixel switch + { + 1 => (data[rowOffset + x / 8] >> (7 - x % 8)) & 0x01, + 4 => (data[rowOffset + x / 2] >> (x % 2 == 0 ? 4 : 0)) & 0x0f, + 8 => data[rowOffset + x], + _ => throw new InvalidDataException($"Unsupported indexed BMP bit depth: {bitsPerPixel}."), + }; + } + + private static ushort ReadUInt16(byte[] data, int offset) + { + EnsureRange(data, offset, 2); + return (ushort)(data[offset] | data[offset + 1] << 8); + } + + private static uint ReadUInt32(byte[] data, int offset) + { + EnsureRange(data, offset, 4); + return (uint)(data[offset] | + data[offset + 1] << 8 | + data[offset + 2] << 16 | + data[offset + 3] << 24); + } + + private static int ReadInt32(byte[] data, int offset) => unchecked((int)ReadUInt32(data, offset)); + + private static void EnsureRange(byte[] data, int offset, int count) + { + if (offset < 0 || count < 0 || offset > data.Length - count) + throw new InvalidDataException("The BMP header is truncated."); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BitmapImageDecoder.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/BitmapImageDecoder.cs.meta new file mode 100644 index 0000000..654bad7 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BitmapImageDecoder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1aad1a4655da0412db037a63625494d0 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsCourseRules.cs b/Assets/ProjectQuint/Runtime/Unity/Library/BmsCourseRules.cs new file mode 100644 index 0000000..f7f9df7 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsCourseRules.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProjectQuint.Core.Gauge; + +namespace ProjectQuint.Library +{ + public enum BmsCourseGradeRule + { + Unspecified, + NormalOnly, + MirrorAllowed, + RandomAllowed, + } + + public enum BmsCourseGaugeRule + { + Automatic, + Lr2, + FiveKeys, + SevenKeys, + NineKeys, + TwentyFourKeys, + } + + public enum BmsCourseLongNoteRule + { + Unspecified, + Ln, + Cn, + Hcn, + } + + public sealed class BmsCourseRules + { + private readonly string[] _unsupportedReasons; + + private BmsCourseRules( + BmsCourseGradeRule gradeRule, + BmsCourseGaugeRule gaugeRule, + BmsCourseLongNoteRule longNoteRule, + bool noSpeed, + bool noGood, + bool noGreat, + string[] unsupportedReasons) + { + GradeRule = gradeRule; + GaugeRule = gaugeRule; + LongNoteRule = longNoteRule; + NoSpeed = noSpeed; + NoGood = noGood; + NoGreat = noGreat; + _unsupportedReasons = unsupportedReasons ?? Array.Empty(); + } + + public BmsCourseGradeRule GradeRule { get; } + public BmsCourseGaugeRule GaugeRule { get; } + public BmsCourseLongNoteRule LongNoteRule { get; } + public bool NoSpeed { get; } + public bool NoGood { get; } + public bool NoGreat { get; } + public bool AllowsMirror => GradeRule == BmsCourseGradeRule.MirrorAllowed || + GradeRule == BmsCourseGradeRule.RandomAllowed; + public bool IsConstraintSupported => _unsupportedReasons.Length == 0; + public string UnsupportedReason => string.Join(" // ", _unsupportedReasons); + + public string Summary + { + get + { + var grade = GradeRule switch + { + BmsCourseGradeRule.NormalOnly => "NORMAL ONLY", + BmsCourseGradeRule.MirrorAllowed => "NORMAL / MIRROR", + BmsCourseGradeRule.RandomAllowed => "NORMAL / MIRROR (RANDOM DISABLED)", + _ => "PROJECTQUINT COURSE", + }; + var gauge = GaugeRule switch + { + BmsCourseGaugeRule.Lr2 => "LR2 GAUGE", + BmsCourseGaugeRule.FiveKeys => "5KEY GAUGE", + BmsCourseGaugeRule.SevenKeys => "7KEY GAUGE", + BmsCourseGaugeRule.NineKeys => "9KEY GAUGE", + BmsCourseGaugeRule.TwentyFourKeys => "24KEY GAUGE", + _ => "AUTO GAUGE", + }; + var longNote = LongNoteRule switch + { + BmsCourseLongNoteRule.Ln => "LN", + BmsCourseLongNoteRule.Cn => "CN", + BmsCourseLongNoteRule.Hcn => "HCN", + _ => "NATIVE LN", + }; + return $"{gauge} // {longNote} // {grade}"; + } + } + + public CourseGaugeProfile ResolveGaugeProfile(int keyCount) + { + return GaugeRule switch + { + BmsCourseGaugeRule.Lr2 => CourseGaugeProfile.Lr2, + BmsCourseGaugeRule.FiveKeys => CourseGaugeProfile.FiveKey, + BmsCourseGaugeRule.SevenKeys => CourseGaugeProfile.SevenKey, + _ => keyCount == 5 + ? CourseGaugeProfile.FiveKey + : CourseGaugeProfile.SevenKey, + }; + } + + public static BmsCourseRules Analyze(IEnumerable rawConstraints) + { + var grade = BmsCourseGradeRule.Unspecified; + var gauge = BmsCourseGaugeRule.Automatic; + var longNote = BmsCourseLongNoteRule.Unspecified; + var gradeSet = false; + var gaugeSet = false; + var longNoteSet = false; + var noSpeed = false; + var noGood = false; + var noGreat = false; + var unsupported = new List(); + + foreach (var raw in rawConstraints ?? Array.Empty()) + { + var value = (raw ?? string.Empty).Trim().ToLowerInvariant(); + if (value.Length == 0) + continue; + switch (value) + { + case "grade" when !gradeSet: + grade = BmsCourseGradeRule.NormalOnly; + gradeSet = true; + break; + case "grade_mirror" when !gradeSet: + grade = BmsCourseGradeRule.MirrorAllowed; + gradeSet = true; + break; + case "grade_random" when !gradeSet: + grade = BmsCourseGradeRule.RandomAllowed; + gradeSet = true; + break; + case "gauge_lr2" when !gaugeSet: + gauge = BmsCourseGaugeRule.Lr2; + gaugeSet = true; + break; + case "gauge_5k" when !gaugeSet: + gauge = BmsCourseGaugeRule.FiveKeys; + gaugeSet = true; + break; + case "gauge_7k" when !gaugeSet: + gauge = BmsCourseGaugeRule.SevenKeys; + gaugeSet = true; + break; + case "gauge_9k" when !gaugeSet: + gauge = BmsCourseGaugeRule.NineKeys; + gaugeSet = true; + unsupported.Add("9KEY COURSE GAUGE IS NOT SUPPORTED"); + break; + case "gauge_24k" when !gaugeSet: + gauge = BmsCourseGaugeRule.TwentyFourKeys; + gaugeSet = true; + unsupported.Add("24KEY COURSE GAUGE IS NOT SUPPORTED"); + break; + case "ln" when !longNoteSet: + longNote = BmsCourseLongNoteRule.Ln; + longNoteSet = true; + break; + case "cn" when !longNoteSet: + longNote = BmsCourseLongNoteRule.Cn; + longNoteSet = true; + unsupported.Add("CN COURSE SEMANTICS ARE NOT SUPPORTED"); + break; + case "hcn" when !longNoteSet: + longNote = BmsCourseLongNoteRule.Hcn; + longNoteSet = true; + unsupported.Add("HCN COURSE SEMANTICS ARE NOT SUPPORTED"); + break; + case "no_speed": + noSpeed = true; + unsupported.Add("NO_SPEED COURSE SEMANTICS ARE NOT SUPPORTED"); + break; + case "no_good": + noGood = true; + unsupported.Add("NO_GOOD COURSE SEMANTICS ARE NOT SUPPORTED"); + break; + case "no_great": + noGreat = true; + unsupported.Add("NO_GREAT COURSE SEMANTICS ARE NOT SUPPORTED"); + break; + case "grade": + case "grade_mirror": + case "grade_random": + case "gauge_lr2": + case "gauge_5k": + case "gauge_7k": + case "gauge_9k": + case "gauge_24k": + case "ln": + case "cn": + case "hcn": + // beatoraja keeps the first constraint from each category. + break; + default: + unsupported.Add($"UNKNOWN COURSE CONSTRAINT: {value.ToUpperInvariant()}"); + break; + } + } + + return new BmsCourseRules( + grade, + gauge, + longNote, + noSpeed, + noGood, + noGreat, + unsupported.Distinct(StringComparer.Ordinal).ToArray()); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsCourseRules.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/BmsCourseRules.cs.meta new file mode 100644 index 0000000..e5b9c78 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsCourseRules.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d1faacc0d46574d188e8698ae01fec42 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableIndex.cs b/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableIndex.cs new file mode 100644 index 0000000..ce419fd --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableIndex.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using UnityEngine; +using UnityEngine.Networking; + +namespace ProjectQuint.Library +{ + public readonly struct BmsDifficultyTablePreset + { + public BmsDifficultyTablePreset( + string id, + string displayName, + string headerUrl, + int defaultKeyCount = 0, + bool defaultHasScratch = false) + { + Id = id; + DisplayName = displayName; + HeaderUrl = headerUrl; + DefaultKeyCount = defaultKeyCount; + DefaultHasScratch = defaultHasScratch; + } + + public string Id { get; } + public string DisplayName { get; } + public string HeaderUrl { get; } + public int DefaultKeyCount { get; } + public bool DefaultHasScratch { get; } + } + + public sealed class BmsDifficultyTableIndex : IChartCatalog + { + public static readonly BmsDifficultyTablePreset[] Presets = + { + new BmsDifficultyTablePreset( + "aery", + "5KEYS AERY", + "https://asumatoki.kr/table/aery/header.json", + 5), + new BmsDifficultyTablePreset( + "satellite", + "Satellite", + "https://stellabms.xyz/sl/header.json", + 7, + true), + new BmsDifficultyTablePreset( + "stella", + "Stella", + "https://stellabms.xyz/st/header.json", + 7, + true), + new BmsDifficultyTablePreset( + "genocide-insane", + "GENOCIDE Insane BMS (legacy mirror)", + "https://miraiscarlet.github.io/bms/table/genocide_insane/header_insane.json", + 7, + true), + new BmsDifficultyTablePreset( + "4ue", + "4UE Difficulty Table", + "https://classmaterma.github.io/4UE/header.json", + 4), + new BmsDifficultyTablePreset( + "6ue", + "6UE Difficulty Table", + "https://classmaterma.github.io/UE/table.html", + 6), + }; + + public static BmsDifficultyTablePreset? FindPreset(string tableId, string headerUrl = null) + { + foreach (var preset in Presets) + { + if (!string.IsNullOrWhiteSpace(tableId) && + string.Equals(preset.Id, tableId.Trim(), StringComparison.OrdinalIgnoreCase)) + return preset; + if (!string.IsNullOrWhiteSpace(headerUrl) && + string.Equals(preset.HeaderUrl, headerUrl.Trim(), StringComparison.OrdinalIgnoreCase)) + return preset; + } + return null; + } + + private static readonly Regex BmsTableMetaRegex = new Regex( + "]+name\\s*=\\s*[\\\"']bmstable[\\\"'][^>]+content\\s*=\\s*[\\\"'](?[^\\\"']+)[\\\"'][^>]*>|" + + "]+content\\s*=\\s*[\\\"'](?[^\\\"']+)[\\\"'][^>]+name\\s*=\\s*[\\\"']bmstable[\\\"'][^>]*>", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private readonly Dictionary _entries; + private readonly int _chartCount; + + private BmsDifficultyTableIndex( + string providerId, + string displayName, + Dictionary entries, + int chartCount) + { + ProviderId = providerId ?? string.Empty; + DisplayName = string.IsNullOrWhiteSpace(displayName) ? "BMS TABLE" : displayName; + _entries = entries ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + _chartCount = Math.Max(0, chartCount); + } + + public string ProviderId { get; } + public string DisplayName { get; } + public ExternalDataOrigin Origin { get; private set; } + public int Count => _chartCount; + + public bool TryGet(string chartHash, out ChartCatalogEntry entry) => + _entries.TryGetValue((chartHash ?? string.Empty).Trim(), out entry); + + internal static ExternalDataResult LoadLocal( + string providerId, + string headerCachePath, + string dataCachePath) + { + try + { + if (!File.Exists(headerCachePath) || !File.Exists(dataCachePath)) + return Unavailable("Difficulty table: cache not found"); + var headerJson = File.ReadAllText(headerCachePath); + var header = ParseHeader(headerJson); + var index = ParseData(providerId, header, headerJson, File.ReadAllText(dataCachePath)); + index.Origin = ExternalDataOrigin.Cache; + return new ExternalDataResult + { + Data = index, + Origin = ExternalDataOrigin.Cache, + Status = $"{index.DisplayName}: cached", + }; + } + catch (Exception exception) + { + return Unavailable($"Difficulty table cache invalid: {exception.Message}"); + } + } + + internal static IEnumerator LoadOnline( + string providerId, + string configuredUrl, + string headerCachePath, + string dataCachePath, + Action> completed) + { + if (!Uri.TryCreate(configuredUrl, UriKind.Absolute, out var configuredUri) || + configuredUri.Scheme != Uri.UriSchemeHttps) + { + completed?.Invoke(Unavailable("Difficulty table URL must use HTTPS")); + yield break; + } + + string firstDocument = null; + string requestFailure = null; + using (var request = UnityWebRequest.Get(configuredUri.AbsoluteUri)) + { + request.timeout = 12; + yield return request.SendWebRequest(); + if (request.result == UnityWebRequest.Result.Success) + firstDocument = request.downloadHandler.text; + else + requestFailure = request.error; + } + + var headerUrl = configuredUri.AbsoluteUri; + var headerJson = firstDocument; + if (!string.IsNullOrWhiteSpace(firstDocument) && LooksLikeHtml(firstDocument)) + { + var match = BmsTableMetaRegex.Match(firstDocument); + if (!match.Success) + { + completed?.Invoke(Unavailable("Table page has no bmstable header metadata")); + yield break; + } + var resolvedHeaderUri = new Uri(configuredUri, match.Groups["url"].Value); + if (resolvedHeaderUri.Scheme != Uri.UriSchemeHttps) + { + completed?.Invoke(Unavailable("Difficulty table header URL must use HTTPS")); + yield break; + } + headerUrl = resolvedHeaderUri.AbsoluteUri; + using var headerRequest = UnityWebRequest.Get(headerUrl); + headerRequest.timeout = 12; + yield return headerRequest.SendWebRequest(); + headerJson = headerRequest.result == UnityWebRequest.Result.Success + ? headerRequest.downloadHandler.text + : null; + requestFailure = headerRequest.result == UnityWebRequest.Result.Success + ? null + : headerRequest.error; + } + + if (string.IsNullOrWhiteSpace(headerJson)) + { + var cached = LoadLocal(providerId, headerCachePath, dataCachePath); + cached.Status = $"{cached.Status} // online header failed: {requestFailure}"; + completed?.Invoke(cached); + yield break; + } + + BmsTableHeader header; + try + { + header = ParseHeader(headerJson); + } + catch (Exception exception) + { + completed?.Invoke(Unavailable($"Difficulty table header invalid: {exception.Message}")); + yield break; + } + + var dataUri = new Uri(new Uri(headerUrl), header.data_url); + if (dataUri.Scheme != Uri.UriSchemeHttps) + { + completed?.Invoke(Unavailable("Difficulty table data URL must use HTTPS")); + yield break; + } + var dataUrl = dataUri.AbsoluteUri; + ExternalDataResult dataResult = null; + yield return ExternalJsonDataSource.LoadOnline( + string.IsNullOrWhiteSpace(header.name) ? "BMS table" : header.name, + dataUrl, + dataCachePath, + string.Empty, + json => ParseData(providerId, header, headerJson, json), + result => dataResult = result); + dataResult ??= Unavailable("Difficulty table data request did not complete"); + if (dataResult.Data != null) + dataResult.Data.Origin = dataResult.Origin; + if (dataResult.Origin != ExternalDataOrigin.Unavailable) + { + try + { + ExternalJsonDataSource.WriteAtomically(headerCachePath, headerJson); + } + catch (Exception exception) + { + dataResult.Status += $" // header cache failed: {exception.Message}"; + } + } + completed?.Invoke(dataResult); + } + + internal static BmsDifficultyTableIndex Parse( + string providerId, + string headerJson, + string dataJson) + { + var header = ParseHeader(headerJson); + return ParseData(providerId, header, headerJson, dataJson); + } + + private static BmsTableHeader ParseHeader(string json) + { + json = TrimBom(json); + var header = JsonUtility.FromJson(json); + if (header == null || string.IsNullOrWhiteSpace(header.data_url)) + throw new InvalidDataException("header data_url is missing"); + return header; + } + + private static BmsDifficultyTableIndex ParseData( + string providerId, + BmsTableHeader header, + string headerJson, + string dataJson) + { + var wrapper = JsonUtility.FromJson( + $"{{\"items\":{TrimBom(dataJson)}}}"); + if (wrapper?.items == null || wrapper.items.Length == 0) + throw new InvalidDataException("table data contains no charts"); + + var levelOrder = ParseLevelOrder(headerJson); + var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); + var chartCount = 0; + foreach (var item in wrapper.items) + { + if (item == null) + continue; + var md5 = (item.md5 ?? string.Empty).Trim(); + var sha256 = (item.sha256 ?? string.Empty).Trim(); + if (md5.Length == 0 && sha256.Length == 0) + continue; + + var rawLevel = item.level ?? string.Empty; + var entry = new ChartCatalogEntry + { + DifficultyLabel = FormatLevel(header.symbol, rawLevel), + DifficultyValue = rawLevel, + DifficultySortIndex = ResolveSortIndex(rawLevel, levelOrder), + TitleOverride = item.title ?? string.Empty, + ArtistOverride = item.artist ?? string.Empty, + }; + if (md5.Length > 0) + entries[md5] = entry; + if (sha256.Length > 0) + entries[sha256] = entry; + chartCount++; + } + if (chartCount == 0) + throw new InvalidDataException("table data contains no chart hashes"); + return new BmsDifficultyTableIndex(providerId, header.name, entries, chartCount); + } + + private static Dictionary ParseLevelOrder(string headerJson) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + var match = Regex.Match( + TrimBom(headerJson), + "\\\"level_order\\\"\\s*:\\s*\\[(?.*?)\\]", + RegexOptions.Singleline | RegexOptions.IgnoreCase); + if (!match.Success) + return result; + var tokens = Regex.Matches(match.Groups["items"].Value, "\\\"(?[^\\\"]*)\\\"|(?-?\\d+(?:\\.\\d+)?)"); + for (var index = 0; index < tokens.Count; index++) + { + var value = tokens[index].Groups["quoted"].Success + ? tokens[index].Groups["quoted"].Value + : tokens[index].Groups["plain"].Value; + result[value] = index; + } + return result; + } + + private static int ResolveSortIndex(string level, IReadOnlyDictionary order) + { + level ??= string.Empty; + if (order.TryGetValue(level, out var index)) + return index; + return double.TryParse(level, System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, out var numeric) + ? (int)Math.Round(numeric * 1000.0) + : int.MaxValue; + } + + private static string FormatLevel(string symbol, string level) + { + symbol ??= string.Empty; + level ??= string.Empty; + if (string.IsNullOrWhiteSpace(symbol) || + level.StartsWith(symbol, StringComparison.OrdinalIgnoreCase) || + level.IndexOf("LEVEL", StringComparison.OrdinalIgnoreCase) >= 0) + return level; + return symbol + level; + } + + private static bool LooksLikeHtml(string text) => + TrimBom(text).TrimStart().StartsWith("<", StringComparison.Ordinal); + + private static string TrimBom(string text) => (text ?? string.Empty).TrimStart('\uFEFF'); + + private static ExternalDataResult Unavailable(string status) => + new ExternalDataResult + { + Origin = ExternalDataOrigin.Unavailable, + Status = status ?? "Difficulty table unavailable", + }; + + [Serializable] + private sealed class BmsTableHeader + { + public string name; + public string symbol; + public string data_url; + } + + [Serializable] + private sealed class BmsTableDataWrapper + { + public BmsTableDataEntry[] items; + } + + [Serializable] + private sealed class BmsTableDataEntry + { + public string md5; + public string sha256; + public string level; + public string title; + public string artist; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableIndex.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableIndex.cs.meta new file mode 100644 index 0000000..004aa13 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableIndex.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: aa03be4976b4a43409e640dde0dba7de \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableRegistry.cs b/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableRegistry.cs new file mode 100644 index 0000000..ba557d4 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableRegistry.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using ProjectQuint.Persistence; + +namespace ProjectQuint.Library +{ + [Serializable] + public sealed class BmsDifficultyTableDefinition + { + public string Id = string.Empty; + public string DisplayName = string.Empty; + public string HeaderUrl = string.Empty; + public long AddedUtcTicks; + public List LegacyProviderIds = new List(); + + public bool IsConfigured => !string.IsNullOrWhiteSpace(HeaderUrl); + } + + [Serializable] + internal sealed class BmsDifficultyTableRegistryDocument + { + public const int CurrentSchemaVersion = 1; + + public int SchemaVersion = CurrentSchemaVersion; + public string ActiveTableId = string.Empty; + public List Tables = new List(); + } + + /// + /// Global difficulty-table definition and cache registry. Each pack stores only a table ID + /// reference; entries still match indexed charts by MD5/SHA-256. + /// + internal sealed class BmsDifficultyTableRegistry + { + private readonly AtomicJsonFile _file; + private readonly string _cacheRoot; + private readonly List _tables = + new List(); + + public BmsDifficultyTableRegistry( + string registryPath = null, + string cacheRoot = null, + Action logWarning = null) + { + var path = registryPath ?? PersistencePaths.DifficultyTableRegistry; + _cacheRoot = cacheRoot ?? PersistencePaths.DifficultyTableRoot; + _file = new AtomicJsonFile( + path, + document => document != null && + document.SchemaVersion == BmsDifficultyTableRegistryDocument.CurrentSchemaVersion && + document.Tables != null, + logWarning); + Load(); + EnsurePresets(); + } + + public IReadOnlyList Tables => _tables; + public string ActiveTableId { get; private set; } = string.Empty; + public BmsDifficultyTableDefinition ActiveTable => Find(ActiveTableId); + + public BmsDifficultyTableDefinition Register( + string headerUrl, + string displayName = null, + string preferredId = null, + bool activate = true) + { + var url = NormalizeUrl(headerUrl); + var preset = FindPresetByUrl(url); + var id = NormalizeId(!string.IsNullOrWhiteSpace(preferredId) + ? preferredId + : preset?.Id ?? BuildStableId(url)); + var existing = FindByUrl(url) ?? Find(id); + if (existing == null) + { + existing = new BmsDifficultyTableDefinition + { + Id = id, + DisplayName = ResolveDisplayName(displayName, preset, id), + HeaderUrl = url, + AddedUtcTicks = DateTime.UtcNow.Ticks, + }; + _tables.Add(existing); + } + else + { + existing.HeaderUrl = url; + if (!string.IsNullOrWhiteSpace(displayName)) + existing.DisplayName = displayName.Trim(); + else if (string.IsNullOrWhiteSpace(existing.DisplayName)) + existing.DisplayName = ResolveDisplayName(null, preset, existing.Id); + } + + if (activate) + ActiveTableId = existing.Id; + Save(); + return existing; + } + + public BmsDifficultyTableDefinition RegisterPreset(int presetIndex, bool activate = true) + { + if (presetIndex < 0 || presetIndex >= BmsDifficultyTableIndex.Presets.Length) + throw new ArgumentOutOfRangeException(nameof(presetIndex)); + var preset = BmsDifficultyTableIndex.Presets[presetIndex]; + return Register(preset.HeaderUrl, preset.DisplayName, preset.Id, activate); + } + + public void Select(string tableId) + { + var table = Find(tableId) ?? throw new ArgumentException("Unknown difficulty table.", nameof(tableId)); + ActiveTableId = table.Id; + Save(); + } + + public bool Remove(string tableId) + { + var table = Find(tableId); + if (table == null) + return false; + _tables.Remove(table); + if (string.Equals(ActiveTableId, table.Id, StringComparison.OrdinalIgnoreCase)) + ActiveTableId = string.Empty; + Save(); + return true; + } + + public void ClearSelection() + { + if (string.IsNullOrWhiteSpace(ActiveTableId)) + return; + ActiveTableId = string.Empty; + Save(); + } + + public BmsDifficultyTableDefinition Find(string tableId) + { + if (string.IsNullOrWhiteSpace(tableId)) + return null; + return _tables.Find(table => + string.Equals(table.Id, tableId.Trim(), StringComparison.OrdinalIgnoreCase)); + } + + public BmsDifficultyTableDefinition FindByUrl(string headerUrl) + { + if (string.IsNullOrWhiteSpace(headerUrl)) + return null; + var normalized = NormalizeUrl(headerUrl); + return _tables.Find(table => + string.Equals(table.HeaderUrl, normalized, StringComparison.OrdinalIgnoreCase)); + } + + public int MigrateLegacyPackTables(IEnumerable packs, string preferredPackId) + { + var candidates = (packs ?? Array.Empty()) + .Where(pack => pack != null && !string.IsNullOrWhiteSpace(pack.DifficultyTableUrl)) + .OrderBy(pack => string.Equals(pack.Id, preferredPackId, StringComparison.OrdinalIgnoreCase) ? 0 : 1) + .ToList(); + var migrated = 0; + BmsDifficultyTableDefinition preferred = null; + foreach (var pack in candidates) + { + var before = FindByUrl(pack.DifficultyTableUrl); + var table = Register(pack.DifficultyTableUrl, activate: false); + PromoteLegacyCache(pack.Id, table); + table.LegacyProviderIds ??= new List(); + if (!string.Equals(pack.Id, table.Id, StringComparison.OrdinalIgnoreCase) && + !table.LegacyProviderIds.Any(id => + string.Equals(id, pack.Id, StringComparison.OrdinalIgnoreCase))) + table.LegacyProviderIds.Add(pack.Id); + if (before == null) + migrated++; + if (preferred == null) + preferred = table; + } + if (preferred != null) + { + ActiveTableId = preferred.Id; + Save(); + } + return migrated; + } + + public IReadOnlyDictionary BuildLegacyProviderMap() + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var table in _tables) + { + foreach (var legacyId in table.LegacyProviderIds ?? new List()) + { + if (!string.IsNullOrWhiteSpace(legacyId)) + result[legacyId.Trim()] = table; + } + } + return result; + } + + public void UpdateDisplayName(string tableId, string displayName) + { + var table = Find(tableId); + if (table == null || string.IsNullOrWhiteSpace(displayName) || + string.Equals(table.DisplayName, displayName.Trim(), StringComparison.Ordinal)) + return; + table.DisplayName = displayName.Trim(); + Save(); + } + + public string HeaderCachePathFor(BmsDifficultyTableDefinition table) => + CachePathFor(table, "header.json"); + + public string DataCachePathFor(BmsDifficultyTableDefinition table) => + CachePathFor(table, "data.json"); + + private string CachePathFor(BmsDifficultyTableDefinition table, string fileName) + { + if (table == null || string.IsNullOrWhiteSpace(table.Id)) + throw new ArgumentNullException(nameof(table)); + return Path.Combine(_cacheRoot, NormalizeId(table.Id), fileName); + } + + private void EnsurePresets() + { + var changed = false; + foreach (var preset in BmsDifficultyTableIndex.Presets) + { + if (FindByUrl(preset.HeaderUrl) != null) + continue; + _tables.Add(new BmsDifficultyTableDefinition + { + Id = NormalizeId(preset.Id), + DisplayName = preset.DisplayName, + HeaderUrl = NormalizeUrl(preset.HeaderUrl), + AddedUtcTicks = DateTime.UtcNow.Ticks, + }); + changed = true; + } + if (changed) + Save(); + } + + private void PromoteLegacyCache(string packId, BmsDifficultyTableDefinition table) + { + if (string.IsNullOrWhiteSpace(packId) || table == null) + return; + var legacyRoot = Path.Combine(_cacheRoot, packId); + if (!Directory.Exists(legacyRoot)) + return; + var targetRoot = Path.Combine(_cacheRoot, NormalizeId(table.Id)); + Directory.CreateDirectory(targetRoot); + CopyIfMissing( + Path.Combine(legacyRoot, "header.json"), + Path.Combine(targetRoot, "header.json")); + CopyIfMissing( + Path.Combine(legacyRoot, "data.json"), + Path.Combine(targetRoot, "data.json")); + } + + private static void CopyIfMissing(string sourcePath, string destinationPath) + { + if (!File.Exists(sourcePath) || File.Exists(destinationPath)) + return; + File.Copy(sourcePath, destinationPath, overwrite: false); + } + + private void Load() + { + _tables.Clear(); + if (!_file.TryLoad(out var document, out _)) + return; + foreach (var table in document.Tables) + { + if (table == null || string.IsNullOrWhiteSpace(table.Id) || + string.IsNullOrWhiteSpace(table.HeaderUrl)) + continue; + try + { + table.Id = NormalizeId(table.Id); + table.HeaderUrl = NormalizeUrl(table.HeaderUrl); + } + catch + { + continue; + } + table.DisplayName = string.IsNullOrWhiteSpace(table.DisplayName) + ? table.Id + : table.DisplayName.Trim(); + table.LegacyProviderIds ??= new List(); + if (_tables.All(existing => + !string.Equals(existing.Id, table.Id, StringComparison.OrdinalIgnoreCase) && + !string.Equals(existing.HeaderUrl, table.HeaderUrl, StringComparison.OrdinalIgnoreCase))) + _tables.Add(table); + } + ActiveTableId = document.ActiveTableId ?? string.Empty; + if (Find(ActiveTableId) == null) + ActiveTableId = string.Empty; + } + + private void Save() + { + _file.Save(new BmsDifficultyTableRegistryDocument + { + ActiveTableId = ActiveTableId, + Tables = new List(_tables), + }); + } + + private static BmsDifficultyTablePreset? FindPresetByUrl(string url) + { + foreach (var preset in BmsDifficultyTableIndex.Presets) + if (string.Equals(preset.HeaderUrl, url, StringComparison.OrdinalIgnoreCase)) + return preset; + return null; + } + + private static string ResolveDisplayName( + string displayName, + BmsDifficultyTablePreset? preset, + string fallback) + { + if (!string.IsNullOrWhiteSpace(displayName)) + return displayName.Trim(); + if (preset.HasValue) + return preset.Value.DisplayName; + return string.IsNullOrWhiteSpace(fallback) ? "BMS TABLE" : fallback; + } + + private static string NormalizeUrl(string headerUrl) + { + var value = (headerUrl ?? string.Empty).Trim(); + if (!Uri.TryCreate(value, UriKind.Absolute, out var uri) || + uri.Scheme != Uri.UriSchemeHttps) + throw new ArgumentException("Difficulty table URL must use HTTPS.", nameof(headerUrl)); + return uri.AbsoluteUri; + } + + private static string NormalizeId(string tableId) + { + var value = (tableId ?? string.Empty).Trim().ToLowerInvariant(); + if (value.Length == 0) + throw new ArgumentException("A difficulty-table ID is required.", nameof(tableId)); + var builder = new StringBuilder(value.Length); + foreach (var character in value) + { + if (char.IsLetterOrDigit(character) || character == '-' || character == '_') + builder.Append(character); + else + builder.Append('-'); + } + return builder.ToString().Trim('-'); + } + + private static string BuildStableId(string url) + { + using var sha = SHA256.Create(); + var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(url.ToUpperInvariant())); + var builder = new StringBuilder("table-"); + for (var index = 0; index < 10; index++) + builder.Append(hash[index].ToString("x2")); + return builder.ToString(); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableRegistry.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableRegistry.cs.meta new file mode 100644 index 0000000..01a49b9 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsDifficultyTableRegistry.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a0092f256d394498896e08c52ff58b15 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsLibraryLocator.cs b/Assets/ProjectQuint/Runtime/Unity/Library/BmsLibraryLocator.cs new file mode 100644 index 0000000..a2cb09f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsLibraryLocator.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; + +namespace ProjectQuint.Library +{ + internal static class BmsLibraryLocator + { + private static readonly HashSet ChartExtensions = + new HashSet(new[] { ".bms", ".bme", ".bml" }, StringComparer.OrdinalIgnoreCase); + + public static string Find(string configuredRoot, string applicationDataPath) + { + if (!string.IsNullOrWhiteSpace(configuredRoot) && Directory.Exists(configuredRoot)) + return Path.GetFullPath(configuredRoot); + + var directory = string.IsNullOrWhiteSpace(applicationDataPath) + ? null + : new DirectoryInfo(Path.GetFullPath(applicationDataPath)); + for (var depth = 0; directory != null && depth < 10; depth++, directory = directory.Parent) + { + foreach (var conventionalName in new[] { "BMS", "BMS-Library", "Songs" }) + { + var conventionalRoot = Path.Combine(directory.FullName, conventionalName); + if (ContainsChart(conventionalRoot)) + return Path.GetFullPath(conventionalRoot); + } + + var discovered = DiscoverChartDirectories(directory.FullName); + if (discovered.Count == 1) + return discovered[0]; + if (discovered.Count > 1) + return directory.FullName; + } + return string.Empty; + } + + internal static bool ContainsChart(string root) => ContainsChart(root, CancellationToken.None); + + internal static bool ContainsChart(string root, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root)) + return false; + try + { + const int maximumDepth = 6; + const int maximumDirectories = 10000; + var pending = new Queue<(string Path, int Depth)>(); + pending.Enqueue((root, 0)); + var visited = 0; + while (pending.Count > 0 && visited++ < maximumDirectories) + { + cancellationToken.ThrowIfCancellationRequested(); + var current = pending.Dequeue(); + foreach (var path in Directory.EnumerateFiles(current.Path, "*", SearchOption.TopDirectoryOnly)) + { + cancellationToken.ThrowIfCancellationRequested(); + if (ChartExtensions.Contains(Path.GetExtension(path))) + return true; + } + if (current.Depth >= maximumDepth) + continue; + + foreach (var child in Directory.EnumerateDirectories(current.Path)) + { + cancellationToken.ThrowIfCancellationRequested(); + if (!ShouldSkipTraversal(child)) + pending.Enqueue((child, current.Depth + 1)); + } + } + return false; + } + catch (OperationCanceledException) + { + throw; + } + catch + { + return false; + } + } + + private static List DiscoverChartDirectories(string root) + { + try + { + return Directory.EnumerateDirectories(root) + .Where(path => !LooksLikeDevelopmentOutput(path)) + .Where(ContainsChart) + .Select(Path.GetFullPath) + .ToList(); + } + catch + { + return new List(); + } + } + + private static bool LooksLikeDevelopmentOutput(string path) + { + var name = Path.GetFileName(path) ?? string.Empty; + return name.StartsWith(".", StringComparison.Ordinal) || + name.EndsWith("Builds", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(".app", StringComparison.OrdinalIgnoreCase) || + Directory.Exists(Path.Combine(path, ".git")) || + Directory.Exists(Path.Combine(path, "ProjectSettings")) && + Directory.Exists(Path.Combine(path, "Assets")); + } + + private static bool ShouldSkipTraversal(string path) + { + var name = Path.GetFileName(path) ?? string.Empty; + return name.StartsWith(".", StringComparison.Ordinal) || + name.Equals("Library", StringComparison.OrdinalIgnoreCase) || + name.Equals("Temp", StringComparison.OrdinalIgnoreCase) || + name.Equals("Logs", StringComparison.OrdinalIgnoreCase) || + name.Equals("obj", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(".app", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsLibraryLocator.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/BmsLibraryLocator.cs.meta new file mode 100644 index 0000000..05fc21d --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsLibraryLocator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ae68d8275950a4e52a161f5f23890b5b \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackImportService.cs b/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackImportService.cs new file mode 100644 index 0000000..c54f688 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackImportService.cs @@ -0,0 +1,521 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Text; +using System.Threading; + +namespace ProjectQuint.Library +{ + internal readonly struct BmsPackImportInspection + { + public BmsPackImportInspection( + string sourcePath, + string destinationPath, + int totalFiles, + long extractedBytes, + long availableBytes) + { + SourcePath = sourcePath ?? string.Empty; + DestinationPath = destinationPath ?? string.Empty; + TotalFiles = totalFiles; + ExtractedBytes = extractedBytes; + AvailableBytes = availableBytes; + } + + public string SourcePath { get; } + public string DestinationPath { get; } + public int TotalFiles { get; } + public long ExtractedBytes { get; } + public long AvailableBytes { get; } + public bool HasEnoughSpace => AvailableBytes >= ExtractedBytes; + } + + internal sealed class BmsPackImportProgress + { + private readonly object _gate = new object(); + private int _completedFiles; + private int _totalFiles; + private long _completedBytes; + private long _totalBytes; + private string _currentEntry = string.Empty; + + public void Report(int completedFiles, int totalFiles, long completedBytes, long totalBytes, string currentEntry) + { + lock (_gate) + { + _completedFiles = completedFiles; + _totalFiles = totalFiles; + _completedBytes = completedBytes; + _totalBytes = totalBytes; + _currentEntry = currentEntry ?? string.Empty; + } + } + + public BmsPackImportProgressSnapshot Snapshot() + { + lock (_gate) + return new BmsPackImportProgressSnapshot( + _completedFiles, + _totalFiles, + _completedBytes, + _totalBytes, + _currentEntry); + } + } + + internal readonly struct BmsPackImportProgressSnapshot + { + public BmsPackImportProgressSnapshot( + int completedFiles, + int totalFiles, + long completedBytes, + long totalBytes, + string currentEntry) + { + CompletedFiles = completedFiles; + TotalFiles = totalFiles; + CompletedBytes = completedBytes; + TotalBytes = totalBytes; + CurrentEntry = currentEntry ?? string.Empty; + } + + public int CompletedFiles { get; } + public int TotalFiles { get; } + public long CompletedBytes { get; } + public long TotalBytes { get; } + public string CurrentEntry { get; } + } + + internal static class BmsPackImportService + { + internal const string CompletionMarkerFileName = ".projectquint-import-complete"; + private static readonly Encoding[] EntryNameEncodingCandidates = CreateEntryNameEncodingCandidates(); + + public static BmsPackImportInspection InspectAdjacentZip(string zipPath) + { + ValidateZipPath(zipPath); + var source = Path.GetFullPath(zipPath); + var destination = BuildAdjacentExtractionPath(source); + using var stream = File.Open(source, FileMode.Open, FileAccess.Read, FileShare.Read); + var entryNameEncoding = DetectEntryNameEncoding(stream); + stream.Position = 0; + using var archive = new ZipArchive( + stream, + ZipArchiveMode.Read, + leaveOpen: false, + entryNameEncoding: entryNameEncoding); + CountArchive(archive, out var totalFiles, out var totalBytes); + var destinationParent = Path.GetDirectoryName(destination) ?? source; + var availableBytes = new DriveInfo(Path.GetPathRoot(destinationParent) ?? destinationParent) + .AvailableFreeSpace; + return new BmsPackImportInspection( + source, + destination, + totalFiles, + totalBytes, + availableBytes); + } + + public static string ExtractZipBesideArchive( + string zipPath, + BmsPackImportProgress progress, + CancellationToken cancellationToken) + { + ValidateZipPath(zipPath); + var source = Path.GetFullPath(zipPath); + var destination = BuildAdjacentExtractionPath(source); + if (IsCompletedManagedExtraction(destination)) + return destination; + if (Directory.Exists(destination)) + throw new IOException( + $"The extraction folder already exists and is not a completed ProjectQuint import: {destination}"); + + return ExtractZipToDestination( + source, + destination, + progress, + cancellationToken, + writeCompletionMarker: true); + } + + public static string ExtractZip( + string zipPath, + string importedPackRoot, + BmsPackImportProgress progress, + CancellationToken cancellationToken) + { + ValidateZipPath(zipPath); + var source = Path.GetFullPath(zipPath); + var destination = Path.Combine( + Path.GetFullPath(importedPackRoot), + BmsPackRegistry.BuildStableId(source)); + if (BmsLibraryLocator.ContainsChart(destination)) + return destination; + if (Directory.Exists(destination)) + Directory.Delete(destination, true); + + Directory.CreateDirectory(importedPackRoot); + return ExtractZipToDestination( + source, + destination, + progress, + cancellationToken, + writeCompletionMarker: false); + } + + public static string BuildAdjacentExtractionPath(string zipPath) + { + if (string.IsNullOrWhiteSpace(zipPath) || + !string.Equals(Path.GetExtension(zipPath), ".zip", StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException("A .zip source path is required."); + var source = Path.GetFullPath(zipPath); + var directory = Path.GetDirectoryName(source); + var name = Path.GetFileNameWithoutExtension(source).Trim(); + if (string.IsNullOrWhiteSpace(directory) || string.IsNullOrWhiteSpace(name)) + throw new InvalidDataException("The ZIP path cannot produce an extraction folder."); + return Path.Combine(directory, name); + } + + public static bool IsCompletedManagedExtraction(string path) => + !string.IsNullOrWhiteSpace(path) && + Directory.Exists(path) && + File.Exists(Path.Combine(path, CompletionMarkerFileName)) && + BmsLibraryLocator.ContainsChart(path); + + private static string ExtractZipToDestination( + string source, + string destination, + BmsPackImportProgress progress, + CancellationToken cancellationToken, + bool writeCompletionMarker) + { + var destinationParent = Path.GetDirectoryName(destination); + if (string.IsNullOrWhiteSpace(destinationParent)) + throw new InvalidDataException("The ZIP extraction destination has no parent folder."); + Directory.CreateDirectory(destinationParent); + var staging = destination + ".importing-" + Guid.NewGuid().ToString("N"); + Directory.CreateDirectory(staging); + var stagingPrefix = Path.GetFullPath(staging) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; + + try + { + using var stream = File.Open(source, FileMode.Open, FileAccess.Read, FileShare.Read); + // Legacy BMS archives are commonly repacked on either Japanese or Korean + // Windows. Detect one lossless encoding for every unflagged central-directory + // name instead of replacing invalid bytes and breaking keysound references. + var entryNameEncoding = DetectEntryNameEncoding(stream); + stream.Position = 0; + using var archive = new ZipArchive( + stream, + ZipArchiveMode.Read, + leaveOpen: false, + entryNameEncoding: entryNameEncoding); + CountArchive(archive, out var totalFiles, out var totalBytes); + + var completedFiles = 0; + var completedBytes = 0L; + var copyBuffer = new byte[1024 * 1024]; + progress?.Report(0, totalFiles, 0, totalBytes, string.Empty); + foreach (var entry in archive.Entries) + { + cancellationToken.ThrowIfCancellationRequested(); + var outputPath = Path.GetFullPath(Path.Combine(staging, entry.FullName)); + if (!outputPath.StartsWith(stagingPrefix, StringComparison.Ordinal)) + throw new InvalidDataException($"Unsafe ZIP entry path: {entry.FullName}"); + + if (string.IsNullOrEmpty(entry.Name)) + { + Directory.CreateDirectory(outputPath); + continue; + } + + var parent = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrWhiteSpace(parent)) + Directory.CreateDirectory(parent); + using (var input = entry.Open()) + using (var output = new FileStream(outputPath, FileMode.CreateNew, FileAccess.Write, FileShare.None)) + { + int read; + while ((read = input.Read(copyBuffer, 0, copyBuffer.Length)) > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + output.Write(copyBuffer, 0, read); + completedBytes += read; + progress?.Report( + completedFiles, + totalFiles, + completedBytes, + totalBytes, + entry.FullName); + } + } + + completedFiles++; + progress?.Report( + completedFiles, + totalFiles, + completedBytes, + totalBytes, + entry.FullName); + } + + cancellationToken.ThrowIfCancellationRequested(); + if (!BmsLibraryLocator.ContainsChart(staging)) + throw new InvalidDataException("The selected ZIP does not contain BMS/BME/BML charts."); + if (writeCompletionMarker) + File.WriteAllText( + Path.Combine(staging, CompletionMarkerFileName), + $"source={source}{Environment.NewLine}completedUtc={DateTime.UtcNow:O}{Environment.NewLine}"); + if (Directory.Exists(destination)) + throw new IOException($"An incomplete imported pack already exists: {destination}"); + Directory.Move(staging, destination); + return destination; + } + catch + { + if (Directory.Exists(staging)) + Directory.Delete(staging, true); + throw; + } + } + + private static void ValidateZipPath(string zipPath) + { + if (string.IsNullOrWhiteSpace(zipPath) || !File.Exists(zipPath)) + throw new FileNotFoundException("BMS pack ZIP not found.", zipPath); + if (!string.Equals(Path.GetExtension(zipPath), ".zip", StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException("Only .zip BMS pack archives can be imported."); + } + + private static void CountArchive( + ZipArchive archive, + out int totalFiles, + out long totalBytes) + { + totalFiles = 0; + totalBytes = 0L; + foreach (var entry in archive.Entries) + { + if (string.IsNullOrEmpty(entry.Name)) + continue; + totalFiles++; + totalBytes += Math.Max(0L, entry.Length); + } + } + + private static Encoding DetectEntryNameEncoding(Stream stream) + { + Exception lastError = null; + Encoding bestEncoding = null; + var bestPenalty = long.MaxValue; + foreach (var encoding in EntryNameEncodingCandidates) + { + stream.Position = 0; + try + { + using var candidate = new ZipArchive( + stream, + ZipArchiveMode.Read, + leaveOpen: true, + entryNameEncoding: encoding); + var penalty = 0L; + foreach (var entry in candidate.Entries) + penalty += SuspiciousUnicodePenalty(entry.FullName); + if (penalty < bestPenalty) + { + bestEncoding = encoding; + bestPenalty = penalty; + } + } + catch (DecoderFallbackException exception) + { + lastError = exception; + } + catch (EncoderFallbackException exception) + { + lastError = exception; + } + } + + if (bestEncoding != null) + return bestEncoding; + + throw new InvalidDataException( + "ZIP filenames are not valid UTF-8, Japanese Windows (CP932), or Korean Windows (CP949). " + + "Repack the archive with UTF-8 filenames before importing.", + lastError); + } + + private static int SuspiciousUnicodePenalty(string value) + { + var penalty = 0; + foreach (var character in value ?? string.Empty) + { + if (character == '\uFFFD') + penalty += 10000; + else if (character == '?') + // Unity Mono's legacy code-page decoder can emit '?' even when configured + // with ExceptionFallback, so treat it as a lossy-decoding signal. + penalty += 1000; + else if (character >= '\uE000' && character <= '\uF8FF') + penalty += 1000; + else if (char.IsSurrogate(character)) + penalty += 10000; + else if (char.IsControl(character)) + penalty += 100; + } + return penalty; + } + + private static Encoding[] CreateEntryNameEncodingCandidates() + { + try + { + var providerType = Type.GetType( + "System.Text.CodePagesEncodingProvider, System.Text.Encoding.CodePages", + throwOnError: false); + var provider = providerType?.GetProperty("Instance")?.GetValue(null) as EncodingProvider; + if (provider != null) + Encoding.RegisterProvider(provider); + } + catch + { + // Unity's Mono runtime exposes Windows code pages directly. Registration is + // needed only on runtimes that ship them in System.Text.Encoding.CodePages. + } + + return new[] + { + new UTF8Encoding(false, true), + Encoding.GetEncoding( + 932, + EncoderFallback.ReplacementFallback, + DecoderFallback.ExceptionFallback), + Encoding.GetEncoding( + 949, + EncoderFallback.ReplacementFallback, + DecoderFallback.ExceptionFallback), + }; + } + } + + internal static class NativeBmsPackPicker + { + public static string LastError { get; private set; } = string.Empty; + + public static string ChooseFolder() => RunAppleScript( + "POSIX path of (choose folder with prompt \"Select a BMS pack folder\")"); + + public static string ChooseZip() => RunAppleScript( + "POSIX path of (choose file with prompt \"Select a BMS pack ZIP\")"); + + public static bool ConfirmZipImport(BmsPackImportInspection inspection) + { + var message = + $"Archive: {Path.GetFileName(inspection.SourcePath)}\n" + + $"Extracted size: {FormatGigabytes(inspection.ExtractedBytes)}\n" + + $"Available space: {FormatGigabytes(inspection.AvailableBytes)}\n" + + $"Destination: {inspection.DestinationPath}\n\n" + + "The original ZIP will be kept. Delete it manually after verifying the pack."; + var result = RunAppleScript( + "button returned of (display dialog \"" + + EscapeAppleScript(message) + + "\" with title \"Import BMS Pack\" buttons {\"Cancel\", \"Import\"} " + + "default button \"Import\" cancel button \"Cancel\")"); + return string.Equals(result, "Import", StringComparison.Ordinal); + } + + public static string ChoosePackDisplayName(string currentName) => RunAppleScript( + "text returned of (display dialog \"Rename BMS pack\" default answer \"" + + EscapeAppleScript(currentName) + + "\" buttons {\"Cancel\", \"Rename\"} default button \"Rename\" cancel button \"Cancel\")"); + + internal static string RunAppleScript(string script) + { +#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX + LastError = string.Empty; + var scriptPath = Path.Combine( + Path.GetTempPath(), + "ProjectQuint-picker-" + Guid.NewGuid().ToString("N") + ".applescript"); + try + { + File.WriteAllText(scriptPath, script); + using var process = Process.Start(new ProcessStartInfo + { + FileName = "/usr/bin/osascript", + Arguments = "\"" + scriptPath.Replace("\"", "\\\"") + "\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8, + CreateNoWindow = true, + }); + if (process == null) + { + LastError = "Could not start the macOS file picker process."; + return string.Empty; + } + var outputTask = process.StandardOutput.ReadToEndAsync(); + var errorTask = process.StandardError.ReadToEndAsync(); + process.WaitForExit(); + System.Threading.Tasks.Task.WaitAll(outputTask, errorTask); + if (process.ExitCode == 0) + return outputTask.Result.Trim(); + + var error = errorTask.Result?.Trim() ?? string.Empty; + if (error.IndexOf("-128", StringComparison.Ordinal) < 0 && + error.IndexOf("cancel", StringComparison.OrdinalIgnoreCase) < 0) + LastError = string.IsNullOrWhiteSpace(error) + ? $"macOS file picker exited with code {process.ExitCode}." + : error; + return string.Empty; + } + catch (Exception exception) + { + LastError = exception.Message; + return string.Empty; + } + finally + { + if (File.Exists(scriptPath)) + File.Delete(scriptPath); + } +#else + return string.Empty; +#endif + } + + private static string EscapeAppleScript(string value) => (value ?? string.Empty) + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\r", string.Empty) + .Replace("\n", "\" & return & \""); + + private static string FormatGigabytes(long bytes) => + $"{Math.Max(0L, bytes) / 1_000_000_000.0:0.0} GB"; + } + + internal static class NativeProfileArchivePicker + { + public static string ChooseExportPath(string defaultName) => NativeBmsPackPicker.RunAppleScript( + "POSIX path of (choose file name with prompt \"Export ProjectQuint profile\" default name \"" + + EscapeAppleScript(defaultName) + "\")"); + + public static string ChooseImportPath() => NativeBmsPackPicker.RunAppleScript( + "POSIX path of (choose file with prompt \"Select a ProjectQuint profile archive\")"); + + private static string EscapeAppleScript(string value) => (value ?? string.Empty) + .Replace("\\", "\\\\") + .Replace("\"", "\\\""); + } + + internal static class NativeProfileImagePicker + { + public static string ChooseImagePath() => NativeBmsPackPicker.RunAppleScript( + "POSIX path of (choose file with prompt \"Select a ProjectQuint profile photo\" " + + "of type {\"public.png\", \"public.jpeg\"})"); + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackImportService.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackImportService.cs.meta new file mode 100644 index 0000000..024bce7 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackImportService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8f8a5333b1f25481dbb2b16e265a1f7d \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackRegistry.cs b/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackRegistry.cs new file mode 100644 index 0000000..7e1f556 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackRegistry.cs @@ -0,0 +1,650 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using ProjectQuint.Persistence; + +namespace ProjectQuint.Library +{ + public enum BmsPackSourceKind + { + Folder, + ExtractedZip, + } + + [Serializable] + public sealed class BmsPackDefinition + { + public string Id = string.Empty; + public string DisplayName = string.Empty; + public string ContentRoot = string.Empty; + public string SourcePath = string.Empty; + public BmsPackSourceKind SourceKind; + // Schema-v1 migration seam only. New table registrations live in the global + // BmsDifficultyTableRegistry and this value is cleared after successful promotion. + public string DifficultyTableUrl = string.Empty; + // Schema-v2 pack-scoped reference. Table definitions and caches remain global; + // each pack independently selects which registered table annotates its charts. + public string DifficultyTableId = string.Empty; + // Schema-v3 music-select resume state. Search focus/query and open modal state are + // intentionally transient; these fields restore only the useful pack-scoped view. + public List SelectedLevelFilters = new List(); + public string LastSelectedChartMd5 = string.Empty; + public int LibrarySortMode; + public bool LibraryFavoritesOnly; + public int ForcedKeyCount; + public long AddedUtcTicks; + + public bool IsAvailable => !string.IsNullOrWhiteSpace(ContentRoot) && Directory.Exists(ContentRoot); + public string KeyModeLabel => ForcedKeyCount >= 4 && ForcedKeyCount <= 7 + ? $"{ForcedKeyCount}K" + : "AUTO"; + } + + [Serializable] + internal sealed class BmsPackRegistryDocument + { + public const int CurrentSchemaVersion = 3; + + public int SchemaVersion = CurrentSchemaVersion; + public string ActivePackId = string.Empty; + public List Packs = new List(); + } + + internal sealed class BmsPackRegistry + { + private readonly AtomicJsonFile _file; + private readonly string _packIndexRoot; + private readonly string _legacyImportedPackRoot; + private readonly Action _logWarning; + private readonly List _packs = new List(); + + public BmsPackRegistry( + string path = null, + Action logWarning = null, + string packIndexRoot = null, + string importedPackRoot = null) + { + _packIndexRoot = Path.GetFullPath(packIndexRoot ?? PersistencePaths.PackIndexRoot); + _legacyImportedPackRoot = + Path.GetFullPath(importedPackRoot ?? PersistencePaths.ImportedPackRoot); + _logWarning = logWarning; + _file = new AtomicJsonFile( + path ?? PersistencePaths.PackRegistry, + document => document != null && + document.SchemaVersion >= 1 && + document.SchemaVersion <= BmsPackRegistryDocument.CurrentSchemaVersion && + document.Packs != null, + logWarning); + Load(); + } + + public IReadOnlyList Packs => _packs; + public string ActivePackId { get; private set; } = string.Empty; + public BmsPackDefinition ActivePack => Find(ActivePackId); + public string LastRemovalWarning { get; private set; } = string.Empty; + + public BmsPackDefinition RegisterFolder( + string folderPath, + string displayName = null, + int forcedKeyCount = 0, + bool chartPresenceVerified = false) + { + if (string.IsNullOrWhiteSpace(folderPath)) + throw new ArgumentException("A BMS pack folder is required.", nameof(folderPath)); + + var root = Path.GetFullPath(folderPath); + if (!Directory.Exists(root)) + throw new DirectoryNotFoundException($"BMS pack folder not found: {root}"); + if (!chartPresenceVerified && !BmsLibraryLocator.ContainsChart(root)) + throw new InvalidDataException("The selected folder does not contain BMS/BME/BML charts."); + + return Register(new BmsPackDefinition + { + Id = BuildStableId(root), + DisplayName = ResolveDisplayName(displayName, root), + ContentRoot = root, + SourcePath = root, + SourceKind = BmsPackSourceKind.Folder, + ForcedKeyCount = NormalizeForcedKeyCount(forcedKeyCount), + AddedUtcTicks = DateTime.UtcNow.Ticks, + }); + } + + public BmsPackDefinition RegisterExtractedZip( + string zipPath, + string extractedRoot, + string displayName = null, + int forcedKeyCount = 0) + { + if (string.IsNullOrWhiteSpace(zipPath) || string.IsNullOrWhiteSpace(extractedRoot)) + throw new ArgumentException("ZIP source and extracted content root are required."); + + var source = Path.GetFullPath(zipPath); + var root = Path.GetFullPath(extractedRoot); + if (!File.Exists(source)) + throw new FileNotFoundException("BMS pack ZIP not found.", source); + if (!BmsLibraryLocator.ContainsChart(root)) + throw new InvalidDataException("The extracted ZIP does not contain BMS/BME/BML charts."); + + return Register(new BmsPackDefinition + { + Id = BuildStableId(source), + DisplayName = ResolveDisplayName(displayName, Path.GetFileNameWithoutExtension(source)), + ContentRoot = root, + SourcePath = source, + SourceKind = BmsPackSourceKind.ExtractedZip, + ForcedKeyCount = NormalizeForcedKeyCount(forcedKeyCount), + AddedUtcTicks = DateTime.UtcNow.Ticks, + }); + } + + public BmsPackDefinition MigrateLegacyFolder(string legacyRoot) + { + if (string.IsNullOrWhiteSpace(legacyRoot) || !Directory.Exists(legacyRoot)) + return null; + + var id = BuildStableId(Path.GetFullPath(legacyRoot)); + return Find(id) ?? RegisterFolder(legacyRoot); + } + + public void Select(string packId) + { + var pack = Find(packId) ?? throw new ArgumentException("Unknown BMS pack.", nameof(packId)); + ActivePackId = pack.Id; + Save(); + } + + public bool Unregister(string packId) + { + var pack = Find(packId); + if (pack == null) + return false; + + LastRemovalWarning = string.Empty; + var stagedOwnedContent = StageOwnedExtractedContentForRemoval(pack); + var removedIndex = _packs.IndexOf(pack); + var previousActivePackId = ActivePackId; + _packs.RemoveAt(removedIndex); + if (string.Equals(previousActivePackId, pack.Id, StringComparison.OrdinalIgnoreCase)) + { + ActivePackId = _packs.Count == 0 + ? string.Empty + : _packs[Math.Min(removedIndex, _packs.Count - 1)].Id; + } + + try + { + Save(); + } + catch + { + _packs.Insert(removedIndex, pack); + ActivePackId = previousActivePackId; + RestoreStagedOwnedContent(stagedOwnedContent, pack.ContentRoot); + throw; + } + + TryDeletePackIndex(pack); + DeleteStagedOwnedContent(stagedOwnedContent, pack); + return true; + } + + public BmsPackDefinition Rename(string packId, string displayName) + { + var pack = Find(packId) ?? throw new ArgumentException("Unknown BMS pack.", nameof(packId)); + var normalizedName = (displayName ?? string.Empty).Trim(); + if (normalizedName.Length == 0) + throw new ArgumentException("A pack display name is required.", nameof(displayName)); + if (normalizedName.Length > 80) + throw new ArgumentException("A pack display name cannot exceed 80 characters.", nameof(displayName)); + + var previousName = pack.DisplayName; + pack.DisplayName = normalizedName; + try + { + Save(); + } + catch + { + pack.DisplayName = previousName; + throw; + } + return pack; + } + + public BmsPackDefinition RelinkFolder(string packId, string folderPath) + { + var pack = Find(packId) ?? throw new ArgumentException("Unknown BMS pack.", nameof(packId)); + if (string.IsNullOrWhiteSpace(folderPath)) + throw new ArgumentException("A BMS pack folder is required.", nameof(folderPath)); + + var root = Path.GetFullPath(folderPath); + if (!Directory.Exists(root)) + throw new DirectoryNotFoundException($"BMS pack folder not found: {root}"); + if (!BmsLibraryLocator.ContainsChart(root)) + throw new InvalidDataException("The selected folder does not contain BMS/BME/BML charts."); + + InvalidateIndex(pack.Id); + var previousRoot = pack.ContentRoot; + var previousSourcePath = pack.SourcePath; + pack.ContentRoot = root; + if (pack.SourceKind == BmsPackSourceKind.Folder) + pack.SourcePath = root; + try + { + Save(); + } + catch + { + pack.ContentRoot = previousRoot; + pack.SourcePath = previousSourcePath; + throw; + } + return pack; + } + + public void InvalidateIndex(string packId) + { + var pack = Find(packId) ?? throw new ArgumentException("Unknown BMS pack.", nameof(packId)); + var directory = ResolvePackIndexDirectory(pack); + if (Directory.Exists(directory)) + Directory.Delete(directory, true); + } + + public int ClearLegacyDifficultyTableUrls() + { + var cleared = 0; + foreach (var pack in _packs) + { + if (string.IsNullOrWhiteSpace(pack.DifficultyTableUrl)) + continue; + pack.DifficultyTableUrl = string.Empty; + cleared++; + } + if (cleared > 0) + Save(); + return cleared; + } + + public BmsPackDefinition AssignDifficultyTable(string packId, string tableId) + { + var pack = Find(packId) ?? throw new ArgumentException("Unknown BMS pack.", nameof(packId)); + var normalizedTableId = (tableId ?? string.Empty).Trim(); + if (string.Equals( + pack.DifficultyTableId, + normalizedTableId, + StringComparison.OrdinalIgnoreCase)) + return pack; + + var previousTableId = pack.DifficultyTableId; + pack.DifficultyTableId = normalizedTableId; + try + { + Save(); + } + catch + { + pack.DifficultyTableId = previousTableId; + throw; + } + return pack; + } + + public BmsPackDefinition SetForcedKeyCount(string packId, int keyCount) + { + var pack = Find(packId) ?? throw new ArgumentException("Unknown BMS pack.", nameof(packId)); + var normalized = NormalizeForcedKeyCount(keyCount); + if (pack.ForcedKeyCount == normalized) + return pack; + + var previous = pack.ForcedKeyCount; + pack.ForcedKeyCount = normalized; + try + { + Save(); + InvalidateIndex(pack.Id); + } + catch + { + pack.ForcedKeyCount = previous; + Save(); + throw; + } + return pack; + } + + public int PromoteLegacyDifficultyTableAssignments(BmsDifficultyTableRegistry tableRegistry) + { + if (tableRegistry == null) + throw new ArgumentNullException(nameof(tableRegistry)); + + var promoted = 0; + // First recover schema-v1 URLs that have not yet been cleared. + foreach (var pack in _packs) + { + if (!string.IsNullOrWhiteSpace(pack.DifficultyTableId) || + string.IsNullOrWhiteSpace(pack.DifficultyTableUrl)) + continue; + var table = tableRegistry.FindByUrl(pack.DifficultyTableUrl); + if (table == null) + continue; + pack.DifficultyTableId = table.Id; + promoted++; + } + + // Earlier global-registry migrations preserved the former pack IDs on each + // table before clearing pack URLs. Use that durable provenance to restore + // per-pack assignments without guessing from folder or display names. + foreach (var table in tableRegistry.Tables) + { + foreach (var legacyPackId in table.LegacyProviderIds ?? new List()) + { + var pack = Find(legacyPackId); + if (pack == null || !string.IsNullOrWhiteSpace(pack.DifficultyTableId)) + continue; + pack.DifficultyTableId = table.Id; + promoted++; + } + } + if (promoted > 0) + Save(); + return promoted; + } + + public int ClearDifficultyTableAssignments(string tableId) + { + if (string.IsNullOrWhiteSpace(tableId)) + return 0; + + var cleared = 0; + foreach (var pack in _packs) + { + if (!string.Equals( + pack.DifficultyTableId, + tableId.Trim(), + StringComparison.OrdinalIgnoreCase)) + continue; + pack.DifficultyTableId = string.Empty; + cleared++; + } + if (cleared > 0) + Save(); + return cleared; + } + + public bool StageLibraryViewState( + string packId, + IReadOnlyCollection selectedLevelFilters, + SongSortMode sortMode, + bool favoritesOnly, + string selectedChartMd5) + { + var pack = Find(packId) ?? throw new ArgumentException("Unknown BMS pack.", nameof(packId)); + var normalizedFilters = NormalizeLevelFilters(selectedLevelFilters); + var normalizedSortMode = Enum.IsDefined(typeof(SongSortMode), sortMode) + ? sortMode + : SongSortMode.DifficultyAscending; + var normalizedChartMd5 = (selectedChartMd5 ?? string.Empty).Trim().ToLowerInvariant(); + if (LevelFiltersEqual(pack.SelectedLevelFilters, normalizedFilters) && + pack.LibrarySortMode == (int)normalizedSortMode && + pack.LibraryFavoritesOnly == favoritesOnly && + string.Equals( + pack.LastSelectedChartMd5, + normalizedChartMd5, + StringComparison.OrdinalIgnoreCase)) + return false; + + pack.SelectedLevelFilters = normalizedFilters; + pack.LibrarySortMode = (int)normalizedSortMode; + pack.LibraryFavoritesOnly = favoritesOnly; + pack.LastSelectedChartMd5 = normalizedChartMd5; + return true; + } + + public void FlushStagedChanges() => Save(); + + public BmsPackDefinition Find(string packId) + { + if (string.IsNullOrWhiteSpace(packId)) + return null; + return _packs.Find(pack => string.Equals(pack.Id, packId, StringComparison.OrdinalIgnoreCase)); + } + + public string CachePathFor(BmsPackDefinition pack) + { + if (pack == null || string.IsNullOrWhiteSpace(pack.Id)) + throw new ArgumentNullException(nameof(pack)); + return Path.Combine(ResolvePackIndexDirectory(pack), "song-index.json"); + } + + public static string BuildStableId(string sourcePath) + { + var normalized = Path.GetFullPath(sourcePath ?? string.Empty) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .ToUpperInvariant(); + using var sha = SHA256.Create(); + var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(normalized)); + var builder = new StringBuilder(20); + for (var index = 0; index < 10; index++) + builder.Append(hash[index].ToString("x2")); + return builder.ToString(); + } + + private BmsPackDefinition Register(BmsPackDefinition candidate) + { + var existing = Find(candidate.Id); + if (existing == null) + { + _packs.Add(candidate); + existing = candidate; + } + else + { + existing.DisplayName = candidate.DisplayName; + existing.ContentRoot = candidate.ContentRoot; + existing.SourcePath = candidate.SourcePath; + existing.SourceKind = candidate.SourceKind; + existing.ForcedKeyCount = candidate.ForcedKeyCount; + } + + ActivePackId = existing.Id; + Save(); + return existing; + } + + private void Load() + { + _packs.Clear(); + if (!_file.TryLoad(out var document, out _)) + return; + + var needsSchemaUpgrade = + document.SchemaVersion < BmsPackRegistryDocument.CurrentSchemaVersion; + foreach (var pack in document.Packs) + { + if (pack == null || string.IsNullOrWhiteSpace(pack.Id) || string.IsNullOrWhiteSpace(pack.ContentRoot)) + continue; + pack.ForcedKeyCount = NormalizeForcedKeyCount(pack.ForcedKeyCount); + pack.DisplayName = ResolveDisplayName(pack.DisplayName, pack.ContentRoot); + pack.DifficultyTableId = (pack.DifficultyTableId ?? string.Empty).Trim(); + pack.SelectedLevelFilters = NormalizeLevelFilters(pack.SelectedLevelFilters); + pack.LastSelectedChartMd5 = + (pack.LastSelectedChartMd5 ?? string.Empty).Trim().ToLowerInvariant(); + if (!Enum.IsDefined(typeof(SongSortMode), pack.LibrarySortMode)) + pack.LibrarySortMode = (int)SongSortMode.DifficultyAscending; + _packs.Add(pack); + } + ActivePackId = document.ActivePackId ?? string.Empty; + if (Find(ActivePackId) == null) + ActivePackId = string.Empty; + if (needsSchemaUpgrade) + Save(); + } + + private void Save() + { + _file.Save(new BmsPackRegistryDocument + { + ActivePackId = ActivePackId, + Packs = new List(_packs), + }); + } + + private string ResolvePackIndexDirectory(BmsPackDefinition pack) + { + var root = _packIndexRoot.TrimEnd( + Path.DirectorySeparatorChar, + Path.AltDirectorySeparatorChar); + var directory = Path.GetFullPath(Path.Combine(root, pack.Id)); + var rootPrefix = root + Path.DirectorySeparatorChar; + if (!directory.StartsWith(rootPrefix, StringComparison.Ordinal)) + throw new InvalidDataException("The pack index path escapes the cache root."); + return directory; + } + + private void TryDeletePackIndex(BmsPackDefinition pack) + { + try + { + var directory = ResolvePackIndexDirectory(pack); + if (Directory.Exists(directory)) + Directory.Delete(directory, true); + } + catch (Exception exception) + { + _logWarning?.Invoke( + $"BMS pack '{pack.DisplayName}' was unregistered, but its derived song index " + + $"could not be removed: {exception.Message}"); + } + } + + private string StageOwnedExtractedContentForRemoval(BmsPackDefinition pack) + { + if (pack?.SourceKind != BmsPackSourceKind.ExtractedZip || + string.IsNullOrWhiteSpace(pack.ContentRoot) || + !Directory.Exists(pack.ContentRoot)) + return string.Empty; + + var contentRoot = Path.GetFullPath(pack.ContentRoot); + var legacyOwned = IsPathWithinRoot(contentRoot, _legacyImportedPackRoot); + var adjacentOwned = false; + if (!string.IsNullOrWhiteSpace(pack.SourcePath)) + { + try + { + adjacentOwned = PathsEqual( + contentRoot, + BmsPackImportService.BuildAdjacentExtractionPath(pack.SourcePath)) && + File.Exists(Path.Combine( + contentRoot, + BmsPackImportService.CompletionMarkerFileName)); + } + catch (Exception) + { + adjacentOwned = false; + } + } + if (!legacyOwned && !adjacentOwned) + throw new InvalidDataException( + $"Refusing to delete an extracted pack outside its managed location: {contentRoot}"); + + var staged = contentRoot + ".removing-" + Guid.NewGuid().ToString("N"); + Directory.Move(contentRoot, staged); + return staged; + } + + private static void RestoreStagedOwnedContent(string stagedPath, string originalPath) + { + if (string.IsNullOrWhiteSpace(stagedPath) || !Directory.Exists(stagedPath)) + return; + if (Directory.Exists(originalPath)) + throw new IOException( + $"Could not restore removed pack content because the original path exists: {originalPath}"); + Directory.Move(stagedPath, originalPath); + } + + private void DeleteStagedOwnedContent(string stagedPath, BmsPackDefinition pack) + { + if (string.IsNullOrWhiteSpace(stagedPath) || !Directory.Exists(stagedPath)) + return; + try + { + Directory.Delete(stagedPath, true); + } + catch (Exception exception) + { + LastRemovalWarning = + $"Pack registration was removed, but managed song files could not be deleted: {exception.Message}"; + _logWarning?.Invoke( + $"BMS pack '{pack.DisplayName}' was unregistered, but its managed song files " + + $"could not be removed: {exception.Message}"); + } + } + + private static bool IsPathWithinRoot(string path, string root) + { + var normalizedPath = Path.GetFullPath(path) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + var normalizedRoot = Path.GetFullPath(root) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return normalizedPath.StartsWith( + normalizedRoot + Path.DirectorySeparatorChar, + StringComparison.OrdinalIgnoreCase); + } + + private static bool PathsEqual(string left, string right) => + string.Equals( + Path.GetFullPath(left ?? string.Empty) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), + Path.GetFullPath(right ?? string.Empty) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), + StringComparison.OrdinalIgnoreCase); + + private static string ResolveDisplayName(string preferred, string source) + { + if (!string.IsNullOrWhiteSpace(preferred)) + return preferred.Trim(); + var trimmed = (source ?? string.Empty) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + var name = Path.GetFileName(trimmed); + return string.IsNullOrWhiteSpace(name) ? "BMS PACK" : name; + } + + private static int NormalizeForcedKeyCount(int keyCount) => + keyCount >= 4 && keyCount <= 7 ? keyCount : 0; + + private static List NormalizeLevelFilters( + IReadOnlyCollection selectedLevelFilters) + { + var normalized = new List(); + foreach (var level in selectedLevelFilters ?? Array.Empty()) + { + var value = (level ?? string.Empty).Trim(); + if (value.Length == 0 || + normalized.Exists(existing => + string.Equals(existing, value, StringComparison.OrdinalIgnoreCase))) + continue; + normalized.Add(value); + } + return normalized; + } + + private static bool LevelFiltersEqual( + IReadOnlyList left, + IReadOnlyList right) + { + if ((left?.Count ?? 0) != (right?.Count ?? 0)) + return false; + for (var index = 0; index < (left?.Count ?? 0); index++) + if (!string.Equals( + left[index], + right[index], + StringComparison.OrdinalIgnoreCase)) + return false; + return true; + } + + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackRegistry.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackRegistry.cs.meta new file mode 100644 index 0000000..e41bba2 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsPackRegistry.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c11fd0e2ddcd948c0bc5ba843bbea2de \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsResourceResolver.cs b/Assets/ProjectQuint/Runtime/Unity/Library/BmsResourceResolver.cs new file mode 100644 index 0000000..93a9e53 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsResourceResolver.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; + +namespace ProjectQuint.Library +{ + public static class BmsResourceResolver + { + private static readonly string[] AudioExtensions = { ".ogg", ".wav", ".mp3" }; + private static readonly string[] VideoExtensions = + { ".mpg", ".mpeg", ".mp4", ".m4v", ".mov", ".webm", ".ogv", ".vp8", ".wmv", ".avi" }; + + public static string ResolveAudio(string chartPath, string declaredPath) => + Resolve(chartPath, declaredPath, AudioExtensions); + + public static string ResolveImage(string chartPath, string declaredPath) => + Resolve(chartPath, declaredPath, new[] { ".png", ".jpg", ".jpeg", ".bmp" }); + + public static string ResolveVideo(string chartPath, string declaredPath) => + Resolve(chartPath, declaredPath, VideoExtensions); + + private static string Resolve(string chartPath, string declaredPath, string[] replacementExtensions) + { + if (string.IsNullOrWhiteSpace(chartPath) || string.IsNullOrWhiteSpace(declaredPath) || + declaredPath.IndexOf('\0') >= 0 || Path.IsPathRooted(declaredPath)) + return null; + + var chartDirectory = Path.GetFullPath(Path.GetDirectoryName(chartPath) ?? string.Empty); + var normalized = declaredPath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); + var candidate = Path.GetFullPath(Path.Combine(chartDirectory, normalized)); + var allowedPrefix = chartDirectory.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; + if (!candidate.StartsWith(allowedPrefix, StringComparison.Ordinal)) + return null; + + if (File.Exists(candidate)) + return candidate; + + foreach (var extension in replacementExtensions) + { + var replacement = Path.ChangeExtension(candidate, extension); + if (File.Exists(replacement)) + return replacement; + + replacement = Path.ChangeExtension(candidate, extension.ToUpperInvariant()); + if (File.Exists(replacement)) + return replacement; + } + + return null; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/BmsResourceResolver.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/BmsResourceResolver.cs.meta new file mode 100644 index 0000000..a71c6b3 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/BmsResourceResolver.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2cc9cd9d313c742a0a492e9fb0205c08 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/ChartCatalog.cs b/Assets/ProjectQuint/Runtime/Unity/Library/ChartCatalog.cs new file mode 100644 index 0000000..37abae0 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/ChartCatalog.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Progression; + +namespace ProjectQuint.Library +{ + public enum ExternalDataOrigin + { + Unavailable, + Network, + Cache, + BundledSnapshot, + } + + public sealed class ChartCatalogEntry + { + public string DifficultyLabel { get; set; } = string.Empty; + public string DifficultyValue { get; set; } = string.Empty; + public int DifficultySortIndex { get; set; } = int.MaxValue; + public string TitleOverride { get; set; } = string.Empty; + public string ArtistOverride { get; set; } = string.Empty; + } + + public interface IChartCatalog + { + string ProviderId { get; } + string DisplayName { get; } + ExternalDataOrigin Origin { get; } + int Count { get; } + bool TryGet(string chartHash, out ChartCatalogEntry entry); + } + + public sealed class ChartCatalogAnnotation + { + public string ProviderId { get; set; } = string.Empty; + public string ProviderDisplayName { get; set; } = string.Empty; + public ExternalDataOrigin Origin { get; set; } + public string DifficultyLabel { get; set; } = string.Empty; + public string DifficultyValue { get; set; } = string.Empty; + public double ChartConstant { get; set; } + public int DifficultySortIndex { get; set; } = int.MaxValue; + public string TitleOverride { get; set; } = string.Empty; + public string ArtistOverride { get; set; } = string.Empty; + public int KeyCountOverride { get; set; } + + public bool HasDifficulty => !string.IsNullOrWhiteSpace(DifficultyLabel); + } + + public static class ChartCatalogAnnotator + { + public static int Apply(IReadOnlyList songs, IChartCatalog catalog) + { + var matched = 0; + var preset = catalog == null + ? null + : BmsDifficultyTableIndex.FindPreset(catalog.ProviderId); + var keyCountOverride = preset.HasValue && + preset.Value.DefaultKeyCount >= 4 && + preset.Value.DefaultKeyCount <= 7 + ? preset.Value.DefaultKeyCount + : 0; + foreach (var song in songs ?? Array.Empty()) + { + if (song == null) + continue; + + song.Catalog = new ChartCatalogAnnotation(); + var sha256 = song.Metadata?.Sha256; + var md5 = song.Metadata?.Md5; + if (catalog == null || + !TryGetByPreferredHash(catalog, sha256, md5, out var entry) || entry == null) + continue; + + song.Catalog = new ChartCatalogAnnotation + { + ProviderId = catalog.ProviderId, + ProviderDisplayName = catalog.DisplayName, + Origin = catalog.Origin, + DifficultyLabel = entry.DifficultyLabel ?? string.Empty, + DifficultyValue = entry.DifficultyValue ?? string.Empty, + ChartConstant = ResolveChartConstant(entry), + DifficultySortIndex = entry.DifficultySortIndex, + TitleOverride = entry.TitleOverride ?? string.Empty, + ArtistOverride = entry.ArtistOverride ?? string.Empty, + KeyCountOverride = keyCountOverride, + }; + matched++; + } + return matched; + } + + private static double ResolveChartConstant(ChartCatalogEntry entry) => + RatingRules.TryResolveChartConstant( + entry?.DifficultyValue, + entry?.DifficultyLabel, + out var chartConstant) + ? chartConstant + : 0.0; + + private static bool TryGetByPreferredHash( + IChartCatalog catalog, + string sha256, + string md5, + out ChartCatalogEntry entry) + { + if (!string.IsNullOrWhiteSpace(sha256) && catalog.TryGet(sha256, out entry)) + return true; + if (!string.IsNullOrWhiteSpace(md5) && catalog.TryGet(md5, out entry)) + return true; + + entry = null; + return false; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/ChartCatalog.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/ChartCatalog.cs.meta new file mode 100644 index 0000000..ab41f6f --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/ChartCatalog.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d9bc768902ea940a099cd353dfbc4f0f \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/ExternalJsonDataSource.cs b/Assets/ProjectQuint/Runtime/Unity/Library/ExternalJsonDataSource.cs new file mode 100644 index 0000000..eb93dc6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/ExternalJsonDataSource.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using UnityEngine.Networking; + +namespace ProjectQuint.Library +{ + internal sealed class ExternalDataResult + where TData : class + { + public TData Data { get; set; } + public ExternalDataOrigin Origin { get; set; } + public string Status { get; set; } = string.Empty; + public string RawJson { get; set; } = string.Empty; + } + + internal static class ExternalJsonDataSource + { + public static ExternalDataResult LoadLocal( + string displayName, + string cachePath, + string bundledJson, + Func parse) + where TData : class + { + return Select( + displayName, + networkJson: null, + ReadText(cachePath), + ReadText(cachePath + ".bak"), + bundledJson, + parse, + networkFailure: string.Empty); + } + + public static IEnumerator LoadOnline( + string displayName, + string url, + string cachePath, + string bundledJson, + Func parse, + Action> completed) + where TData : class + { + string networkJson = null; + string networkFailure = null; + if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || + uri.Scheme != Uri.UriSchemeHttps) + { + networkFailure = "HTTPS is required"; + } + else + { + using var request = UnityWebRequest.Get(uri.AbsoluteUri); + request.timeout = 10; + yield return request.SendWebRequest(); + if (request.result == UnityWebRequest.Result.Success) + networkJson = request.downloadHandler.text; + else + networkFailure = request.error; + } + + var selected = Select( + displayName, + networkJson, + ReadText(cachePath), + ReadText(cachePath + ".bak"), + bundledJson, + parse, + networkFailure); + if (selected.Origin == ExternalDataOrigin.Network) + { + try + { + WriteAtomically(cachePath, selected.RawJson); + } + catch (Exception exception) + { + selected.Status += $" (cache write failed: {exception.Message})"; + } + } + completed?.Invoke(selected); + } + + internal static ExternalDataResult Select( + string displayName, + string networkJson, + string cacheJson, + string backupJson, + string bundledJson, + Func parse, + string networkFailure = null) + where TData : class + { + if (parse == null) + throw new ArgumentNullException(nameof(parse)); + + var failures = new List(); + if (TryCandidate(networkJson, parse, out var data, out var failure)) + { + return Result(data, ExternalDataOrigin.Network, $"{displayName}: online", networkJson); + } + if (!string.IsNullOrWhiteSpace(networkJson)) + failures.Add("online data invalid: " + failure); + else if (!string.IsNullOrWhiteSpace(networkFailure)) + failures.Add("online request failed: " + networkFailure); + + if (TryCandidate(cacheJson, parse, out data, out failure)) + return Result(data, ExternalDataOrigin.Cache, $"{displayName}: cached", cacheJson); + if (!string.IsNullOrWhiteSpace(cacheJson)) + failures.Add("cache invalid: " + failure); + + if (TryCandidate(backupJson, parse, out data, out failure)) + return Result(data, ExternalDataOrigin.Cache, $"{displayName}: backup cache", backupJson); + if (!string.IsNullOrWhiteSpace(backupJson)) + failures.Add("backup cache invalid: " + failure); + + if (TryCandidate(bundledJson, parse, out data, out failure)) + return Result(data, ExternalDataOrigin.BundledSnapshot, $"{displayName}: bundled snapshot", bundledJson); + if (!string.IsNullOrWhiteSpace(bundledJson)) + failures.Add("bundled snapshot invalid: " + failure); + + return new ExternalDataResult + { + Origin = ExternalDataOrigin.Unavailable, + Status = failures.Count == 0 + ? $"{displayName}: unavailable" + : $"{displayName}: unavailable ({string.Join("; ", failures)})", + }; + } + + private static ExternalDataResult Result( + TData data, + ExternalDataOrigin origin, + string status, + string rawJson) + where TData : class => new ExternalDataResult + { + Data = data, + Origin = origin, + Status = status, + RawJson = rawJson ?? string.Empty, + }; + + private static bool TryCandidate( + string json, + Func parse, + out TData data, + out string failure) + where TData : class + { + data = null; + failure = "empty"; + if (string.IsNullOrWhiteSpace(json)) + return false; + + try + { + data = parse(json); + if (data == null) + throw new InvalidDataException("parser rejected the document"); + failure = string.Empty; + return true; + } + catch (Exception exception) + { + data = null; + failure = exception.Message; + return false; + } + } + + private static string ReadText(string path) + { + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + return null; + try + { + return File.ReadAllText(path, Encoding.UTF8); + } + catch + { + return null; + } + } + + internal static void WriteAtomically(string path, string json) + { + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directory)) + Directory.CreateDirectory(directory); + + var temporaryPath = path + ".tmp"; + if (File.Exists(temporaryPath)) + File.Delete(temporaryPath); + try + { + var bytes = new UTF8Encoding(false).GetBytes(json ?? string.Empty); + using (var stream = new FileStream( + temporaryPath, + FileMode.CreateNew, + FileAccess.Write, + FileShare.None, + 4096, + FileOptions.WriteThrough)) + { + stream.Write(bytes, 0, bytes.Length); + stream.Flush(true); + } + + if (File.Exists(path)) + File.Replace(temporaryPath, path, path + ".bak"); + else + File.Move(temporaryPath, path); + } + finally + { + if (File.Exists(temporaryPath)) + File.Delete(temporaryPath); + } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/ExternalJsonDataSource.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/ExternalJsonDataSource.cs.meta new file mode 100644 index 0000000..114078b --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/ExternalJsonDataSource.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3ed5633ca80714cd2882b3ed3cc0b0de \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/JapaneseSearchNormalizer.cs b/Assets/ProjectQuint/Runtime/Unity/Library/JapaneseSearchNormalizer.cs new file mode 100644 index 0000000..bfcb5ab --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/JapaneseSearchNormalizer.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace ProjectQuint.Library +{ + internal static class JapaneseSearchNormalizer + { + public static IReadOnlyList BuildIndexVariants(string value) + { + var variants = new List(3); + var unique = new HashSet(StringComparer.Ordinal); + AddVariant(unique, variants, NormalizeCompact(value)); + + var romanized = JapaneseKanaRomanizer.Romanize(value); + AddVariant(unique, variants, NormalizeCompact(romanized)); + if (ContainsJapaneseKana(value)) + { + var simplified = JapaneseKanaRomanizer.RomanizeWithSimplifiedLongVowels(value); + AddVariant(unique, variants, NormalizeCompact(simplified)); + } + return variants; + } + + public static IReadOnlyList BuildQueryVariants(string value) + { + var variants = new List(3); + var unique = new HashSet(StringComparer.Ordinal); + AddVariant(unique, variants, NormalizeCompact(value)); + + // Latin input is already the user's intended spelling. Applying lossy Japanese + // long-vowel rules here turned queries such as "aaaa" into the one-letter "a". + if (!ContainsJapaneseKana(value)) + return variants; + + var romanized = JapaneseKanaRomanizer.Romanize(value); + AddVariant(unique, variants, NormalizeCompact(romanized)); + var simplified = JapaneseKanaRomanizer.RomanizeWithSimplifiedLongVowels(value); + AddVariant(unique, variants, NormalizeCompact(simplified)); + return variants; + } + + public static string NormalizeCompact(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return string.Empty; + + var normalized = value.Normalize(NormalizationForm.FormKC) + .Normalize(NormalizationForm.FormD); + var builder = new StringBuilder(normalized.Length); + foreach (var character in normalized) + { + var category = CharUnicodeInfo.GetUnicodeCategory(character); + if (category == UnicodeCategory.NonSpacingMark || + category == UnicodeCategory.SpacingCombiningMark || + category == UnicodeCategory.EnclosingMark) + continue; + if (char.IsLetterOrDigit(character)) + builder.Append(char.ToLowerInvariant(character)); + } + return builder.ToString(); + } + + private static bool ContainsJapaneseKana(string value) + { + if (string.IsNullOrEmpty(value)) + return false; + foreach (var character in value) + if ((character >= '\u3040' && character <= '\u309f') || + (character >= '\u30a0' && character <= '\u30ff') || + character == '\u30fc') + return true; + return false; + } + + private static void AddVariant(ISet unique, ICollection variants, string value) + { + if (!string.IsNullOrEmpty(value) && unique.Add(value)) + variants.Add(value); + } + + } + + internal static class JapaneseKanaRomanizer + { + private static readonly IReadOnlyDictionary Digraphs = + new Dictionary(StringComparer.Ordinal) + { + ["きゃ"] = "kya", ["きゅ"] = "kyu", ["きょ"] = "kyo", + ["しゃ"] = "sha", ["しゅ"] = "shu", ["しょ"] = "sho", + ["ちゃ"] = "cha", ["ちゅ"] = "chu", ["ちょ"] = "cho", + ["にゃ"] = "nya", ["にゅ"] = "nyu", ["にょ"] = "nyo", + ["ひゃ"] = "hya", ["ひゅ"] = "hyu", ["ひょ"] = "hyo", + ["みゃ"] = "mya", ["みゅ"] = "myu", ["みょ"] = "myo", + ["りゃ"] = "rya", ["りゅ"] = "ryu", ["りょ"] = "ryo", + ["ぎゃ"] = "gya", ["ぎゅ"] = "gyu", ["ぎょ"] = "gyo", + ["じゃ"] = "ja", ["じゅ"] = "ju", ["じょ"] = "jo", + ["びゃ"] = "bya", ["びゅ"] = "byu", ["びょ"] = "byo", + ["ぴゃ"] = "pya", ["ぴゅ"] = "pyu", ["ぴょ"] = "pyo", + ["いぇ"] = "ye", ["うぃ"] = "wi", ["うぇ"] = "we", ["うぉ"] = "wo", + ["ゔぁ"] = "va", ["ゔぃ"] = "vi", ["ゔぇ"] = "ve", ["ゔぉ"] = "vo", + ["ゔゅ"] = "vyu", ["しぇ"] = "she", ["じぇ"] = "je", ["ちぇ"] = "che", + ["てぃ"] = "ti", ["てゅ"] = "tyu", ["でぃ"] = "di", ["でゅ"] = "dyu", + ["とぅ"] = "tu", ["どぅ"] = "du", ["すぃ"] = "si", ["ずぃ"] = "zi", + ["つぁ"] = "tsa", ["つぃ"] = "tsi", ["つぇ"] = "tse", ["つぉ"] = "tso", + ["ふぁ"] = "fa", ["ふぃ"] = "fi", ["ふぇ"] = "fe", ["ふぉ"] = "fo", + ["ふゅ"] = "fyu", ["くぁ"] = "kwa", ["くぃ"] = "kwi", + ["くぇ"] = "kwe", ["くぉ"] = "kwo", ["ぐぁ"] = "gwa", + ["ぐぃ"] = "gwi", ["ぐぇ"] = "gwe", ["ぐぉ"] = "gwo", + }; + + private static readonly IReadOnlyDictionary Monographs = + new Dictionary + { + ['あ'] = "a", ['い'] = "i", ['う'] = "u", ['え'] = "e", ['お'] = "o", + ['か'] = "ka", ['き'] = "ki", ['く'] = "ku", ['け'] = "ke", ['こ'] = "ko", + ['さ'] = "sa", ['し'] = "shi", ['す'] = "su", ['せ'] = "se", ['そ'] = "so", + ['た'] = "ta", ['ち'] = "chi", ['つ'] = "tsu", ['て'] = "te", ['と'] = "to", + ['な'] = "na", ['に'] = "ni", ['ぬ'] = "nu", ['ね'] = "ne", ['の'] = "no", + ['は'] = "ha", ['ひ'] = "hi", ['ふ'] = "fu", ['へ'] = "he", ['ほ'] = "ho", + ['ま'] = "ma", ['み'] = "mi", ['む'] = "mu", ['め'] = "me", ['も'] = "mo", + ['や'] = "ya", ['ゆ'] = "yu", ['よ'] = "yo", + ['ら'] = "ra", ['り'] = "ri", ['る'] = "ru", ['れ'] = "re", ['ろ'] = "ro", + ['わ'] = "wa", ['ゐ'] = "wi", ['ゑ'] = "we", ['を'] = "wo", ['ん'] = "n", + ['が'] = "ga", ['ぎ'] = "gi", ['ぐ'] = "gu", ['げ'] = "ge", ['ご'] = "go", + ['ざ'] = "za", ['じ'] = "ji", ['ず'] = "zu", ['ぜ'] = "ze", ['ぞ'] = "zo", + ['だ'] = "da", ['ぢ'] = "ji", ['づ'] = "zu", ['で'] = "de", ['ど'] = "do", + ['ば'] = "ba", ['び'] = "bi", ['ぶ'] = "bu", ['べ'] = "be", ['ぼ'] = "bo", + ['ぱ'] = "pa", ['ぴ'] = "pi", ['ぷ'] = "pu", ['ぺ'] = "pe", ['ぽ'] = "po", + ['ゔ'] = "vu", ['ぁ'] = "a", ['ぃ'] = "i", ['ぅ'] = "u", ['ぇ'] = "e", + ['ぉ'] = "o", ['ゃ'] = "ya", ['ゅ'] = "yu", ['ょ'] = "yo", + ['ゎ'] = "wa", ['ゕ'] = "ka", ['ゖ'] = "ke", + }; + + public static string Romanize(string value) => Romanize(value, simplifyLongVowels: false); + + public static string RomanizeWithSimplifiedLongVowels(string value) => + Romanize(value, simplifyLongVowels: true); + + private static string Romanize(string value, bool simplifyLongVowels) + { + if (string.IsNullOrEmpty(value)) + return string.Empty; + + var source = value.Normalize(NormalizationForm.FormKC); + var builder = new StringBuilder(source.Length * 2); + var geminate = false; + var previousKanaVowel = '\0'; + for (var index = 0; index < source.Length; index++) + { + var character = ToHiragana(source[index]); + if (character == 'っ') + { + geminate = true; + continue; + } + if (character == 'ー') + { + if (!simplifyLongVowels) + AppendLastVowel(builder); + continue; + } + if (simplifyLongVowels && + ((character == 'う' && previousKanaVowel == 'o') || + (character == 'い' && previousKanaVowel == 'e'))) + continue; + + string romanized = null; + if (index + 1 < source.Length) + { + var pair = string.Concat(character, ToHiragana(source[index + 1])); + if (Digraphs.TryGetValue(pair, out romanized)) + index++; + } + if (romanized == null) + Monographs.TryGetValue(character, out romanized); + + if (romanized == null) + { + if (geminate) + builder.Append("tsu"); + geminate = false; + builder.Append(source[index]); + previousKanaVowel = '\0'; + continue; + } + + if (geminate && romanized.Length > 0 && IsAsciiConsonant(romanized[0])) + builder.Append(romanized[0]); + geminate = false; + builder.Append(romanized); + previousKanaVowel = LastVowel(romanized); + } + if (geminate) + builder.Append("tsu"); + return builder.ToString(); + } + + private static char ToHiragana(char character) => + character >= '\u30A1' && character <= '\u30F6' + ? (char)(character - 0x60) + : character; + + private static void AppendLastVowel(StringBuilder builder) + { + for (var index = builder.Length - 1; index >= 0; index--) + { + var character = builder[index]; + if (character == 'a' || character == 'e' || character == 'i' || + character == 'o' || character == 'u') + { + builder.Append(character); + return; + } + if (!char.IsLetter(character)) + break; + } + } + + private static char LastVowel(string value) + { + for (var index = value.Length - 1; index >= 0; index--) + { + var character = value[index]; + if (character == 'a' || character == 'e' || character == 'i' || + character == 'o' || character == 'u') + return character; + } + return '\0'; + } + + private static bool IsAsciiConsonant(char character) => + character >= 'a' && character <= 'z' && + character != 'a' && character != 'e' && character != 'i' && + character != 'o' && character != 'u'; + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/JapaneseSearchNormalizer.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/JapaneseSearchNormalizer.cs.meta new file mode 100644 index 0000000..9741cb5 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/JapaneseSearchNormalizer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 20434ba50ac2f46b5b41bcfe956f9a46 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongClearLampStore.cs b/Assets/ProjectQuint/Runtime/Unity/Library/SongClearLampStore.cs new file mode 100644 index 0000000..2e3c5f9 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongClearLampStore.cs @@ -0,0 +1,41 @@ +using System; +using ProjectQuint.Core.Gauge; +using ProjectQuint.Persistence; + +namespace ProjectQuint.Library +{ + internal static class SongClearLampStore + { + private const string PreferencePrefix = "ProjectQuint.ClearLampV3."; + + public static SongClearLamp Load(SongLibraryEntry entry) + => SongRecordRepository.Shared.LoadClearLamp(entry); + + public static void Record(SongLibraryEntry entry, SongClearLamp lamp) + { + SongRecordRepository.Shared.RecordClearLamp(entry, lamp); + } + + internal static SongClearLamp Resolve( + GaugeMode gaugeType, + bool cleared, + bool autoScratch = false) + { + if (!cleared) + return gaugeType == GaugeMode.Normal + ? SongClearLamp.NormalFailed + : SongClearLamp.Failed; + if (autoScratch) + return SongClearLamp.AutoScratchClear; + return gaugeType switch + { + GaugeMode.ExHard => SongClearLamp.ExHardClear, + GaugeMode.Hard => SongClearLamp.HardClear, + _ => SongClearLamp.NormalClear, + }; + } + + internal static string BuildPreferenceKey(SongLibraryEntry entry) + => PreferencePrefix + ChartIdentityResolver.BuildLegacyPreferenceSuffix(entry); + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongClearLampStore.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/SongClearLampStore.cs.meta new file mode 100644 index 0000000..794661d --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongClearLampStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: aa52098a446d4b5d98d93f2d19d97a26 diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryScanner.cs b/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryScanner.cs new file mode 100644 index 0000000..57b2dd1 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryScanner.cs @@ -0,0 +1,765 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading; +using ProjectQuint.Core.Analysis; +using ProjectQuint.Core.Bms; +using ProjectQuint.Core.Identity; +using UnityEngine; + +namespace ProjectQuint.Library +{ + public enum SongClearLamp + { + None = 0, + NormalClear = 1, + HardClear = 2, + Failed = 3, + NormalFailed = 4, + ExHardClear = 5, + AutoScratchClear = 6, + } + + public sealed class SongLibraryEntry + { + public string ChartPath { get; set; } = string.Empty; + public string PackId { get; set; } = string.Empty; + public string ThumbnailFile { get; set; } = string.Empty; + public BmsMetadata Metadata { get; set; } = new BmsMetadata(); + public ChartCatalogAnnotation Catalog { get; set; } = new ChartCatalogAnnotation(); + public SongClearLamp ClearLamp { get; set; } + public SongPlayRecord BestRecord { get; set; } = new SongPlayRecord(); + public bool IsFavorite { get; set; } + public double DurationSeconds { get; set; } + public bool DurationIsEstimated { get; set; } + public ChartFeatureVector Features { get; set; } + public string FeatureAnalysisError { get; set; } = string.Empty; + public string SearchIndex { get; private set; } = string.Empty; + internal SongSearchDocument SearchDocument { get; private set; } = SongSearchDocument.Empty; + + public string DisplayTitle => !string.IsNullOrWhiteSpace(Catalog?.TitleOverride) + ? Catalog.TitleOverride + : Metadata?.Title ?? string.Empty; + public string DisplayArtist => !string.IsNullOrWhiteSpace(Catalog?.ArtistOverride) + ? Catalog.ArtistOverride + : Metadata?.Artist ?? string.Empty; + public string DifficultyLabel => Catalog?.HasDifficulty == true + ? Catalog.DifficultyLabel + : Metadata?.PlayLevel > 0 ? $"BMS LEVEL {Metadata.PlayLevel}" : string.Empty; + public string CompactDifficultyLabel => Catalog?.HasDifficulty == true + ? !string.IsNullOrWhiteSpace(Catalog.DifficultyValue) + ? Catalog.DifficultyValue + : Catalog.DifficultyLabel + : Metadata?.PlayLevel > 0 ? Metadata.PlayLevel.ToString() : string.Empty; + public int DifficultySortIndex => Catalog?.HasDifficulty == true + ? Catalog.DifficultySortIndex + : Metadata?.PlayLevel ?? int.MaxValue; + public bool HasCatalogDifficulty => Catalog?.HasDifficulty == true; + public string DifficultySourceLabel => HasCatalogDifficulty + ? Catalog.ProviderDisplayName + : Metadata?.PlayLevel > 0 ? "BMS PLAYLEVEL" : "UNRANKED"; + public int EffectiveKeyCount => + Catalog?.KeyCountOverride >= 4 && Catalog.KeyCountOverride <= 7 + ? Catalog.KeyCountOverride + : Metadata?.KeyCount ?? 5; + + internal void RebuildSearchIndex(SearchAliasIndex aliases = null) + { + SearchDocument = SongSearchIndexer.Build(this, aliases); + SearchIndex = SearchDocument.CombinedIndex; + } + } + + public sealed class SongLibraryScanner + { + private const int CacheVersion = 6; + private const int ThumbnailResolverVersion = 2; + private static readonly string[] ChartExtensions = { ".bms", ".bme", ".bml" }; + private static readonly HashSet ThumbnailImageExtensions = + new HashSet( + new[] { ".png", ".jpg", ".jpeg", ".bmp" }, + StringComparer.OrdinalIgnoreCase); + private static readonly string[] RejectedFallbackNameParts = + { + "poor", + "miss", + "layer", + "sprite", + "parts", + "font", + "judge", + "combo", + "lane", + "note", + }; + private int _completed; + private int _failed; + private int _total; + private int _cacheHits; + private int _cacheMisses; + private readonly string _cachePath; + private readonly string _legacyCachePath; + private readonly string _packId; + private readonly int _forcedKeyCount; + private int _loadedCacheVersion; + + public SongLibraryScanner( + string cachePath = null, + string legacyCachePath = null, + string packId = null, + int forcedKeyCount = 0) + { + _cachePath = cachePath; + _legacyCachePath = legacyCachePath; + _packId = packId ?? string.Empty; + _forcedKeyCount = forcedKeyCount; + } + + public int Completed => Volatile.Read(ref _completed); + public int Failed => Volatile.Read(ref _failed); + public int Total => Volatile.Read(ref _total); + public int CacheHits => Volatile.Read(ref _cacheHits); + public int CacheMisses => Volatile.Read(ref _cacheMisses); + public string CacheStatus { get; private set; } = string.Empty; + + internal static IReadOnlyList LoadCachedThumbnailEntries( + string cachePath, + string libraryRoot, + string packId, + IReadOnlyCollection requestedChartIds) + { + if (string.IsNullOrWhiteSpace(cachePath) || + string.IsNullOrWhiteSpace(libraryRoot) || + requestedChartIds == null || + requestedChartIds.Count == 0 || + !File.Exists(cachePath)) + return Array.Empty(); + + try + { + var document = JsonUtility.FromJson( + File.ReadAllText(cachePath)); + if (document == null || + document.Version < 1 || + document.Version > CacheVersion || + !string.Equals( + Path.GetFullPath(document.LibraryRoot ?? string.Empty), + Path.GetFullPath(libraryRoot), + StringComparison.OrdinalIgnoreCase)) + return Array.Empty(); + + var result = new List( + Math.Min(requestedChartIds.Count, document.Entries?.Length ?? 0)); + foreach (var cached in document.Entries ?? Array.Empty()) + { + if (cached == null || string.IsNullOrWhiteSpace(cached.ChartPath)) + continue; + var chartId = !string.IsNullOrWhiteSpace(cached.Md5) + ? ChartId.FromMd5(cached.Md5).Value + : ChartId.FromSha256(cached.Sha256).Value; + if (string.IsNullOrEmpty(chartId) || !requestedChartIds.Contains(chartId)) + continue; + var chartPath = Path.GetFullPath(cached.ChartPath); + result.Add(new SongLibraryEntry + { + ChartPath = chartPath, + PackId = packId ?? string.Empty, + ThumbnailFile = cached.ThumbnailFile ?? string.Empty, + Metadata = cached.ToMetadata(chartPath), + }); + } + return result; + } + catch (Exception exception) + { + Debug.LogWarning( + $"[ProjectQuint Library] Cached profile thumbnail index was ignored: {exception.Message}"); + return Array.Empty(); + } + } + + public List Scan(string libraryRoot, CancellationToken cancellationToken) + { + if (!Directory.Exists(libraryRoot)) + throw new DirectoryNotFoundException($"BMS library not found: {libraryRoot}"); + + Volatile.Write(ref _completed, 0); + Volatile.Write(ref _failed, 0); + Volatile.Write(ref _cacheHits, 0); + Volatile.Write(ref _cacheMisses, 0); + CacheStatus = string.Empty; + + var normalizedRoot = Path.GetFullPath(libraryRoot); + var cachedEntries = LoadCache(normalizedRoot); + + var paths = new List(); + foreach (var path in Directory.EnumerateFiles(normalizedRoot, "*", SearchOption.AllDirectories)) + { + cancellationToken.ThrowIfCancellationRequested(); + if (!ChartExtensions.Contains(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase)) + continue; + paths.Add(path); + // Publish discovery progress instead of displaying an apparently frozen 0/0 + // while a large pack tree is still being enumerated. + Volatile.Write(ref _total, paths.Count); + } + Volatile.Write(ref _total, paths.Count); + + var result = new List(paths.Count); + var refreshedCache = new List(paths.Count); + + foreach (var path in paths) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + var fullPath = Path.GetFullPath(path); + var file = new FileInfo(fullPath); + BmsMetadata rawMetadata; + ChartFeatureVector features; + string featureAnalysisError; + string thumbnailFile; + double durationSeconds; + bool durationIsEstimated; + var hasMatchingCache = cachedEntries.TryGetValue(fullPath, out var cached) && + cached.FileLength == file.Length && + cached.LastWriteUtcTicks == file.LastWriteTimeUtc.Ticks && + !string.IsNullOrWhiteSpace(cached.Md5) && + !string.IsNullOrWhiteSpace(cached.Sha256) && + cached.FeatureAnalyzerVersion == ChartFeatureVector.CurrentAnalyzerVersion; + if (hasMatchingCache && _loadedCacheVersion == CacheVersion) + { + rawMetadata = cached.ToMetadata(fullPath); + thumbnailFile = cached.ThumbnailFile ?? string.Empty; + if (cached.ThumbnailResolverVersion < ThumbnailResolverVersion && + string.IsNullOrWhiteSpace(thumbnailFile)) + { + try + { + var thumbnailDocument = BmsParser.ParseFile( + fullPath, + forcedKeyCount: _forcedKeyCount); + var thumbnailChart = BmsTimelineCompiler.Compile(thumbnailDocument); + thumbnailFile = SelectThumbnailFile( + fullPath, + rawMetadata, + thumbnailChart); + } + catch (Exception thumbnailException) + { + Debug.LogWarning( + $"[ProjectQuint Library] Thumbnail fallback scan failed for " + + $"'{fullPath}': {thumbnailException.Message}"); + } + } + BmsParser.EnsureSupportedLongNoteNotation(rawMetadata.LnType); + features = cached.Features; + featureAnalysisError = cached.FeatureAnalysisError ?? string.Empty; + durationSeconds = cached.DurationSeconds; + durationIsEstimated = false; + if (durationSeconds <= 0 && features?.ActiveDurationSeconds > 0) + { + durationSeconds = features.ActiveDurationSeconds; + durationIsEstimated = true; + } + if (features != null && + features.CalibrationVersion != ChartFeatureVector.CurrentCalibrationVersion) + { + ChartFeatureCalibration.Apply(features); + } + Interlocked.Increment(ref _cacheHits); + } + else + { + var document = BmsParser.ParseFile(fullPath, forcedKeyCount: _forcedKeyCount); + rawMetadata = document.Metadata; + var hashes = ComputeHashes(fullPath); + rawMetadata.Md5 = hashes.Md5; + rawMetadata.Sha256 = hashes.Sha256; + thumbnailFile = SelectThumbnailFile( + fullPath, + rawMetadata, + chart: null); + features = null; + featureAnalysisError = string.Empty; + durationSeconds = 0; + durationIsEstimated = false; + try + { + var compiled = BmsTimelineCompiler.Compile(document); + if (string.IsNullOrWhiteSpace(thumbnailFile)) + { + thumbnailFile = SelectThumbnailFile( + fullPath, + rawMetadata, + compiled); + } + var playable = BmsPlayableChartConverter.Convert(compiled).Chart; + durationSeconds = playable.Duration; + features = ChartFeatureAnalyzer.Analyze(playable); + } + catch (Exception featureException) + { + featureAnalysisError = featureException.Message; + } + Interlocked.Increment(ref _cacheMisses); + } + + refreshedCache.Add(SongLibraryCacheEntry.From( + rawMetadata, + file, + thumbnailFile, + features, + featureAnalysisError, + durationIsEstimated ? 0.0 : durationSeconds)); + result.Add(new SongLibraryEntry + { + ChartPath = path, + PackId = _packId, + ThumbnailFile = thumbnailFile, + Metadata = CloneMetadata(rawMetadata), + DurationSeconds = durationSeconds, + DurationIsEstimated = durationIsEstimated, + Features = features, + FeatureAnalysisError = featureAnalysisError, + }); + } + catch + { + Interlocked.Increment(ref _failed); + } + finally + { + Interlocked.Increment(ref _completed); + } + } + + cancellationToken.ThrowIfCancellationRequested(); + SaveCache(normalizedRoot, refreshedCache); + + var ordered = result + .OrderBy(entry => entry.Metadata.PlayLevel <= 0 ? 1 : 0) + .ThenBy(entry => entry.Metadata.PlayLevel) + .ThenBy(entry => entry.DisplayTitle, StringComparer.OrdinalIgnoreCase) + .ToList(); + SongSearchIndexer.Rebuild(ordered); + return ordered; + } + + private Dictionary LoadCache(string libraryRoot) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + _loadedCacheVersion = 0; + var sourcePath = !string.IsNullOrWhiteSpace(_cachePath) && File.Exists(_cachePath) + ? _cachePath + : !string.IsNullOrWhiteSpace(_legacyCachePath) && File.Exists(_legacyCachePath) + ? _legacyCachePath + : string.Empty; + if (string.IsNullOrWhiteSpace(sourcePath)) + { + CacheStatus = "cache not found"; + return result; + } + + try + { + var document = JsonUtility.FromJson(File.ReadAllText(sourcePath)); + if (document == null || document.Version < 1 || document.Version > CacheVersion || + !string.Equals(Path.GetFullPath(document.LibraryRoot ?? string.Empty), libraryRoot, + StringComparison.OrdinalIgnoreCase) || + document.ForcedKeyCount != _forcedKeyCount) + { + CacheStatus = "cache version, library root or key mode changed"; + return result; + } + + foreach (var entry in document.Entries ?? Array.Empty()) + { + if (entry == null || string.IsNullOrWhiteSpace(entry.ChartPath)) + continue; + result[Path.GetFullPath(entry.ChartPath)] = entry; + } + _loadedCacheVersion = document.Version; + CacheStatus = document.Version == CacheVersion + ? $"loaded {result.Count:N0} cached charts" + : $"migrating {result.Count:N0} legacy chart hashes"; + } + catch (Exception exception) + { + CacheStatus = $"cache ignored: {exception.Message}"; + } + + return result; + } + + private void SaveCache(string libraryRoot, List entries) + { + if (string.IsNullOrWhiteSpace(_cachePath)) + return; + + try + { + var directory = Path.GetDirectoryName(_cachePath); + if (!string.IsNullOrWhiteSpace(directory)) + Directory.CreateDirectory(directory); + + var document = new SongLibraryCacheDocument + { + Version = CacheVersion, + LibraryRoot = libraryRoot, + ForcedKeyCount = _forcedKeyCount, + Entries = entries.ToArray(), + }; + var temporaryPath = _cachePath + ".tmp"; + File.WriteAllText(temporaryPath, JsonUtility.ToJson(document)); + if (File.Exists(_cachePath)) + File.Delete(_cachePath); + File.Move(temporaryPath, _cachePath); + CacheStatus = $"{CacheHits:N0} cached, {CacheMisses:N0} refreshed"; + } + catch (Exception exception) + { + CacheStatus = $"cache write failed: {exception.Message}"; + } + } + + private static BmsMetadata CloneMetadata(BmsMetadata source) => new BmsMetadata + { + ChartPath = source.ChartPath, + Title = source.Title, + Subtitle = source.Subtitle, + Artist = source.Artist, + SubArtist = source.SubArtist, + Genre = source.Genre, + StageFile = source.StageFile, + BackBmp = source.BackBmp, + Md5 = source.Md5, + Sha256 = source.Sha256, + InitialBpm = source.InitialBpm, + MinimumBpm = source.MinimumBpm, + MaximumBpm = source.MaximumBpm, + Total = source.Total, + PlayLevel = source.PlayLevel, + Difficulty = source.Difficulty, + Rank = source.Rank, + LnType = source.LnType, + LnObj = source.LnObj, + KeyCount = source.KeyCount, + HasScratch = source.HasScratch, + UsedKeyChannelMask = source.UsedKeyChannelMask, + }; + + internal static string SelectThumbnailFile( + string chartPath, + BmsMetadata metadata, + BmsChart chart) + { + var explicitCandidates = new[] + { + metadata?.StageFile, + metadata?.BackBmp, + }; + foreach (var declared in explicitCandidates) + { + if (IsResolvableThumbnailImage(chartPath, declared)) + return declared; + } + + if (chart?.BgaEvents == null || chart.BmpDefinitions == null) + return string.Empty; + + foreach (var bgaEvent in chart.BgaEvents + .Where(item => item.Layer == BmsBgaLayer.Base) + .OrderBy(item => item.Time)) + { + if (!chart.BmpDefinitions.TryGetValue( + bgaEvent.ResourceId ?? string.Empty, + out var declared) || + !IsResolvableThumbnailImage(chartPath, declared)) + continue; + return declared; + } + + foreach (var bgaEvent in chart.BgaEvents + .Where(item => item.Layer == BmsBgaLayer.Layer) + .OrderBy(item => item.Time)) + { + if (!chart.BmpDefinitions.TryGetValue( + bgaEvent.ResourceId ?? string.Empty, + out var declared) || + !IsSafeFallbackImage(chartPath, declared)) + continue; + return declared; + } + + var poorOnlyResources = new HashSet( + chart.BgaEvents + .Where(item => item.Layer == BmsBgaLayer.Poor) + .Select(item => item.ResourceId ?? string.Empty), + StringComparer.Ordinal); + poorOnlyResources.ExceptWith( + chart.BgaEvents + .Where(item => item.Layer != BmsBgaLayer.Poor) + .Select(item => item.ResourceId ?? string.Empty)); + + var declaredFallback = SelectBestFallbackCandidate( + chart.BmpDefinitions + .Where(item => !poorOnlyResources.Contains(item.Key)) + .Select(item => item.Value) + .Where(item => IsSafeFallbackImage(chartPath, item))); + if (!string.IsNullOrWhiteSpace(declaredFallback)) + return declaredFallback; + + var poorOnlyFiles = new HashSet( + chart.BmpDefinitions + .Where(item => poorOnlyResources.Contains(item.Key)) + .Select(item => Path.GetFileName(item.Value ?? string.Empty)), + StringComparer.OrdinalIgnoreCase); + return SelectDirectoryFallback(chartPath, poorOnlyFiles); + } + + private static bool IsResolvableThumbnailImage( + string chartPath, + string declaredPath) + { + var resolved = BmsResourceResolver.ResolveImage(chartPath, declaredPath); + return !string.IsNullOrEmpty(resolved) && + ThumbnailImageExtensions.Contains(Path.GetExtension(resolved)); + } + + private static bool IsSafeFallbackImage(string chartPath, string declaredPath) => + IsResolvableThumbnailImage(chartPath, declaredPath) && + !HasRejectedFallbackName(Path.GetFileName(declaredPath)); + + private static string SelectDirectoryFallback( + string chartPath, + IReadOnlyCollection excludedFileNames) + { + try + { + var directory = Path.GetDirectoryName(chartPath); + if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory)) + return string.Empty; + + var candidates = Directory + .EnumerateFiles(directory, "*", SearchOption.TopDirectoryOnly) + .Where(path => ThumbnailImageExtensions.Contains(Path.GetExtension(path))) + .Where(path => !HasRejectedFallbackName(Path.GetFileName(path))) + .Where(path => excludedFileNames == null || + !excludedFileNames.Contains( + Path.GetFileName(path), + StringComparer.OrdinalIgnoreCase)) + .Select(Path.GetFileName); + return SelectBestFallbackCandidate(candidates); + } + catch (Exception) + { + return string.Empty; + } + } + + private static string SelectBestFallbackCandidate(IEnumerable candidates) + { + var distinct = candidates + .Where(item => !string.IsNullOrWhiteSpace(item)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(item => item, StringComparer.OrdinalIgnoreCase) + .ToArray(); + if (distinct.Length == 1) + return distinct[0]; + + var ranked = distinct + .Select(item => new + { + Path = item, + Rank = SemanticFallbackRank(Path.GetFileNameWithoutExtension(item)), + }) + .Where(item => item.Rank < int.MaxValue) + .OrderBy(item => item.Rank) + .ThenBy(item => item.Path, StringComparer.OrdinalIgnoreCase) + .ToArray(); + if (ranked.Length == 0) + return string.Empty; + + var bestRank = ranked[0].Rank; + return ranked.Count(item => item.Rank == bestRank) == 1 + ? ranked[0].Path + : string.Empty; + } + + private static int SemanticFallbackRank(string fileStem) + { + var normalized = (fileStem ?? string.Empty) + .Replace("-", " ") + .Replace("_", " ") + .Replace(".", " ") + .ToLowerInvariant(); + var tokens = normalized.Split( + new[] { ' ' }, + StringSplitOptions.RemoveEmptyEntries); + if (tokens.Any(item => item == "jacket" || item == "cover")) + return 0; + if (tokens.Any(item => item == "stage" || item == "stagefile")) + return 1; + if (tokens.Any(item => item == "title")) + return 2; + if (tokens.Any(item => + item == "background" || item == "back" || item == "bgi" || item == "bg")) + return 3; + if (tokens.Any(item => + item == "preview" || item == "thumb" || item == "thumbnail")) + return 4; + if (tokens.Any(item => item == "banner")) + return 5; + return int.MaxValue; + } + + private static bool HasRejectedFallbackName(string fileName) + { + var normalized = Path.GetFileNameWithoutExtension(fileName ?? string.Empty) + .ToLowerInvariant(); + return RejectedFallbackNameParts.Any(normalized.Contains); + } + + private static ChartHashes ComputeHashes(string path) + { + using var stream = File.OpenRead(path); + using var md5 = MD5.Create(); + var md5Value = md5.ComputeHash(stream); + stream.Position = 0; + using var sha256 = SHA256.Create(); + var shaValue = sha256.ComputeHash(stream); + return new ChartHashes(ToHex(md5Value), ToHex(shaValue)); + } + + private static string ToHex(byte[] bytes) => + BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant(); + + private readonly struct ChartHashes + { + public ChartHashes(string md5, string sha256) + { + Md5 = md5; + Sha256 = sha256; + } + + public string Md5 { get; } + public string Sha256 { get; } + } + + [Serializable] + private sealed class SongLibraryCacheDocument + { + public int Version; + public string LibraryRoot; + public int ForcedKeyCount; + public SongLibraryCacheEntry[] Entries; + } + + [Serializable] + private sealed class SongLibraryCacheEntry + { + public string ChartPath; + public long FileLength; + public long LastWriteUtcTicks; + public string Title; + public string Subtitle; + public string Artist; + public string SubArtist; + public string Genre; + public string StageFile; + public string BackBmp; + public string ThumbnailFile; + public int ThumbnailResolverVersion; + public string Md5; + public string Sha256; + public double InitialBpm; + public double MinimumBpm; + public double MaximumBpm; + public double Total; + public int PlayLevel; + public int Difficulty; + public int Rank; + public int LnType; + public string LnObj; + public int KeyCount; + public bool HasScratch; + public int UsedKeyChannelMask; + public double DurationSeconds; + public int FeatureAnalyzerVersion; + public int FeatureCalibrationVersion; + public ChartFeatureVector Features; + public string FeatureAnalysisError; + + public static SongLibraryCacheEntry From( + BmsMetadata metadata, + FileInfo file, + string thumbnailFile, + ChartFeatureVector features, + string featureAnalysisError, + double durationSeconds) => + new SongLibraryCacheEntry + { + ChartPath = file.FullName, + FileLength = file.Length, + LastWriteUtcTicks = file.LastWriteTimeUtc.Ticks, + Title = metadata.Title, + Subtitle = metadata.Subtitle, + Artist = metadata.Artist, + SubArtist = metadata.SubArtist, + Genre = metadata.Genre, + StageFile = metadata.StageFile, + BackBmp = metadata.BackBmp, + ThumbnailFile = thumbnailFile ?? string.Empty, + ThumbnailResolverVersion = SongLibraryScanner.ThumbnailResolverVersion, + Md5 = metadata.Md5, + Sha256 = metadata.Sha256, + InitialBpm = metadata.InitialBpm, + MinimumBpm = metadata.MinimumBpm, + MaximumBpm = metadata.MaximumBpm, + Total = metadata.Total, + PlayLevel = metadata.PlayLevel, + Difficulty = metadata.Difficulty, + Rank = metadata.Rank, + LnType = metadata.LnType, + LnObj = metadata.LnObj, + KeyCount = metadata.KeyCount, + HasScratch = metadata.HasScratch, + UsedKeyChannelMask = metadata.UsedKeyChannelMask, + DurationSeconds = Math.Max(0.0, durationSeconds), + FeatureAnalyzerVersion = ChartFeatureVector.CurrentAnalyzerVersion, + FeatureCalibrationVersion = ChartFeatureVector.CurrentCalibrationVersion, + Features = features, + FeatureAnalysisError = featureAnalysisError ?? string.Empty, + }; + + public BmsMetadata ToMetadata(string chartPath) => new BmsMetadata + { + ChartPath = chartPath, + Title = Title, + Subtitle = Subtitle, + Artist = Artist, + SubArtist = SubArtist, + Genre = Genre, + StageFile = StageFile, + BackBmp = BackBmp, + Md5 = Md5, + Sha256 = Sha256, + InitialBpm = InitialBpm, + MinimumBpm = MinimumBpm, + MaximumBpm = MaximumBpm, + Total = Total, + PlayLevel = PlayLevel, + Difficulty = Difficulty, + Rank = Rank, + LnType = LnType, + LnObj = LnObj, + KeyCount = KeyCount <= 0 ? 5 : KeyCount, + HasScratch = HasScratch, + UsedKeyChannelMask = UsedKeyChannelMask, + }; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryScanner.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryScanner.cs.meta new file mode 100644 index 0000000..6a00ea5 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryScanner.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1a10454111d804d1b8797ae34b91007e \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryView.cs b/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryView.cs new file mode 100644 index 0000000..913f05a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryView.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ProjectQuint.Library +{ + public enum SongSortMode + { + DifficultyAscending, + DifficultyDescending, + Title, + FavoritesFirst, + } + + public static class SongLibraryView + { + public const string RecommendedFilter = "LEVEL 14–15"; + public const string AllLevelsFilter = "ALL CHARTS"; + public const string UnrankedFilter = "UNRANKED"; + public const string FavoritesFilter = "FAVORITES"; + + public static List BuildLevelFilters(IReadOnlyList songs) + { + var filters = new List { AllLevelsFilter }; + if (songs == null) + return filters; + + filters.AddRange(songs + .Where(song => !string.IsNullOrWhiteSpace(song.CompactDifficultyLabel)) + .GroupBy(song => song.CompactDifficultyLabel, StringComparer.OrdinalIgnoreCase) + .Select(group => new + { + Level = group.Key, + Group = group.Min(DifficultyGroup), + SortIndex = group.Min(song => song.DifficultySortIndex), + }) + .OrderBy(item => item.Group) + .ThenBy(item => item.SortIndex) + .ThenBy(item => item.Level, StringComparer.OrdinalIgnoreCase) + .Select(item => item.Level)); + + if (songs.Any(song => string.IsNullOrWhiteSpace(song.CompactDifficultyLabel))) + filters.Add(UnrankedFilter); + return filters; + } + + public static List Apply( + IReadOnlyList songs, + string searchQuery, + string levelFilter, + SongSortMode sortMode, + bool favoritesOnly = false) + { + if (songs == null) + return new List(); + + var query = (searchQuery ?? string.Empty).Trim(); + IEnumerable filtered = songs.Where(song => + (!favoritesOnly || song.IsFavorite) && + MatchesLevel(song, levelFilter) && MatchesSearch(song, query)); + + return Sort(filtered, query, sortMode).ToList(); + } + + public static List Apply( + IReadOnlyList songs, + string searchQuery, + IReadOnlyCollection levelFilters, + SongSortMode sortMode, + bool favoritesOnly = false) + { + if (songs == null) + return new List(); + + var query = (searchQuery ?? string.Empty).Trim(); + var selectedLevels = levelFilters == null || levelFilters.Count == 0 + ? null + : new HashSet(levelFilters, StringComparer.OrdinalIgnoreCase); + IEnumerable filtered = songs.Where(song => + (!favoritesOnly || song.IsFavorite) && + MatchesAnyLevel(song, selectedLevels) && MatchesSearch(song, query)); + + return Sort(filtered, query, sortMode).ToList(); + } + + public static string SortLabel(SongSortMode sortMode) + { + switch (sortMode) + { + case SongSortMode.DifficultyDescending: return "DIFFICULTY ↓"; + case SongSortMode.Title: return "TITLE A–Z"; + case SongSortMode.FavoritesFirst: return "FAVORITES FIRST"; + default: return "DIFFICULTY ↑"; + } + } + + private static bool MatchesLevel(SongLibraryEntry song, string levelFilter) + { + if (string.IsNullOrWhiteSpace(levelFilter) || + string.Equals(levelFilter, AllLevelsFilter, StringComparison.OrdinalIgnoreCase)) + return true; + + var level = song.CompactDifficultyLabel ?? string.Empty; + if (string.Equals(levelFilter, RecommendedFilter, StringComparison.OrdinalIgnoreCase)) + return string.Equals(level, "LEVEL 14", StringComparison.OrdinalIgnoreCase) || + string.Equals(level, "LEVEL 15", StringComparison.OrdinalIgnoreCase); + if (string.Equals(levelFilter, UnrankedFilter, StringComparison.OrdinalIgnoreCase)) + return string.IsNullOrWhiteSpace(level); + return string.Equals(level, levelFilter, StringComparison.OrdinalIgnoreCase); + } + + private static bool MatchesAnyLevel(SongLibraryEntry song, ISet levelFilters) + { + if (levelFilters == null || levelFilters.Count == 0 || levelFilters.Contains(AllLevelsFilter)) + return true; + + var level = song.CompactDifficultyLabel ?? string.Empty; + return string.IsNullOrWhiteSpace(level) + ? levelFilters.Contains(UnrankedFilter) + : levelFilters.Contains(level); + } + + private static bool MatchesSearch(SongLibraryEntry song, string query) + => SongSearchIndexer.Matches(song, query); + + private static IOrderedEnumerable Sort( + IEnumerable songs, + string query, + SongSortMode sortMode) + { + // Search relevance is the primary order only while a query is active. The + // selected library sort remains the deterministic tie-breaker. + var ordered = songs.OrderByDescending(song => + string.IsNullOrWhiteSpace(query) + ? 0 + : SongSearchIndexer.CalculateMatchScore(song, query)); + switch (sortMode) + { + case SongSortMode.DifficultyDescending: + return ordered + .ThenBy(DifficultyGroup) + .ThenByDescending(song => song.DifficultySortIndex) + .ThenBy(song => song.DisplayTitle, StringComparer.OrdinalIgnoreCase); + case SongSortMode.Title: + return ordered + .ThenBy(song => song.DisplayTitle, StringComparer.OrdinalIgnoreCase) + .ThenBy(song => song.DisplayArtist, StringComparer.OrdinalIgnoreCase); + case SongSortMode.FavoritesFirst: + return ordered + .ThenByDescending(song => song.IsFavorite) + .ThenBy(DifficultyGroup) + .ThenBy(song => song.DifficultySortIndex) + .ThenBy(song => song.DisplayTitle, StringComparer.OrdinalIgnoreCase); + default: + return ordered + .ThenBy(DifficultyGroup) + .ThenBy(song => song.DifficultySortIndex) + .ThenBy(song => song.DisplayTitle, StringComparer.OrdinalIgnoreCase); + } + } + + private static int DifficultyGroup(SongLibraryEntry song) => + song?.HasCatalogDifficulty == true ? 0 : + song?.Metadata?.PlayLevel > 0 ? 1 : 2; + + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryView.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryView.cs.meta new file mode 100644 index 0000000..d6e64e4 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongLibraryView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 443233f6785cd4356a3fc324354312d6 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongPlayRecordStore.cs b/Assets/ProjectQuint/Runtime/Unity/Library/SongPlayRecordStore.cs new file mode 100644 index 0000000..c592100 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongPlayRecordStore.cs @@ -0,0 +1,506 @@ +using System; +using System.Collections.Generic; +using ProjectQuint.Core.Progression; +using ProjectQuint.Persistence; + +namespace ProjectQuint.Library +{ + [Serializable] + public sealed class RatingPlayRecord + { + public bool HasRecord; + public string EventId = string.Empty; + public string ProviderId = string.Empty; + public string ProviderDisplayName = string.Empty; + public string DifficultyLabel = string.Empty; + public string DifficultyValue = string.Empty; + public string Title = string.Empty; + public string Artist = string.Empty; + public double ChartConstant; + public int KeyCount; + public bool HasScratch; + public string LaneModifier = string.Empty; + public bool HardClear; + public double AccuracyPercent; + public double PlayRating; + public int PlayCount; + public int RatingRulesVersion; + public long RecordedUtcTicks; + } + + [Serializable] + public sealed class SongPlayRecord + { + public bool HasRecord; + public int BestScore; + public double BestAccuracyPercent; + public int BestMaxCombo; + public bool HasBestScoreJudgementBreakdown; + public int BestScoreKoolCount; + public int BestScoreCoolCount; + public int BestScoreGoodCount; + public int BestScoreMissCount; + public int BestScorePurePerfectCount; + public int PlayCount; + public bool HasJudgementBreakdown; + public int LastScore; + public double LastAccuracyPercent; + public int LastMaxCombo; + public int LastKoolCount; + public int LastCoolCount; + public int LastGoodCount; + public int LastMissCount; + public int LastFastCount; + public int LastLateCount; + public string LastAppliedEventId = string.Empty; + public int LastPurePerfectCount; + public int LastJudgedUnits; + public int LastTotalScoringUnits; + public bool LastCleared; + public bool LastHardClear; + public bool EverFullCombo; + public bool EverAllPure; + public RatingPlayRecord BestRating = new RatingPlayRecord(); + public List BestRatings = new List(); + } + + internal readonly struct SongPlayResult + { + public SongPlayResult( + int score, + double accuracyPercent, + int maxCombo, + int koolCount, + int coolCount, + int goodCount, + int missCount, + int fastCount, + int lateCount, + string eventId = "", + int purePerfectCount = 0, + int judgedUnits = 0, + int totalScoringUnits = 0, + bool cleared = false, + bool hardClear = false, + string packId = "", + string packDisplayName = "", + string providerId = "", + string providerDisplayName = "", + string difficultyLabel = "", + string difficultyValue = "", + string title = "", + string artist = "", + double chartConstant = 0.0, + int keyCount = 0, + bool hasScratch = false, + string laneModifier = "", + long occurredUtcTicks = 0) + { + Score = score; + AccuracyPercent = accuracyPercent; + MaxCombo = maxCombo; + KoolCount = koolCount; + CoolCount = coolCount; + GoodCount = goodCount; + MissCount = missCount; + FastCount = fastCount; + LateCount = lateCount; + EventId = eventId ?? string.Empty; + PurePerfectCount = purePerfectCount; + JudgedUnits = judgedUnits; + TotalScoringUnits = totalScoringUnits; + Cleared = cleared; + HardClear = hardClear; + PackId = packId ?? string.Empty; + PackDisplayName = packDisplayName ?? string.Empty; + ProviderId = providerId ?? string.Empty; + ProviderDisplayName = providerDisplayName ?? string.Empty; + DifficultyLabel = difficultyLabel ?? string.Empty; + DifficultyValue = difficultyValue ?? string.Empty; + Title = title ?? string.Empty; + Artist = artist ?? string.Empty; + ChartConstant = chartConstant; + KeyCount = keyCount; + HasScratch = hasScratch; + LaneModifier = laneModifier ?? string.Empty; + OccurredUtcTicks = occurredUtcTicks; + } + + public int Score { get; } + public double AccuracyPercent { get; } + public int MaxCombo { get; } + public int KoolCount { get; } + public int CoolCount { get; } + public int GoodCount { get; } + public int MissCount { get; } + public int FastCount { get; } + public int LateCount { get; } + public string EventId { get; } + public int PurePerfectCount { get; } + public int JudgedUnits { get; } + public int TotalScoringUnits { get; } + public bool Cleared { get; } + public bool HardClear { get; } + public string PackId { get; } + public string PackDisplayName { get; } + public string ProviderId { get; } + public string ProviderDisplayName { get; } + public string DifficultyLabel { get; } + public string DifficultyValue { get; } + public string Title { get; } + public string Artist { get; } + public double ChartConstant { get; } + public int KeyCount { get; } + public bool HasScratch { get; } + public string LaneModifier { get; } + public long OccurredUtcTicks { get; } + public bool RatingEligible => + Cleared && + !string.IsNullOrWhiteSpace(ProviderId) && + ChartConstant > 0.0 && + KeyCount >= 4 && KeyCount <= 7 && + !string.Equals(LaneModifier, "RANDOM", StringComparison.OrdinalIgnoreCase); + public double PlayRating => RatingEligible + ? RatingRules.CalculatePlayRating(ChartConstant, AccuracyPercent, HardClear) + : 0.0; + + public static SongPlayResult FromEvent(PlayResultEvent playEvent) => new SongPlayResult( + playEvent.Score, + playEvent.AccuracyPercent, + playEvent.MaxCombo, + playEvent.KoolCount, + playEvent.CoolCount, + playEvent.GoodCount, + playEvent.MissCount, + playEvent.FastCount, + playEvent.LateCount, + playEvent.EventId, + playEvent.PurePerfectCount, + playEvent.JudgedUnits, + playEvent.TotalScoringUnits, + playEvent.Cleared, + playEvent.HardClear, + playEvent.PackId, + playEvent.PackDisplayName, + playEvent.ProviderId, + playEvent.ProviderDisplayName, + playEvent.DifficultyLabel, + playEvent.DifficultyValue, + playEvent.Title, + playEvent.Artist, + playEvent.ChartConstant, + playEvent.KeyCount, + playEvent.HasScratch, + playEvent.LaneModifier, + playEvent.OccurredUtcTicks); + } + + internal static class SongPlayRecordStore + { + private const string PreferencePrefix = "ProjectQuint.PlayRecordV3."; + + public static SongPlayRecord Load(SongLibraryEntry entry) + => SongRecordRepository.Shared.LoadPlayRecord(entry); + + public static void Record(SongLibraryEntry entry, SongPlayResult result) + { + SongRecordRepository.Shared.RecordPlay(entry, result); + } + + internal static SongPlayRecord MergeRun(SongPlayRecord current, SongPlayResult result) + { + current ??= new SongPlayRecord(); + if (!string.IsNullOrWhiteSpace(result.EventId) && + string.Equals(current.LastAppliedEventId, result.EventId, StringComparison.Ordinal)) + return current; + + var hadRecord = current.HasRecord; + var previousBestScore = Math.Max(0, current.BestScore); + var capturesBestScoreBreakdown = + !hadRecord || + result.Score > previousBestScore || + !current.HasBestScoreJudgementBreakdown && result.Score == previousBestScore; + current = Merge(current, result.Score, result.AccuracyPercent, result.MaxCombo); + if (capturesBestScoreBreakdown) + { + current.HasBestScoreJudgementBreakdown = true; + current.BestScoreKoolCount = Math.Max(0, result.KoolCount); + current.BestScoreCoolCount = Math.Max(0, result.CoolCount); + current.BestScoreGoodCount = Math.Max(0, result.GoodCount); + current.BestScoreMissCount = Math.Max(0, result.MissCount); + current.BestScorePurePerfectCount = Math.Max(0, result.PurePerfectCount); + } + current.PlayCount = Math.Max(0, current.PlayCount) + 1; + current.HasJudgementBreakdown = true; + current.LastScore = Math.Max(0, result.Score); + current.LastAccuracyPercent = Math.Max(0, Math.Min(100, result.AccuracyPercent)); + current.LastMaxCombo = Math.Max(0, result.MaxCombo); + current.LastKoolCount = Math.Max(0, result.KoolCount); + current.LastCoolCount = Math.Max(0, result.CoolCount); + current.LastGoodCount = Math.Max(0, result.GoodCount); + current.LastMissCount = Math.Max(0, result.MissCount); + current.LastFastCount = Math.Max(0, result.FastCount); + current.LastLateCount = Math.Max(0, result.LateCount); + current.LastAppliedEventId = result.EventId ?? string.Empty; + current.LastPurePerfectCount = Math.Max(0, result.PurePerfectCount); + current.LastJudgedUnits = Math.Max(0, result.JudgedUnits); + current.LastTotalScoringUnits = Math.Max(0, result.TotalScoringUnits); + current.LastCleared = result.Cleared; + current.LastHardClear = result.HardClear; + current.EverFullCombo |= result.Cleared && result.MissCount == 0; + current.EverAllPure |= result.Cleared && result.TotalScoringUnits > 0 && + result.PurePerfectCount >= result.TotalScoringUnits; + NormalizeRatingHistory(current); + if (result.RatingEligible) + { + var scopedBestIndex = FindRatingScope( + current.BestRatings, + result.ProviderId, + result.KeyCount, + result.HasScratch); + var scopedBest = scopedBestIndex >= 0 + ? current.BestRatings[scopedBestIndex] + : null; + var scopedPlayCount = Math.Max(0, scopedBest?.PlayCount ?? 0) + 1; + if (scopedBest == null || result.PlayRating > scopedBest.PlayRating + 0.000000001) + { + var replacement = new RatingPlayRecord + { + HasRecord = true, + EventId = result.EventId, + ProviderId = result.ProviderId.Trim(), + ProviderDisplayName = string.IsNullOrWhiteSpace(result.ProviderDisplayName) + ? result.ProviderId.Trim() + : result.ProviderDisplayName.Trim(), + DifficultyLabel = result.DifficultyLabel, + DifficultyValue = result.DifficultyValue, + Title = result.Title, + Artist = result.Artist, + ChartConstant = result.ChartConstant, + KeyCount = result.KeyCount, + HasScratch = result.HasScratch, + LaneModifier = result.LaneModifier, + HardClear = result.HardClear, + AccuracyPercent = Math.Max(0.0, Math.Min(100.0, result.AccuracyPercent)), + PlayRating = result.PlayRating, + PlayCount = scopedPlayCount, + RatingRulesVersion = RatingRules.RulesVersion, + RecordedUtcTicks = result.OccurredUtcTicks, + }; + if (scopedBestIndex >= 0) + current.BestRatings[scopedBestIndex] = replacement; + else + current.BestRatings.Add(replacement); + } + else + { + scopedBest.PlayCount = scopedPlayCount; + } + } + RefreshLegacyBestRating(current); + return current; + } + + internal static bool NormalizeRatingHistory(SongPlayRecord record) + { + if (record == null) + return false; + + var changed = false; + record.BestRatings ??= new List(); + changed |= NormalizeRatingRules(record.BestRating); + if (record.BestRating?.HasRecord == true && IsScopedRating(record.BestRating)) + { + var legacyScope = FindRatingScope( + record.BestRatings, + record.BestRating.ProviderId, + record.BestRating.KeyCount, + record.BestRating.HasScratch); + if (legacyScope < 0) + { + record.BestRatings.Add(CloneRating(record.BestRating)); + changed = true; + } + else if (record.BestRating.PlayRating > record.BestRatings[legacyScope].PlayRating) + { + record.BestRatings[legacyScope] = CloneRating(record.BestRating); + changed = true; + } + } + + var normalized = new List(record.BestRatings.Count); + foreach (var candidate in record.BestRatings) + { + changed |= NormalizeRatingRules(candidate); + if (!IsScopedRating(candidate)) + { + changed = true; + continue; + } + + var existing = FindRatingScope( + normalized, + candidate.ProviderId, + candidate.KeyCount, + candidate.HasScratch); + if (existing < 0) + { + normalized.Add(candidate); + continue; + } + + changed = true; + var existingRating = normalized[existing]; + var combinedPlayCount = Math.Max(0, existingRating.PlayCount) + Math.Max(0, candidate.PlayCount); + if (candidate.PlayRating > existingRating.PlayRating) + { + candidate.PlayCount = combinedPlayCount; + normalized[existing] = candidate; + } + else + { + existingRating.PlayCount = combinedPlayCount; + } + } + if (normalized.Count != record.BestRatings.Count) + changed = true; + record.BestRatings = normalized; + for (var index = 0; index < record.BestRatings.Count; index++) + { + var rating = record.BestRatings[index]; + if (rating.PlayCount > 0) + continue; + rating.PlayCount = record.BestRatings.Count == 1 + ? Math.Max(1, record.PlayCount) + : 1; + changed = true; + } + + var previousBest = record.BestRating; + RefreshLegacyBestRating(record); + if (!SameRating(previousBest, record.BestRating)) + changed = true; + return changed; + } + + private static bool NormalizeRatingRules(RatingPlayRecord rating) + { + if (rating?.HasRecord != true || + rating.RatingRulesVersion >= RatingRules.RulesVersion) + return false; + + if (!RatingRules.TryResolveChartConstant( + rating.DifficultyValue, + rating.DifficultyLabel, + out var chartConstant)) + { + rating.HasRecord = false; + rating.ChartConstant = 0.0; + rating.PlayRating = 0.0; + rating.RatingRulesVersion = RatingRules.RulesVersion; + return true; + } + + rating.ChartConstant = chartConstant; + rating.PlayRating = RatingRules.CalculatePlayRating( + chartConstant, + rating.AccuracyPercent, + rating.HardClear); + rating.RatingRulesVersion = RatingRules.RulesVersion; + return true; + } + + private static void RefreshLegacyBestRating(SongPlayRecord record) + { + RatingPlayRecord strongest = null; + foreach (var candidate in record.BestRatings ?? new List()) + { + if (candidate?.HasRecord == true && + (strongest == null || candidate.PlayRating > strongest.PlayRating)) + strongest = candidate; + } + record.BestRating = strongest == null + ? record.BestRating ?? new RatingPlayRecord() + : CloneRating(strongest); + } + + private static int FindRatingScope( + IReadOnlyList ratings, + string providerId, + int keyCount, + bool hasScratch) + { + for (var index = 0; index < (ratings?.Count ?? 0); index++) + { + var candidate = ratings[index]; + if (candidate != null && + string.Equals(candidate.ProviderId?.Trim(), providerId?.Trim(), StringComparison.OrdinalIgnoreCase) && + candidate.KeyCount == keyCount && + candidate.HasScratch == hasScratch) + return index; + } + return -1; + } + + private static bool IsScopedRating(RatingPlayRecord rating) => + rating?.HasRecord == true && + !string.IsNullOrWhiteSpace(rating.ProviderId) && + rating.KeyCount >= 4 && rating.KeyCount <= 7 && + rating.PlayRating > 0.0; + + private static bool SameRating(RatingPlayRecord left, RatingPlayRecord right) => + ReferenceEquals(left, right) || + left != null && right != null && + string.Equals(left.EventId, right.EventId, StringComparison.Ordinal) && + string.Equals(left.ProviderId, right.ProviderId, StringComparison.OrdinalIgnoreCase) && + left.KeyCount == right.KeyCount && + left.HasScratch == right.HasScratch && + Math.Abs(left.PlayRating - right.PlayRating) <= 0.000000001; + + internal static RatingPlayRecord CloneRating(RatingPlayRecord source) + { + if (source == null) + return new RatingPlayRecord(); + return new RatingPlayRecord + { + HasRecord = source.HasRecord, + EventId = source.EventId, + ProviderId = source.ProviderId, + ProviderDisplayName = source.ProviderDisplayName, + DifficultyLabel = source.DifficultyLabel, + DifficultyValue = source.DifficultyValue, + Title = source.Title, + Artist = source.Artist, + ChartConstant = source.ChartConstant, + KeyCount = source.KeyCount, + HasScratch = source.HasScratch, + LaneModifier = source.LaneModifier, + HardClear = source.HardClear, + AccuracyPercent = source.AccuracyPercent, + PlayRating = source.PlayRating, + PlayCount = source.PlayCount, + RatingRulesVersion = source.RatingRulesVersion, + RecordedUtcTicks = source.RecordedUtcTicks, + }; + } + + internal static SongPlayRecord Merge( + SongPlayRecord current, + int score, + double accuracyPercent, + int maxCombo) + { + current ??= new SongPlayRecord(); + current.HasRecord = true; + current.BestScore = Math.Max(current.BestScore, Math.Max(0, score)); + current.BestAccuracyPercent = Math.Max( + current.BestAccuracyPercent, + Math.Max(0, Math.Min(100, accuracyPercent))); + current.BestMaxCombo = Math.Max(current.BestMaxCombo, Math.Max(0, maxCombo)); + return current; + } + + internal static string BuildPreferenceKey(SongLibraryEntry entry) + => PreferencePrefix + ChartIdentityResolver.BuildLegacyPreferenceSuffix(entry); + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongPlayRecordStore.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/SongPlayRecordStore.cs.meta new file mode 100644 index 0000000..3c4239e --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongPlayRecordStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 158b636928db4482b99df9a7ef8bfeb5 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongSearchIndex.cs b/Assets/ProjectQuint/Runtime/Unity/Library/SongSearchIndex.cs new file mode 100644 index 0000000..3eb0872 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongSearchIndex.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; + +namespace ProjectQuint.Library +{ + internal sealed class SongSearchDocument + { + public static SongSearchDocument Empty { get; } = new SongSearchDocument( + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty()); + + public SongSearchDocument( + string[] title, + string[] artist, + string[] fileName, + string[] aliases) + { + Title = title ?? Array.Empty(); + Artist = artist ?? Array.Empty(); + FileName = fileName ?? Array.Empty(); + Aliases = aliases ?? Array.Empty(); + CombinedIndex = string.Join("\n", Title + .Concat(Artist) + .Concat(FileName) + .Concat(Aliases) + .Distinct(StringComparer.Ordinal) + .OrderBy(token => token, StringComparer.Ordinal)); + } + + public string[] Title { get; } + public string[] Artist { get; } + public string[] FileName { get; } + public string[] Aliases { get; } + public string CombinedIndex { get; } + } + + [Serializable] + internal sealed class SearchAliasDocument + { + public SearchAliasEntry[] Entries = Array.Empty(); + } + + [Serializable] + internal sealed class SearchAliasEntry + { + public string ChartMd5 = string.Empty; + public string MatchText = string.Empty; + public string[] Aliases = Array.Empty(); + } + + internal sealed class SearchAliasIndex + { + private readonly IReadOnlyList _entries; + + private SearchAliasIndex(IReadOnlyList entries, string status) + { + _entries = entries ?? Array.Empty(); + Status = status ?? string.Empty; + } + + public static SearchAliasIndex Empty { get; } = + new SearchAliasIndex(Array.Empty(), "no aliases"); + + public int Count => _entries.Count; + public string Status { get; } + + public static SearchAliasIndex Load(string bundledJson, string userPath) + { + var entries = new List(); + var bundledCount = AddJson(entries, bundledJson); + var userCount = 0; + if (!string.IsNullOrWhiteSpace(userPath) && File.Exists(userPath)) + { + try + { + userCount = AddJson(entries, File.ReadAllText(userPath)); + } + catch (Exception) + { + userCount = 0; + } + } + return new SearchAliasIndex( + entries, + $"{bundledCount:N0} bundled + {userCount:N0} user aliases"); + } + + public static SearchAliasIndex Parse(string json) => + new SearchAliasIndex(ParseEntries(json), "parsed aliases"); + + public IEnumerable Resolve(SongLibraryEntry song) + { + if (song == null || _entries.Count == 0) + yield break; + + var md5 = song.Metadata?.Md5 ?? string.Empty; + var title = song.DisplayTitle ?? string.Empty; + var artist = song.DisplayArtist ?? string.Empty; + var fileName = Path.GetFileNameWithoutExtension(song.ChartPath) ?? string.Empty; + foreach (var entry in _entries) + { + if (entry == null) + continue; + if (!string.IsNullOrWhiteSpace(entry.ChartMd5) && + !string.Equals(entry.ChartMd5.Trim(), md5, StringComparison.OrdinalIgnoreCase)) + continue; + if (!string.IsNullOrWhiteSpace(entry.MatchText) && + title.IndexOf(entry.MatchText, StringComparison.OrdinalIgnoreCase) < 0 && + artist.IndexOf(entry.MatchText, StringComparison.OrdinalIgnoreCase) < 0 && + fileName.IndexOf(entry.MatchText, StringComparison.OrdinalIgnoreCase) < 0) + continue; + + foreach (var alias in entry.Aliases ?? Array.Empty()) + if (!string.IsNullOrWhiteSpace(alias)) + yield return alias; + } + } + + private static int AddJson(ICollection target, string json) + { + var parsed = ParseEntries(json); + foreach (var entry in parsed) + target.Add(entry); + return parsed.Count; + } + + private static IReadOnlyList ParseEntries(string json) + { + if (string.IsNullOrWhiteSpace(json)) + return Array.Empty(); + try + { + var document = JsonUtility.FromJson(json); + return (document?.Entries ?? Array.Empty()) + .Where(entry => entry != null && + (!string.IsNullOrWhiteSpace(entry.ChartMd5) || + !string.IsNullOrWhiteSpace(entry.MatchText)) && + entry.Aliases?.Any(alias => !string.IsNullOrWhiteSpace(alias)) == true) + .ToArray(); + } + catch (Exception) + { + return Array.Empty(); + } + } + } + + internal static class SongSearchIndexer + { + public static void Rebuild(IReadOnlyList songs, SearchAliasIndex aliases = null) + { + foreach (var song in songs ?? Array.Empty()) + song?.RebuildSearchIndex(aliases); + } + + public static bool Matches(SongLibraryEntry song, string query) + => CalculateMatchScore(song, query) > 0; + + public static int CalculateMatchScore(SongLibraryEntry song, string query) + { + if (song == null) + return 0; + if (string.IsNullOrWhiteSpace(query)) + return 1; + if (string.IsNullOrEmpty(song.SearchIndex)) + song.RebuildSearchIndex(); + + var best = 0; + foreach (var queryVariant in JapaneseSearchNormalizer.BuildQueryVariants(query)) + { + best = Math.Max(best, Score(song.SearchDocument.Artist, queryVariant, 1200)); + best = Math.Max(best, Score(song.SearchDocument.Title, queryVariant, 1100)); + best = Math.Max(best, Score(song.SearchDocument.Aliases, queryVariant, 1000)); + best = Math.Max(best, Score(song.SearchDocument.FileName, queryVariant, 900)); + } + return best; + } + + public static SongSearchDocument Build(SongLibraryEntry song, SearchAliasIndex aliases) + { + if (song == null) + return SongSearchDocument.Empty; + + return new SongSearchDocument( + BuildField(song.DisplayTitle), + BuildField(song.DisplayArtist), + BuildField(Path.GetFileNameWithoutExtension(song.ChartPath)), + BuildField(aliases?.Resolve(song))); + } + + private static int Score(IEnumerable indexedValues, string query, int exactScore) + { + if (string.IsNullOrEmpty(query)) + return 0; + var best = 0; + foreach (var value in indexedValues ?? Array.Empty()) + { + if (string.Equals(value, query, StringComparison.Ordinal)) + return exactScore; + if (value.StartsWith(query, StringComparison.Ordinal)) + best = Math.Max(best, exactScore - 100); + else if (value.IndexOf(query, StringComparison.Ordinal) >= 0) + best = Math.Max(best, exactScore - 200); + } + return best; + } + + private static string[] BuildField(string value) => BuildField(new[] { value }); + + private static string[] BuildField(IEnumerable values) + { + var tokens = new HashSet(StringComparer.Ordinal); + foreach (var value in values ?? Array.Empty()) + foreach (var variant in JapaneseSearchNormalizer.BuildIndexVariants(value)) + tokens.Add(variant); + return tokens.OrderBy(token => token, StringComparer.Ordinal).ToArray(); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Library/SongSearchIndex.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Library/SongSearchIndex.cs.meta new file mode 100644 index 0000000..19cbd0a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Library/SongSearchIndex.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4ae792a10ebac4753bd8a00488bf5f84 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence.meta new file mode 100644 index 0000000..e54a148 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 77ea2769dae9b460d9751a055fca3234 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/AtomicJsonFile.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/AtomicJsonFile.cs new file mode 100644 index 0000000..daec282 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/AtomicJsonFile.cs @@ -0,0 +1,176 @@ +using System; +using System.IO; +using System.Text; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + internal enum PersistentReadSource + { + Missing, + Primary, + Backup, + } + + internal sealed class AtomicJsonFile + where TDocument : class + { + private readonly string _path; + private readonly Func _validate; + private readonly Action _logWarning; + + public AtomicJsonFile( + string path, + Func validate, + Action logWarning = null) + { + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("A persistence path is required.", nameof(path)); + + _path = path; + _validate = validate ?? throw new ArgumentNullException(nameof(validate)); + _logWarning = logWarning ?? (message => Debug.LogWarning(message)); + } + + public string Path => _path; + public string BackupPath => _path + ".bak"; + + public bool TryLoad(out TDocument document, out PersistentReadSource source) + { + if (TryRead(_path, out document, out var primaryFailure)) + { + source = PersistentReadSource.Primary; + return true; + } + + if (TryRead(BackupPath, out document, out var backupFailure)) + { + source = PersistentReadSource.Backup; + _logWarning( + $"[ProjectQuint Persistence] Recovered '{_path}' from its backup. " + + $"Primary failure: {primaryFailure}"); + RestorePrimaryFromBackup(document); + return true; + } + + source = PersistentReadSource.Missing; + document = null; + if (File.Exists(_path) || File.Exists(BackupPath)) + { + _logWarning( + $"[ProjectQuint Persistence] Could not read '{_path}' or its backup. " + + $"Primary: {primaryFailure}; backup: {backupFailure}"); + } + return false; + } + + public void Save(TDocument document) + { + if (document == null) + throw new ArgumentNullException(nameof(document)); + if (!_validate(document)) + throw new InvalidDataException($"Document validation failed for '{_path}'."); + + var json = JsonUtility.ToJson(document, true); + WriteAtomically(json, preserveExistingBackup: false); + } + + private bool TryRead(string path, out TDocument document, out string failure) + { + document = null; + failure = "file not found"; + if (!File.Exists(path)) + return false; + + try + { + var json = File.ReadAllText(path, Encoding.UTF8); + document = JsonUtility.FromJson(json); + if (document == null) + throw new InvalidDataException("JSON produced no document"); + if (!_validate(document)) + throw new InvalidDataException("schema version or document contents are unsupported"); + failure = string.Empty; + return true; + } + catch (Exception exception) + { + document = null; + failure = exception.Message; + return false; + } + } + + private void RestorePrimaryFromBackup(TDocument document) + { + try + { + var json = JsonUtility.ToJson(document, true); + WriteAtomically(json, preserveExistingBackup: true); + } + catch (Exception exception) + { + _logWarning( + $"[ProjectQuint Persistence] Backup data was loaded, but primary restore failed for " + + $"'{_path}': {exception.Message}"); + } + } + + private void WriteAtomically(string json, bool preserveExistingBackup) + { + var directory = System.IO.Path.GetDirectoryName(_path); + if (!string.IsNullOrWhiteSpace(directory)) + Directory.CreateDirectory(directory); + + var temporaryPath = _path + ".tmp"; + if (File.Exists(temporaryPath)) + File.Delete(temporaryPath); + + try + { + var bytes = new UTF8Encoding(false).GetBytes(json ?? string.Empty); + using (var stream = new FileStream( + temporaryPath, + FileMode.CreateNew, + FileAccess.Write, + FileShare.None, + 4096, + FileOptions.WriteThrough)) + { + stream.Write(bytes, 0, bytes.Length); + stream.Flush(true); + } + + if (!File.Exists(_path)) + { + File.Move(temporaryPath, _path); + return; + } + + if (preserveExistingBackup) + { + var corruptPath = BuildCorruptPath(); + File.Replace(temporaryPath, _path, corruptPath); + } + else + { + File.Replace(temporaryPath, _path, BackupPath); + } + } + finally + { + if (File.Exists(temporaryPath)) + File.Delete(temporaryPath); + } + } + + private string BuildCorruptPath() + { + var stamp = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fff"); + var candidate = $"{_path}.corrupt-{stamp}"; + for (var suffix = 1; File.Exists(candidate); suffix++) + candidate = $"{_path}.corrupt-{stamp}-{suffix}"; + return candidate; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/AtomicJsonFile.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/AtomicJsonFile.cs.meta new file mode 100644 index 0000000..5b6f4f2 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/AtomicJsonFile.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 30963ef3cbbef4bdcb835576399424a8 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ChartIdentityResolver.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/ChartIdentityResolver.cs new file mode 100644 index 0000000..f61d5b2 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ChartIdentityResolver.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using ProjectQuint.Core.Identity; +using ProjectQuint.Library; + +namespace ProjectQuint.Persistence +{ + internal static class ChartIdentityResolver + { + public static ChartId Resolve(SongLibraryEntry entry) + { + var md5 = entry?.Metadata?.Md5; + if (!string.IsNullOrWhiteSpace(md5)) + return ChartId.FromMd5(md5); + + var chartPath = entry?.ChartPath ?? string.Empty; + if (File.Exists(chartPath)) + { + using var stream = File.OpenRead(chartPath); + using var sha256 = SHA256.Create(); + return ChartId.FromSha256(ToHex(sha256.ComputeHash(stream))); + } + + return ChartId.FromPathFallback(HashText(chartPath)); + } + + public static string BuildLegacyPreferenceSuffix(SongLibraryEntry entry) + { + var md5 = entry?.Metadata?.Md5; + return !string.IsNullOrWhiteSpace(md5) + ? md5.Trim().ToLowerInvariant() + : HashText(entry?.ChartPath ?? string.Empty); + } + + public static string HashText(string value) + { + using var sha256 = SHA256.Create(); + return ToHex(sha256.ComputeHash(Encoding.UTF8.GetBytes(value ?? string.Empty))); + } + + private static string ToHex(byte[] bytes) => + BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant(); + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ChartIdentityResolver.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/ChartIdentityResolver.cs.meta new file mode 100644 index 0000000..66d87e8 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ChartIdentityResolver.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3fa475bdc85b54173b53821601f878a9 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/CourseRecordRepository.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/CourseRecordRepository.cs new file mode 100644 index 0000000..b598d9c --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/CourseRecordRepository.cs @@ -0,0 +1,332 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProjectQuint.Core.Progression; +using ProjectQuint.Library; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + [Serializable] + internal sealed class CourseRecordsDocument + { + public const int CurrentSchemaVersion = 6; + + public int SchemaVersion = CurrentSchemaVersion; + public List Records = new List(); + } + + [Serializable] + internal sealed class CourseRecordData + { + public string CourseId = string.Empty; + public string ProviderId = string.Empty; + public string ProviderDisplayName = string.Empty; + public string CourseName = string.Empty; + public AeryCourseRecord Record = new AeryCourseRecord(); + } + + internal sealed class CourseRecordRepository + { + private static readonly Lazy SharedRepository = + new Lazy(() => new CourseRecordRepository()); + + private readonly AtomicJsonFile _file; + private readonly Action _logWarning; + private Dictionary _records; + + public CourseRecordRepository(string path = null, Action logWarning = null) + { + _logWarning = logWarning ?? (message => Debug.LogWarning(message)); + _file = new AtomicJsonFile( + path ?? PersistencePaths.CourseRecords, + document => document != null && + document.SchemaVersion >= 1 && + document.SchemaVersion <= CourseRecordsDocument.CurrentSchemaVersion && + document.Records != null, + _logWarning); + } + + public static CourseRecordRepository Shared => SharedRepository.Value; + + internal void Reload() + { + _records = null; + } + + public AeryCourseRecord Load(AeryCourseDefinition course) + { + if (course == null) + return new AeryCourseRecord(); + + EnsureLoaded(); + var courseId = BuildCourseId(course); + if (_records.TryGetValue(courseId, out var stored)) + { + if (ApplyCourseMetadata(stored, course)) + Save(); + if (stored.Record?.HasRecord == true) + return stored.Record ?? new AeryCourseRecord(); + } + + return stored?.Record ?? new AeryCourseRecord(); + } + + public AeryCourseRecord Record(AeryCourseDefinition course, AeryCourseResult result) + { + if (course == null || result.Progress == null) + return new AeryCourseRecord(); + + EnsureLoaded(); + var stored = GetOrCreate(course); + ApplyCourseMetadata(stored, course); + var current = course.Record?.HasRecord == true ? course.Record : stored.Record; + stored.Record = AeryCourseRecordStore.Merge(current, result); + course.Record = stored.Record; + Save(); + return stored.Record; + } + + public int MigrateDifficultyTableProviders( + IReadOnlyDictionary providerMap) + { + EnsureLoaded(); + if (providerMap == null || providerMap.Count == 0) + return 0; + + var migratedRecords = 0; + var migrated = new Dictionary(StringComparer.Ordinal); + foreach (var source in _records.Values) + { + if (source == null) + continue; + + var legacyId = (source.ProviderId ?? string.Empty).Trim(); + var table = legacyId.Length > 0 && providerMap.TryGetValue(legacyId, out var mapped) + ? mapped + : FindTableFromCourseId(source.CourseId, providerMap, out legacyId); + if (table != null) + { + var suffix = ExtractCourseSuffix(source.CourseId, legacyId); + source.ProviderId = table.Id; + source.ProviderDisplayName = string.IsNullOrWhiteSpace(table.DisplayName) + ? table.Id + : table.DisplayName; + source.CourseId = $"{NormalizeProviderId(table.Id)}-sha256:{suffix}"; + migratedRecords++; + } + + if (migrated.TryGetValue(source.CourseId, out var existing)) + MergeCourseRecord(existing, source); + else + migrated[source.CourseId] = source; + } + + if (migratedRecords <= 0) + return 0; + _records = migrated; + Save(); + return migratedRecords; + } + + public ProfileCourseSnapshot CalculateCollection(string featuredBadgeCourseId = null) + { + EnsureLoaded(); + var entries = _records.Values + .Where(item => item?.Record?.HasRecord == true) + .Select(item => new CourseCollectionEntry( + item.CourseId, + item.ProviderDisplayName, + item.CourseName, + item.Record.Cleared, + item.Record.PlayCount, + item.Record.ClearCount, + item.Record.BestStageReached, + item.Record.BestAccuracyPercent, + item.Record.BestTrophyRank, + item.Record.BestTrophyName)) + .OrderByDescending(item => item.Cleared) + .ThenByDescending(item => item.BestStageReached) + .ThenBy(item => item.CourseName, StringComparer.OrdinalIgnoreCase) + .ToArray(); + return entries.Length == 0 + ? ProfileCourseSnapshot.Empty + : new ProfileCourseSnapshot( + entries.Length, + entries.Count(item => item.Cleared), + entries.Sum(item => item.PlayCount), + entries, + featuredBadgeCourseId); + } + + internal static string BuildCourseId(AeryCourseDefinition course) + { + var providerId = NormalizeProviderId(course?.ProviderId); + return $"{providerId}-sha256:{BuildLegacySuffix(course)}"; + } + + internal static string BuildLegacySuffix(AeryCourseDefinition course) + { + var identity = string.Join(":", course?.StageHashes ?? Array.Empty()).ToLowerInvariant(); + return ChartIdentityResolver.HashText(identity); + } + + private void EnsureLoaded() + { + if (_records != null) + return; + + _records = new Dictionary(StringComparer.Ordinal); + if (!_file.TryLoad(out var document, out _)) + return; + + if (document.SchemaVersion < CourseRecordsDocument.CurrentSchemaVersion) + { + Save(); + return; + } + + var migratedDocument = document.SchemaVersion < CourseRecordsDocument.CurrentSchemaVersion; + + var scoreFormatMigrated = false; + foreach (var record in document.Records) + { + if (record == null || string.IsNullOrWhiteSpace(record.CourseId)) + continue; + record.CourseId = record.CourseId.Trim().ToLowerInvariant(); + record.Record ??= new AeryCourseRecord(); + scoreFormatMigrated |= AeryCourseRecordStore.NormalizeScoreFormat(record.Record); + _records[record.CourseId] = record; + } + if (migratedDocument || scoreFormatMigrated) + Save(); + } + + private CourseRecordData GetOrCreate(AeryCourseDefinition course) + { + var courseId = BuildCourseId(course); + if (_records.TryGetValue(courseId, out var stored)) + { + ApplyCourseMetadata(stored, course); + return stored; + } + + stored = new CourseRecordData { CourseId = courseId }; + ApplyCourseMetadata(stored, course); + _records.Add(courseId, stored); + return stored; + } + + private void Save() + { + _file.Save(new CourseRecordsDocument + { + Records = _records.Values + .OrderBy(record => record.CourseId, StringComparer.Ordinal) + .ToList(), + }); + } + + private static bool ApplyCourseMetadata(CourseRecordData stored, AeryCourseDefinition course) + { + if (stored == null || course == null) + return false; + var displayName = course.DisplayName; + var changed = false; + if (!string.IsNullOrWhiteSpace(displayName) && + !string.Equals(stored.CourseName, displayName, StringComparison.Ordinal)) + { + stored.CourseName = displayName; + changed = true; + } + var providerId = NormalizeProviderId(course.ProviderId); + var providerDisplayName = string.IsNullOrWhiteSpace(course.ProviderDisplayName) + ? providerId.ToUpperInvariant() + : course.ProviderDisplayName.Trim(); + if (!string.Equals(stored.ProviderId, providerId, StringComparison.Ordinal)) + { + stored.ProviderId = providerId; + changed = true; + } + if (!string.Equals(stored.ProviderDisplayName, providerDisplayName, StringComparison.Ordinal)) + { + stored.ProviderDisplayName = providerDisplayName; + changed = true; + } + return changed; + } + + private static string NormalizeProviderId(string providerId) + { + var normalized = (providerId ?? string.Empty).Trim().ToLowerInvariant(); + return string.IsNullOrWhiteSpace(normalized) ? "aery" : normalized; + } + + private static BmsDifficultyTableDefinition FindTableFromCourseId( + string courseId, + IReadOnlyDictionary providerMap, + out string legacyId) + { + foreach (var candidate in providerMap) + { + var prefix = $"{candidate.Key.Trim().ToLowerInvariant()}-sha256:"; + if (!string.IsNullOrWhiteSpace(courseId) && + courseId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + legacyId = candidate.Key; + return candidate.Value; + } + } + legacyId = string.Empty; + return null; + } + + private static string ExtractCourseSuffix(string courseId, string providerId) + { + var prefix = $"{NormalizeProviderId(providerId)}-sha256:"; + return !string.IsNullOrWhiteSpace(courseId) && + courseId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) + ? courseId.Substring(prefix.Length) + : ChartIdentityResolver.HashText(courseId ?? string.Empty); + } + + private static void MergeCourseRecord(CourseRecordData target, CourseRecordData source) + { + if (target == null || source == null) + return; + if (string.IsNullOrWhiteSpace(target.ProviderId)) + target.ProviderId = source.ProviderId; + if (string.IsNullOrWhiteSpace(target.ProviderDisplayName)) + target.ProviderDisplayName = source.ProviderDisplayName; + if (string.IsNullOrWhiteSpace(target.CourseName)) + target.CourseName = source.CourseName; + + var targetRecord = target.Record ??= new AeryCourseRecord(); + var sourceRecord = source.Record; + if (sourceRecord == null) + return; + AeryCourseRecordStore.NormalizeScoreFormat(targetRecord); + AeryCourseRecordStore.NormalizeScoreFormat(sourceRecord); + var targetHadRecord = targetRecord.HasRecord; + targetRecord.HasRecord |= sourceRecord.HasRecord; + targetRecord.Cleared |= sourceRecord.Cleared; + targetRecord.PlayCount = Math.Max(targetRecord.PlayCount, sourceRecord.PlayCount); + targetRecord.ClearCount = Math.Max(targetRecord.ClearCount, sourceRecord.ClearCount); + targetRecord.BestStageReached = Math.Max(targetRecord.BestStageReached, sourceRecord.BestStageReached); + targetRecord.BestScore = Math.Max(targetRecord.BestScore, sourceRecord.BestScore); + targetRecord.BestAccuracyPercent = Math.Max(targetRecord.BestAccuracyPercent, sourceRecord.BestAccuracyPercent); + targetRecord.BestExScore = Math.Max(targetRecord.BestExScore, sourceRecord.BestExScore); + targetRecord.BestExScoreRate = Math.Max(targetRecord.BestExScoreRate, sourceRecord.BestExScoreRate); + targetRecord.BestMaxCombo = Math.Max(targetRecord.BestMaxCombo, sourceRecord.BestMaxCombo); + if (!targetHadRecord || sourceRecord.HasRecord && sourceRecord.BestMissRate < targetRecord.BestMissRate) + targetRecord.BestMissRate = sourceRecord.BestMissRate; + if (sourceRecord.BestTrophyRank > targetRecord.BestTrophyRank) + { + targetRecord.BestTrophyRank = sourceRecord.BestTrophyRank; + targetRecord.BestTrophyName = sourceRecord.BestTrophyName; + } + if (string.IsNullOrWhiteSpace(targetRecord.LastModifier)) + targetRecord.LastModifier = sourceRecord.LastModifier; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/CourseRecordRepository.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/CourseRecordRepository.cs.meta new file mode 100644 index 0000000..51690b2 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/CourseRecordRepository.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 64e967d5b3f99437e98474965baf367f \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/FavoriteChartRepository.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/FavoriteChartRepository.cs new file mode 100644 index 0000000..200e1be --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/FavoriteChartRepository.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProjectQuint.Library; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + [Serializable] + internal sealed class FavoriteChartsDocument + { + public const int CurrentSchemaVersion = 1; + + public int SchemaVersion = CurrentSchemaVersion; + public List ChartIds = new List(); + } + + /// + /// Chart-level favorites live in their own atomic, versioned document. The set + /// intentionally retains IDs whose pack or chart is temporarily unavailable. + /// + internal sealed class FavoriteChartRepository + { + private static readonly Lazy SharedRepository = + new Lazy(() => new FavoriteChartRepository()); + + private readonly AtomicJsonFile _file; + private HashSet _chartIds; + + public FavoriteChartRepository(string path = null, Action logWarning = null) + { + _file = new AtomicJsonFile( + path ?? PersistencePaths.Favorites, + document => document != null && + document.SchemaVersion == FavoriteChartsDocument.CurrentSchemaVersion && + document.ChartIds != null, + logWarning ?? (message => Debug.LogWarning(message))); + } + + public static FavoriteChartRepository Shared => SharedRepository.Value; + + internal void Reload() + { + _chartIds = null; + } + internal int Count + { + get + { + EnsureLoaded(); + return _chartIds.Count; + } + } + + public void Apply(IReadOnlyList songs) + { + EnsureLoaded(); + foreach (var song in songs ?? Array.Empty()) + if (song != null) + song.IsFavorite = _chartIds.Contains(ResolveId(song)); + } + + public bool IsFavorite(SongLibraryEntry song) + { + if (song == null) + return false; + EnsureLoaded(); + return _chartIds.Contains(ResolveId(song)); + } + + public bool Toggle(SongLibraryEntry song) + { + if (song == null) + return false; + + EnsureLoaded(); + var chartId = ResolveId(song); + var isFavorite = _chartIds.Add(chartId); + if (!isFavorite) + _chartIds.Remove(chartId); + song.IsFavorite = isFavorite; + Save(); + return isFavorite; + } + + internal bool ContainsChartId(string chartId) + { + EnsureLoaded(); + return !string.IsNullOrWhiteSpace(chartId) && + _chartIds.Contains(chartId.Trim().ToLowerInvariant()); + } + + private void EnsureLoaded() + { + if (_chartIds != null) + return; + + _chartIds = new HashSet(StringComparer.Ordinal); + if (!_file.TryLoad(out var document, out _)) + return; + + foreach (var chartId in document.ChartIds) + if (!string.IsNullOrWhiteSpace(chartId)) + _chartIds.Add(chartId.Trim().ToLowerInvariant()); + } + + private void Save() + { + _file.Save(new FavoriteChartsDocument + { + ChartIds = _chartIds.OrderBy(id => id, StringComparer.Ordinal).ToList(), + }); + } + + private static string ResolveId(SongLibraryEntry song) => + ChartIdentityResolver.Resolve(song).Value; + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/FavoriteChartRepository.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/FavoriteChartRepository.cs.meta new file mode 100644 index 0000000..c9fcb89 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/FavoriteChartRepository.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2a7afe15940149f4860e71189a75eabd diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/PersistencePaths.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/PersistencePaths.cs new file mode 100644 index 0000000..bb9a887 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/PersistencePaths.cs @@ -0,0 +1,58 @@ +using System.IO; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + internal static class PersistencePaths + { + public const string DefaultProfileId = "local-player"; + + public static string Root => Path.Combine(Application.persistentDataPath, "ProjectQuint"); + public static string Settings => Path.Combine(Root, "settings.json"); + public static string ProfilesRoot => Path.Combine(Root, "profiles"); + public static string DefaultProfileRoot => ProfileRoot(DefaultProfileId); + public static string SongRecords => ProfileFile(DefaultProfileId, "records.json"); + public static string CourseRecords => ProfileFile(DefaultProfileId, "courses.json"); + public static string Favorites => ProfileFile(DefaultProfileId, "favorites.json"); + public static string PlayerProfile => ProfileFile(DefaultProfileId, "profile.json"); + public static string ProfileImage => ProfileFile(DefaultProfileId, "avatar.png"); + public static string PendingPlayCommit => ProfileFile(DefaultProfileId, "pending-play-commit.json"); + public static string ProfileMigrationMarker => ProfileFile(DefaultProfileId, "profile-storage-migration.json"); + public static string LegacyGlobalSongRecords => Path.Combine(Root, "records.json"); + public static string LegacyGlobalCourseRecords => Path.Combine(Root, "courses.json"); + public static string LegacyGlobalFavorites => Path.Combine(Root, "favorites.json"); + public static string SearchAliases => Path.Combine(Root, "search-aliases.json"); + public static string PackRegistry => Path.Combine(Root, "packs.json"); + public static string PackIndexRoot => Path.Combine(Root, "pack-indexes"); + public static string ImportedPackRoot => Path.Combine(Root, "imported-packs"); + public static string DifficultyTableRoot => Path.Combine(Root, "difficulty-tables"); + public static string DifficultyTableRegistry => Path.Combine(DifficultyTableRoot, "registry.json"); + public static string BgaVideoCache => Path.Combine(Root, "bga-video-cache"); + public static string PremixCache => Path.Combine(Root, "premix-cache"); + public static string PinnedPremixCache => Path.Combine(Root, "premix-cache-pinned"); + public static string PinnedPremixManifestRoot => Path.Combine(PinnedPremixCache, "manifests"); + + public static string ProfileRoot(string profileId) => + ProfileRootFor(Root, profileId); + + public static string ProfileFile(string profileId, string fileName) => + Path.Combine(ProfileRoot(profileId), fileName); + + internal static string ProfileRootFor(string root, string profileId) + { + var normalized = NormalizeProfileId(profileId); + return Path.Combine(root, "profiles", normalized); + } + + private static string NormalizeProfileId(string profileId) + { + var value = (profileId ?? string.Empty).Trim(); + if (value.Length == 0) + throw new System.ArgumentException("A profile ID is required.", nameof(profileId)); + foreach (var character in value) + if (!char.IsLetterOrDigit(character) && character != '-' && character != '_') + throw new System.ArgumentException($"Invalid profile ID '{profileId}'.", nameof(profileId)); + return value; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/PersistencePaths.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/PersistencePaths.cs.meta new file mode 100644 index 0000000..b012334 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/PersistencePaths.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1dd94e5a629204d4e85b73d71604b828 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultCommitCoordinator.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultCommitCoordinator.cs new file mode 100644 index 0000000..a348494 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultCommitCoordinator.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ProjectQuint.Core.Progression; +using ProjectQuint.Library; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + [Serializable] + internal sealed class PendingPlayCommitDocument + { + public const int CurrentSchemaVersion = 2; + + public int SchemaVersion = CurrentSchemaVersion; + public PlayResultEvent PlayEvent = new PlayResultEvent(); + } + + internal sealed class PlayResultCommitCoordinator + { + private readonly AtomicJsonFile _pendingFile; + private readonly SongRecordRepository _songRecords; + private readonly PlayerProgressionRepository _progression; + + public PlayResultCommitCoordinator( + string pendingPath = null, + SongRecordRepository songRecords = null, + PlayerProgressionRepository progression = null, + Action logWarning = null) + { + _songRecords = songRecords ?? SongRecordRepository.Shared; + _progression = progression ?? PlayerProgressionRepository.Shared; + _pendingFile = new AtomicJsonFile( + pendingPath ?? PersistencePaths.PendingPlayCommit, + document => document != null && + document.SchemaVersion == PendingPlayCommitDocument.CurrentSchemaVersion && + document.PlayEvent != null && + document.PlayEvent.RulesVersion == PlayResultEvent.CurrentRulesVersion && + !string.IsNullOrWhiteSpace(document.PlayEvent.EventId), + logWarning ?? (message => Debug.LogWarning(message))); + } + + public PlayCommitOutcome Commit(SongLibraryEntry entry, PlayResultEvent playEvent) + { + if (entry == null) + throw new ArgumentNullException(nameof(entry)); + if (playEvent == null) + throw new ArgumentNullException(nameof(playEvent)); + if (string.IsNullOrWhiteSpace(playEvent.EventId)) + throw new ArgumentException("A play event ID is required.", nameof(playEvent)); + + playEvent.NormalizeRatingRules(); + playEvent.ChartId = ChartIdentityResolver.Resolve(entry).Value; + _pendingFile.Save(new PendingPlayCommitDocument { PlayEvent = playEvent }); + + var previousRating = RatingFor(playEvent); + _songRecords.RecordPlay(entry, SongPlayResult.FromEvent(playEvent)); + _songRecords.RecordClearLamp(entry, NormalizeLamp(playEvent.ClearLamp)); + var profileUpdate = _progression.Record(playEvent); + var currentRating = RatingFor(playEvent); + ClearPending(); + + return new PlayCommitOutcome( + profileUpdate, + new RatingUpdate( + playEvent.RatingEligible, + playEvent.PlayRating, + previousRating, + currentRating)); + } + + public bool TryResume(IReadOnlyList songs, out PlayCommitOutcome outcome) + { + outcome = default; + if (!_pendingFile.TryLoad(out var pending, out _)) + return false; + + var chartId = (pending.PlayEvent.ChartId ?? string.Empty).Trim().ToLowerInvariant(); + var entry = (songs ?? Array.Empty()) + .FirstOrDefault(song => song != null && + string.Equals( + ChartIdentityResolver.Resolve(song).Value, + chartId, + StringComparison.Ordinal)); + if (entry == null) + return false; + + outcome = Commit(entry, pending.PlayEvent); + return true; + } + + private RatingSummary RatingFor(PlayResultEvent playEvent) => + playEvent.RatingEligible + ? _songRecords.CalculateRating( + playEvent.ProviderId, + playEvent.ProviderDisplayName, + playEvent.KeyCount, + playEvent.HasScratch) + : default; + + private void ClearPending() + { + DeleteIfPresent(_pendingFile.Path); + DeleteIfPresent(_pendingFile.BackupPath); + DeleteIfPresent(_pendingFile.Path + ".tmp"); + } + + private static void DeleteIfPresent(string path) + { + if (File.Exists(path)) + File.Delete(path); + } + + private static SongClearLamp NormalizeLamp(int value) => + Enum.IsDefined(typeof(SongClearLamp), value) + ? (SongClearLamp)value + : SongClearLamp.None; + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultCommitCoordinator.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultCommitCoordinator.cs.meta new file mode 100644 index 0000000..c3f0afc --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultCommitCoordinator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d33fa6a6a7c7a4770b312c015dae9f54 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultEvent.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultEvent.cs new file mode 100644 index 0000000..2dd4053 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultEvent.cs @@ -0,0 +1,86 @@ +using System; +using ProjectQuint.Core.Progression; + +namespace ProjectQuint.Persistence +{ + [Serializable] + internal sealed class PlayResultEvent + { + public const int CurrentRulesVersion = 2; + + public int RulesVersion = CurrentRulesVersion; + public string EventId = string.Empty; + public string ChartId = string.Empty; + public string PackId = string.Empty; + public string PackDisplayName = string.Empty; + public string ProviderId = string.Empty; + public string ProviderDisplayName = string.Empty; + public string DifficultyLabel = string.Empty; + public string DifficultyValue = string.Empty; + public string Title = string.Empty; + public string Artist = string.Empty; + public double ChartConstant; + public int KeyCount; + public bool HasScratch; + public string LaneModifier = string.Empty; + public string GaugeType = string.Empty; + public bool Completed; + public bool Cleared; + public bool HardClear; + public int ClearLamp; + public int Score; + public double AccuracyPercent; + public int MaxCombo; + public int KoolCount; + public int PurePerfectCount; + public int CoolCount; + public int GoodCount; + public int MissCount; + public int FastCount; + public int LateCount; + public int JudgedUnits; + public int TotalScoringUnits; + public double ActivePlaySeconds; + public long OccurredUtcTicks; + public int RatingRulesVersion = RatingRules.RulesVersion; + public int XpRulesVersion = ProfileLevelRules.XpRulesVersion; + + public bool RatingEligible => + Cleared && + !string.IsNullOrWhiteSpace(ProviderId) && + ChartConstant > 0.0 && + KeyCount >= 4 && KeyCount <= 7 && + !string.Equals(LaneModifier, "RANDOM", StringComparison.OrdinalIgnoreCase); + + public double PlayRating => RatingEligible + ? RatingRules.CalculatePlayRating(ChartConstant, AccuracyPercent, HardClear) + : 0.0; + + public bool NormalizeRatingRules() + { + if (RatingRulesVersion >= RatingRules.RulesVersion) + return false; + + ChartConstant = RatingRules.TryResolveChartConstant( + DifficultyValue, + DifficultyLabel, + out var chartConstant) + ? chartConstant + : 0.0; + RatingRulesVersion = RatingRules.RulesVersion; + return true; + } + } + + internal readonly struct PlayCommitOutcome + { + public PlayCommitOutcome(ProfileProgressUpdate profile, RatingUpdate rating) + { + Profile = profile; + Rating = rating; + } + + public ProfileProgressUpdate Profile { get; } + public RatingUpdate Rating { get; } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultEvent.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultEvent.cs.meta new file mode 100644 index 0000000..58a77c7 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayResultEvent.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a4899436b0069405c8cd06446e491556 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayerProgressionRepository.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayerProgressionRepository.cs new file mode 100644 index 0000000..d9e38b8 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayerProgressionRepository.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProjectQuint.Core.Progression; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + [Serializable] + internal sealed class PlayerProgressionDocument + { + public const int CurrentSchemaVersion = 2; + + public int SchemaVersion = CurrentSchemaVersion; + public string ProfileId = "local-player"; + public string DisplayName = "PLAYER"; + public long CreatedUtcTicks; + public long LastPlayedUtcTicks; + public long TotalXp; + public int XpRulesVersion = ProfileLevelRules.XpRulesVersion; + public int ValidPlayCount; + public double ActivePlaySeconds; + public string LastRatingProviderId = string.Empty; + public string LastRatingProviderDisplayName = string.Empty; + public int LastRatingKeyCount; + public bool LastRatingHasScratch; + public string FeaturedCourseBadgeId = string.Empty; + public List AppliedEventIds = new List(); + public List ChartMilestones = new List(); + } + + [Serializable] + internal sealed class ProfileChartMilestoneData + { + public string ChartId = string.Empty; + public bool Played; + public bool NormalClear; + public bool HardClear; + public bool FullCombo; + public bool AllPure; + } + + internal sealed class PlayerProgressionRepository + { + private static readonly Lazy SharedRepository = + new Lazy(() => new PlayerProgressionRepository()); + + private readonly AtomicJsonFile _file; + private PlayerProgressionDocument _document; + private HashSet _appliedEvents; + private Dictionary _milestones; + + public PlayerProgressionRepository(string path = null, Action logWarning = null) + { + _file = new AtomicJsonFile( + path ?? PersistencePaths.PlayerProfile, + document => document != null && + document.SchemaVersion >= 1 && + document.SchemaVersion <= PlayerProgressionDocument.CurrentSchemaVersion && + document.AppliedEventIds != null && + document.ChartMilestones != null, + logWarning ?? (message => Debug.LogWarning(message))); + } + + public static PlayerProgressionRepository Shared => SharedRepository.Value; + + internal void Reload() + { + _document = null; + _appliedEvents = null; + _milestones = null; + } + + public ProfileProgressSnapshot Snapshot() + { + EnsureLoaded(); + return ToSnapshot(_document); + } + + public string FeaturedCourseBadgeId + { + get + { + EnsureLoaded(); + return _document.FeaturedCourseBadgeId ?? string.Empty; + } + } + + public void SetFeaturedCourseBadge(string courseId) + { + EnsureLoaded(); + var normalized = (courseId ?? string.Empty).Trim().ToLowerInvariant(); + if (string.Equals(_document.FeaturedCourseBadgeId, normalized, StringComparison.Ordinal)) + return; + _document.FeaturedCourseBadgeId = normalized; + Save(); + } + + public ProfileProgressUpdate Record(PlayResultEvent playEvent) + { + if (playEvent == null) + throw new ArgumentNullException(nameof(playEvent)); + if (string.IsNullOrWhiteSpace(playEvent.EventId)) + throw new ArgumentException("A play event ID is required.", nameof(playEvent)); + + EnsureLoaded(); + var previous = ToSnapshot(_document); + if (_appliedEvents.Contains(playEvent.EventId)) + return new ProfileProgressUpdate(false, 0, previous, previous, false, false, false, false, false); + + _appliedEvents.Add(playEvent.EventId); + _document.AppliedEventIds.Add(playEvent.EventId); + var validPlay = playEvent.Completed || + playEvent.ActivePlaySeconds >= 30.0 && playEvent.JudgedUnits >= 20; + var firstPlay = false; + var firstNormalClear = false; + var firstHardClear = false; + var firstFullCombo = false; + var firstAllPure = false; + var xp = 0; + + if (validPlay) + { + _document.ValidPlayCount++; + _document.ActivePlaySeconds += Math.Max(0.0, playEvent.ActivePlaySeconds); + xp = ProfileLevelRules.CalculateBaseXp( + playEvent.ActivePlaySeconds, + playEvent.JudgedUnits); + if (playEvent.Cleared) + xp += playEvent.HardClear ? 30 : 20; + + var chartId = (playEvent.ChartId ?? string.Empty).Trim().ToLowerInvariant(); + if (!_milestones.TryGetValue(chartId, out var milestone)) + { + milestone = new ProfileChartMilestoneData { ChartId = chartId }; + _milestones[chartId] = milestone; + _document.ChartMilestones.Add(milestone); + } + + if (!milestone.Played) + { + milestone.Played = true; + firstPlay = true; + xp += 20; + } + if (playEvent.Cleared && !milestone.NormalClear) + { + milestone.NormalClear = true; + firstNormalClear = true; + xp += 40; + } + if (playEvent.HardClear && !milestone.HardClear) + { + milestone.HardClear = true; + firstHardClear = true; + xp += 60; + } + if (playEvent.Cleared && playEvent.MissCount == 0 && !milestone.FullCombo) + { + milestone.FullCombo = true; + firstFullCombo = true; + xp += 80; + } + if (playEvent.Cleared && playEvent.TotalScoringUnits > 0 && + playEvent.PurePerfectCount >= playEvent.TotalScoringUnits && !milestone.AllPure) + { + milestone.AllPure = true; + firstAllPure = true; + xp += 120; + } + + _document.TotalXp += xp; + _document.LastPlayedUtcTicks = playEvent.OccurredUtcTicks > 0 + ? playEvent.OccurredUtcTicks + : DateTime.UtcNow.Ticks; + if (playEvent.RatingEligible) + { + _document.LastRatingProviderId = playEvent.ProviderId ?? string.Empty; + _document.LastRatingProviderDisplayName = string.IsNullOrWhiteSpace(playEvent.ProviderDisplayName) + ? _document.LastRatingProviderId + : playEvent.ProviderDisplayName; + _document.LastRatingKeyCount = playEvent.KeyCount; + _document.LastRatingHasScratch = playEvent.HasScratch; + } + } + + Save(); + return new ProfileProgressUpdate( + true, + xp, + previous, + ToSnapshot(_document), + firstPlay, + firstNormalClear, + firstHardClear, + firstFullCombo, + firstAllPure); + } + + private void EnsureLoaded() + { + if (_document != null) + return; + + if (!_file.TryLoad(out _document, out _)) + { + _document = new PlayerProgressionDocument + { + CreatedUtcTicks = DateTime.UtcNow.Ticks, + }; + } + else if (_document.SchemaVersion < PlayerProgressionDocument.CurrentSchemaVersion) + { + _document = new PlayerProgressionDocument + { + ProfileId = string.IsNullOrWhiteSpace(_document.ProfileId) + ? PersistencePaths.DefaultProfileId + : _document.ProfileId, + DisplayName = string.IsNullOrWhiteSpace(_document.DisplayName) + ? "PLAYER" + : _document.DisplayName, + CreatedUtcTicks = _document.CreatedUtcTicks > 0 + ? _document.CreatedUtcTicks + : DateTime.UtcNow.Ticks, + }; + Save(); + } + + _document.AppliedEventIds ??= new List(); + _document.ChartMilestones ??= new List(); + _document.FeaturedCourseBadgeId ??= string.Empty; + _appliedEvents = new HashSet( + _document.AppliedEventIds.Where(value => !string.IsNullOrWhiteSpace(value)), + StringComparer.Ordinal); + _milestones = _document.ChartMilestones + .Where(item => item != null && !string.IsNullOrWhiteSpace(item.ChartId)) + .GroupBy(item => item.ChartId.Trim().ToLowerInvariant(), StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.Last(), StringComparer.Ordinal); + } + + private void Save() + { + _document.SchemaVersion = PlayerProgressionDocument.CurrentSchemaVersion; + _document.XpRulesVersion = ProfileLevelRules.XpRulesVersion; + _file.Save(_document); + } + + private static ProfileProgressSnapshot ToSnapshot(PlayerProgressionDocument document) => + new ProfileProgressSnapshot( + document?.DisplayName, + document?.TotalXp ?? 0, + document?.ValidPlayCount ?? 0, + document?.ActivePlaySeconds ?? 0.0, + document?.LastRatingProviderId, + document?.LastRatingProviderDisplayName, + document?.LastRatingKeyCount ?? 0, + document?.LastRatingHasScratch == true); + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayerProgressionRepository.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayerProgressionRepository.cs.meta new file mode 100644 index 0000000..21bed9c --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/PlayerProgressionRepository.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fe5bc7cd44cbc4b1f974edbf6a7341e6 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ProductIdentityStorageMigrator.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProductIdentityStorageMigrator.cs new file mode 100644 index 0000000..4f48cc4 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProductIdentityStorageMigrator.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + internal enum ProductIdentityStorageMigrationStatus + { + NoLegacyData, + Migrated, + DestinationConflict, + } + + internal readonly struct ProductIdentityStorageMigrationResult + { + public ProductIdentityStorageMigrationResult( + ProductIdentityStorageMigrationStatus status, + string sourcePath, + string destinationPath) + { + Status = status; + SourcePath = sourcePath ?? string.Empty; + DestinationPath = destinationPath ?? string.Empty; + } + + public ProductIdentityStorageMigrationStatus Status { get; } + public string SourcePath { get; } + public string DestinationPath { get; } + } + + internal static class ProductIdentityStorageMigrator + { + private const string LegacyCompanyName = "DefaultCompany"; + private const string ProductName = "ProjectQuint"; + private const string UnityBootstrapDirectoryName = "Unity"; + private const string UnityTestResultsFileName = "TestResults.xml"; + + public static ProductIdentityStorageMigrationResult MigrateLegacyDefaultCompany() + { + var destination = Path.GetFullPath(Application.persistentDataPath); + var companyDirectory = Directory.GetParent(destination); + var applicationSupportDirectory = companyDirectory?.Parent; + if (applicationSupportDirectory == null) + throw new InvalidOperationException( + $"Could not resolve the macOS Application Support root from '{destination}'."); + var source = Path.Combine( + applicationSupportDirectory.FullName, + LegacyCompanyName, + ProductName); + return Migrate(source, destination); + } + + internal static ProductIdentityStorageMigrationResult Migrate( + string sourcePath, + string destinationPath) + { + if (string.IsNullOrWhiteSpace(sourcePath) || string.IsNullOrWhiteSpace(destinationPath)) + throw new ArgumentException("Legacy and current product data paths are required."); + + var source = Path.GetFullPath(sourcePath); + var destination = Path.GetFullPath(destinationPath); + if (string.Equals(source, destination, StringComparison.Ordinal) || !Directory.Exists(source)) + { + return new ProductIdentityStorageMigrationResult( + ProductIdentityStorageMigrationStatus.NoLegacyData, + source, + destination); + } + + if (Directory.Exists(destination)) + { + if (ContainsUserData(destination)) + { + return new ProductIdentityStorageMigrationResult( + ProductIdentityStorageMigrationStatus.DestinationConflict, + source, + destination); + } + + // Unity can create Insights/Analytics data and TestResults.xml before the + // first MonoBehaviour Awake. Those bootstrap artifacts must not make a new + // product identity look like an independently populated user-data root. + Directory.Delete(destination, true); + } + + var destinationParent = Path.GetDirectoryName(destination); + if (string.IsNullOrWhiteSpace(destinationParent)) + throw new InvalidOperationException($"Product data destination has no parent: {destination}"); + Directory.CreateDirectory(destinationParent); + Directory.Move(source, destination); + return new ProductIdentityStorageMigrationResult( + ProductIdentityStorageMigrationStatus.Migrated, + source, + destination); + } + + private static bool ContainsUserData(string destination) + { + foreach (var entry in Directory.EnumerateFileSystemEntries(destination)) + { + var name = Path.GetFileName(entry); + if (Directory.Exists(entry)) + { + if (string.Equals(name, UnityBootstrapDirectoryName, + StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (Directory.GetFileSystemEntries(entry).Length == 0) + continue; + + return true; + } + + if (string.Equals(name, UnityTestResultsFileName, + StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + return true; + } + + return false; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ProductIdentityStorageMigrator.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProductIdentityStorageMigrator.cs.meta new file mode 100644 index 0000000..43c2860 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProductIdentityStorageMigrator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8bab4fcd7da9f47f39c919c06994e909 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileArchiveService.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileArchiveService.cs new file mode 100644 index 0000000..f033495 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileArchiveService.cs @@ -0,0 +1,580 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + [Serializable] + internal sealed class ProfileArchiveManifest + { + public const int CurrentFormatVersion = 1; + public const string FormatName = "projectquint-profile-export"; + + public string format = FormatName; + public int formatVersion = CurrentFormatVersion; + public string exportId = string.Empty; + public string createdAtUtc = string.Empty; + public string appVersion = string.Empty; + public ProfileArchiveIdentity profile = new ProfileArchiveIdentity(); + public List documents = new List(); + } + + [Serializable] + internal sealed class ProfileArchiveIdentity + { + public string id = PersistencePaths.DefaultProfileId; + public string displayName = "PLAYER"; + } + + [Serializable] + internal sealed class ProfileArchiveDocumentEntry + { + public string path = string.Empty; + public int schemaVersion; + public string sha256 = string.Empty; + public long bytes; + } + + [Serializable] + internal sealed class ProfileImportJournal + { + public const int CurrentSchemaVersion = 1; + + public int SchemaVersion = CurrentSchemaVersion; + public string ProfileId = PersistencePaths.DefaultProfileId; + public string TargetPath = string.Empty; + public string StagingPath = string.Empty; + public string BackupPath = string.Empty; + } + + internal readonly struct ProfileArchiveResult + { + private ProfileArchiveResult(bool success, string status, string archivePath, string backupPath) + { + Success = success; + Status = status ?? string.Empty; + ArchivePath = archivePath ?? string.Empty; + BackupPath = backupPath ?? string.Empty; + } + + public bool Success { get; } + public string Status { get; } + public string ArchivePath { get; } + public string BackupPath { get; } + + public static ProfileArchiveResult Succeeded(string status, string archivePath, string backupPath = "") => + new ProfileArchiveResult(true, status, archivePath, backupPath); + + public static ProfileArchiveResult Failed(string status, string archivePath = "") => + new ProfileArchiveResult(false, status, archivePath, string.Empty); + } + + internal sealed class ProfileArchiveService + { + public const string FileExtension = ".pqprofile"; + private const string ManifestPath = "manifest.json"; + private const long MaximumManifestBytes = 1024 * 1024; + private const long MaximumDocumentBytes = 32L * 1024 * 1024; + private const long MaximumArchiveContentBytes = 128L * 1024 * 1024; + + private static readonly ProfileDocumentDefinition[] DocumentDefinitions = + { + new ProfileDocumentDefinition("profile/profile.json", "profile.json"), + new ProfileDocumentDefinition("profile/records.json", "records.json"), + new ProfileDocumentDefinition("profile/courses.json", "courses.json"), + new ProfileDocumentDefinition("profile/favorites.json", "favorites.json"), + }; + + private readonly string _root; + private readonly Func _utcNow; + private readonly string _appVersion; + + public ProfileArchiveService( + string root = null, + Func utcNow = null, + string appVersion = null) + { + _root = string.IsNullOrWhiteSpace(root) ? PersistencePaths.Root : Path.GetFullPath(root); + _utcNow = utcNow ?? (() => DateTime.UtcNow); + _appVersion = string.IsNullOrWhiteSpace(appVersion) ? Application.version : appVersion; + } + + public ProfileArchiveResult Export(string profileId, string destinationPath) + { + try + { + profileId = NormalizeProfileId(profileId); + destinationPath = NormalizeExportPath(destinationPath); + var sourceRoot = ProfileRoot(profileId); + var payloads = new Dictionary(StringComparer.Ordinal); + foreach (var definition in DocumentDefinitions) + payloads.Add( + definition.ArchivePath, + LoadDocumentForExport(sourceRoot, definition, profileId)); + + var progression = JsonUtility.FromJson( + Encoding.UTF8.GetString(payloads["profile/profile.json"].Bytes)); + var manifest = new ProfileArchiveManifest + { + exportId = Guid.NewGuid().ToString("D"), + createdAtUtc = _utcNow().ToUniversalTime().ToString("O"), + appVersion = _appVersion, + profile = new ProfileArchiveIdentity + { + id = profileId, + displayName = string.IsNullOrWhiteSpace(progression?.DisplayName) + ? "PLAYER" + : progression.DisplayName, + }, + }; + foreach (var definition in DocumentDefinitions) + { + var payload = payloads[definition.ArchivePath]; + manifest.documents.Add(new ProfileArchiveDocumentEntry + { + path = definition.ArchivePath, + schemaVersion = payload.SchemaVersion, + sha256 = Sha256(payload.Bytes), + bytes = payload.Bytes.LongLength, + }); + } + + var destinationDirectory = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrWhiteSpace(destinationDirectory)) + Directory.CreateDirectory(destinationDirectory); + var temporaryPath = destinationPath + ".tmp-" + Guid.NewGuid().ToString("N"); + try + { + using (var stream = new FileStream( + temporaryPath, + FileMode.CreateNew, + FileAccess.ReadWrite, + FileShare.None, + 4096, + FileOptions.WriteThrough)) + using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: false)) + { + foreach (var definition in DocumentDefinitions) + WriteEntry(archive, definition.ArchivePath, payloads[definition.ArchivePath].Bytes); + WriteEntry( + archive, + ManifestPath, + new UTF8Encoding(false).GetBytes(JsonUtility.ToJson(manifest, true))); + } + if (File.Exists(destinationPath)) + File.Replace(temporaryPath, destinationPath, destinationPath + ".bak"); + else + File.Move(temporaryPath, destinationPath); + } + finally + { + if (File.Exists(temporaryPath)) + File.Delete(temporaryPath); + } + + return ProfileArchiveResult.Succeeded( + $"PROFILE EXPORTED // {Path.GetFileName(destinationPath)}", + destinationPath); + } + catch (Exception exception) + { + return ProfileArchiveResult.Failed($"PROFILE EXPORT FAILED // {exception.Message}", destinationPath); + } + } + + public ProfileArchiveResult Import(string archivePath, string targetProfileId) + { + string stagingPath = null; + string backupPath = null; + string targetPath = null; + var targetMoved = false; + try + { + targetProfileId = NormalizeProfileId(targetProfileId); + archivePath = Path.GetFullPath(archivePath ?? string.Empty); + if (!File.Exists(archivePath)) + throw new FileNotFoundException("Profile archive was not found.", archivePath); + if (!string.Equals(Path.GetExtension(archivePath), FileExtension, StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException($"Profile archive must use the {FileExtension} extension."); + + RecoverInterruptedImport(targetProfileId); + var imported = ReadAndValidateArchive(archivePath); + var profilesRoot = ProfilesRoot; + Directory.CreateDirectory(profilesRoot); + targetPath = ProfileRoot(targetProfileId); + stagingPath = Path.Combine(profilesRoot, $".{targetProfileId}.import-{Guid.NewGuid():N}"); + Directory.CreateDirectory(stagingPath); + foreach (var definition in DocumentDefinitions) + WriteFileDurably( + Path.Combine(stagingPath, definition.FileName), + imported[definition.ArchivePath].Bytes); + + backupPath = BuildBackupPath(targetProfileId); + var journal = new ProfileImportJournal + { + ProfileId = targetProfileId, + TargetPath = targetPath, + StagingPath = stagingPath, + BackupPath = backupPath, + }; + WriteJournal(journal); + + if (Directory.Exists(targetPath)) + { + Directory.CreateDirectory(Path.GetDirectoryName(backupPath)); + Directory.Move(targetPath, backupPath); + targetMoved = true; + } + Directory.Move(stagingPath, targetPath); + stagingPath = null; + DeleteJournal(); + return ProfileArchiveResult.Succeeded( + targetMoved + ? $"PROFILE IMPORTED // BACKUP {Path.GetFileName(backupPath)}" + : "PROFILE IMPORTED // NEW LOCAL PROFILE", + archivePath, + targetMoved ? backupPath : string.Empty); + } + catch (Exception exception) + { + if (targetMoved && !string.IsNullOrWhiteSpace(targetPath) && !Directory.Exists(targetPath) && + !string.IsNullOrWhiteSpace(backupPath) && Directory.Exists(backupPath)) + Directory.Move(backupPath, targetPath); + DeleteJournal(); + if (!string.IsNullOrWhiteSpace(stagingPath) && Directory.Exists(stagingPath)) + Directory.Delete(stagingPath, true); + return ProfileArchiveResult.Failed($"PROFILE IMPORT FAILED // {exception.Message}", archivePath); + } + } + + internal void RecoverInterruptedImport(string profileId) + { + profileId = NormalizeProfileId(profileId); + if (!File.Exists(JournalPath)) + return; + + var journal = JsonUtility.FromJson(File.ReadAllText(JournalPath, Encoding.UTF8)); + if (journal == null || journal.SchemaVersion != ProfileImportJournal.CurrentSchemaVersion || + !string.Equals(journal.ProfileId, profileId, StringComparison.Ordinal) || + !IsExpectedTargetPath(journal.TargetPath, profileId) || + !IsExpectedStagingPath(journal.StagingPath, profileId) || + !IsExpectedBackupPath(journal.BackupPath, profileId)) + throw new InvalidDataException("Profile import journal is invalid."); + + if (Directory.Exists(journal.TargetPath)) + { + if (Directory.Exists(journal.StagingPath)) + Directory.Delete(journal.StagingPath, true); + DeleteJournal(); + return; + } + if (Directory.Exists(journal.StagingPath)) + { + Directory.Move(journal.StagingPath, journal.TargetPath); + DeleteJournal(); + return; + } + if (Directory.Exists(journal.BackupPath)) + { + Directory.Move(journal.BackupPath, journal.TargetPath); + DeleteJournal(); + return; + } + throw new InvalidDataException("Profile import journal cannot recover its target, staging or backup."); + } + + private Dictionary ReadAndValidateArchive(string archivePath) + { + using var stream = File.OpenRead(archivePath); + using var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false); + var allowed = new HashSet( + DocumentDefinitions.Select(item => item.ArchivePath).Append(ManifestPath), + StringComparer.Ordinal); + var entries = new Dictionary(StringComparer.Ordinal); + foreach (var entry in archive.Entries) + { + if (!allowed.Contains(entry.FullName) || !entries.TryAdd(entry.FullName, entry)) + throw new InvalidDataException($"Unexpected or duplicate archive entry '{entry.FullName}'."); + } + if (entries.Count != allowed.Count || allowed.Any(path => !entries.ContainsKey(path))) + throw new InvalidDataException("Profile archive is missing required entries."); + + var manifestBytes = ReadEntryBounded(entries[ManifestPath], MaximumManifestBytes); + var manifest = JsonUtility.FromJson(Encoding.UTF8.GetString(manifestBytes)); + if (manifest == null || !string.Equals(manifest.format, ProfileArchiveManifest.FormatName, StringComparison.Ordinal) || + manifest.formatVersion != ProfileArchiveManifest.CurrentFormatVersion || manifest.profile == null || + manifest.documents == null) + throw new InvalidDataException("Profile archive manifest format is unsupported."); + NormalizeProfileId(manifest.profile.id); + + var manifestEntries = manifest.documents + .Where(item => item != null && !string.IsNullOrWhiteSpace(item.path)) + .GroupBy(item => item.path, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.ToArray(), StringComparer.Ordinal); + if (manifestEntries.Count != DocumentDefinitions.Length || manifestEntries.Values.Any(group => group.Length != 1)) + throw new InvalidDataException("Profile archive manifest document list is invalid."); + + var result = new Dictionary(StringComparer.Ordinal); + long totalBytes = 0; + foreach (var definition in DocumentDefinitions) + { + if (!manifestEntries.TryGetValue(definition.ArchivePath, out var listed)) + throw new InvalidDataException($"Manifest is missing '{definition.ArchivePath}'."); + var manifestEntry = listed[0]; + var bytes = ReadEntryBounded(entries[definition.ArchivePath], MaximumDocumentBytes); + totalBytes = checked(totalBytes + bytes.LongLength); + if (totalBytes > MaximumArchiveContentBytes) + throw new InvalidDataException("Profile archive exceeds the uncompressed size limit."); + var schemaVersion = ValidateDocument(definition.FileName, bytes); + if (manifestEntry.bytes != bytes.LongLength || manifestEntry.schemaVersion != schemaVersion || + !string.Equals(manifestEntry.sha256, Sha256(bytes), StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException($"Manifest integrity check failed for '{definition.ArchivePath}'."); + result.Add(definition.ArchivePath, new ProfileDocumentPayload(bytes, schemaVersion)); + } + return result; + } + + private ProfileDocumentPayload LoadDocumentForExport( + string profileRoot, + ProfileDocumentDefinition definition, + string profileId) + { + var path = Path.Combine(profileRoot, definition.FileName); + foreach (var candidate in new[] { path, path + ".bak" }) + { + if (!File.Exists(candidate)) + continue; + try + { + var bytes = File.ReadAllBytes(candidate); + return new ProfileDocumentPayload(bytes, ValidateDocument(definition.FileName, bytes)); + } + catch when (!string.Equals(candidate, path + ".bak", StringComparison.Ordinal)) + { + // The backup candidate is validated next. If it also fails, export fails explicitly. + } + } + + if (File.Exists(path) || File.Exists(path + ".bak")) + throw new InvalidDataException($"'{definition.FileName}' and its backup are invalid."); + var defaultBytes = CreateDefaultDocument(definition.FileName, profileId); + return new ProfileDocumentPayload(defaultBytes, ValidateDocument(definition.FileName, defaultBytes)); + } + + private byte[] CreateDefaultDocument(string fileName, string profileId) + { + object document = fileName switch + { + "profile.json" => new PlayerProgressionDocument + { + ProfileId = profileId, + CreatedUtcTicks = _utcNow().ToUniversalTime().Ticks, + }, + "records.json" => new SongRecordsDocument(), + "courses.json" => new CourseRecordsDocument(), + "favorites.json" => new FavoriteChartsDocument(), + _ => throw new InvalidDataException($"Unsupported profile document '{fileName}'."), + }; + return new UTF8Encoding(false).GetBytes(JsonUtility.ToJson(document, true)); + } + + private static int ValidateDocument(string fileName, byte[] bytes) + { + if (bytes == null || bytes.LongLength == 0 || bytes.LongLength > MaximumDocumentBytes) + throw new InvalidDataException($"'{fileName}' has an invalid size."); + var json = Encoding.UTF8.GetString(bytes); + switch (fileName) + { + case "profile.json": + { + var document = JsonUtility.FromJson(json); + if (document == null || document.SchemaVersion != PlayerProgressionDocument.CurrentSchemaVersion || + document.AppliedEventIds == null || document.ChartMilestones == null) + throw new InvalidDataException("profile.json schema is unsupported."); + return document.SchemaVersion; + } + case "records.json": + { + var document = JsonUtility.FromJson(json); + if (document == null || document.SchemaVersion < 3 || + document.SchemaVersion > SongRecordsDocument.CurrentSchemaVersion || document.Records == null) + throw new InvalidDataException("records.json schema is unsupported."); + return document.SchemaVersion; + } + case "courses.json": + { + var document = JsonUtility.FromJson(json); + if (document == null || document.SchemaVersion < 3 || + document.SchemaVersion > CourseRecordsDocument.CurrentSchemaVersion || document.Records == null) + throw new InvalidDataException("courses.json schema is unsupported."); + return document.SchemaVersion; + } + case "favorites.json": + { + var document = JsonUtility.FromJson(json); + if (document == null || document.SchemaVersion != FavoriteChartsDocument.CurrentSchemaVersion || + document.ChartIds == null) + throw new InvalidDataException("favorites.json schema is unsupported."); + return document.SchemaVersion; + } + default: + throw new InvalidDataException($"Unsupported profile document '{fileName}'."); + } + } + + private void WriteJournal(ProfileImportJournal journal) + { + Directory.CreateDirectory(ProfilesRoot); + WriteFileDurably(JournalPath, new UTF8Encoding(false).GetBytes(JsonUtility.ToJson(journal, true))); + } + + private void DeleteJournal() + { + if (File.Exists(JournalPath)) + File.Delete(JournalPath); + } + + private string BuildBackupPath(string profileId) + { + var root = Path.Combine(ProfilesRoot, ".backups"); + var stamp = _utcNow().ToUniversalTime().ToString("yyyyMMdd-HHmmss-fff"); + var candidate = Path.Combine(root, $"{profileId}-{stamp}"); + for (var suffix = 1; Directory.Exists(candidate); suffix++) + candidate = Path.Combine(root, $"{profileId}-{stamp}-{suffix}"); + return candidate; + } + + private bool IsExpectedTargetPath(string path, string profileId) => + PathsEqual(path, ProfileRoot(profileId)); + + private bool IsExpectedStagingPath(string path, string profileId) + { + if (!TryGetSafeChild(path, ProfilesRoot, out var name)) + return false; + return name.StartsWith($".{profileId}.import-", StringComparison.Ordinal) && + name.Length > profileId.Length + ".import-".Length + 1; + } + + private bool IsExpectedBackupPath(string path, string profileId) => + TryGetSafeChild(path, Path.Combine(ProfilesRoot, ".backups"), out var name) && + name.StartsWith(profileId + "-", StringComparison.Ordinal) && + name.Length > profileId.Length + 1; + + private static bool TryGetSafeChild(string path, string parent, out string name) + { + name = string.Empty; + if (string.IsNullOrWhiteSpace(path) || string.IsNullOrWhiteSpace(parent)) + return false; + var full = Path.GetFullPath(path); + var fullParent = Path.GetFullPath(parent); + if (!string.Equals(Path.GetDirectoryName(full), fullParent, StringComparison.Ordinal)) + return false; + name = Path.GetFileName(full); + return name.Length > 0; + } + + private static bool PathsEqual(string left, string right) => + !string.IsNullOrWhiteSpace(left) && !string.IsNullOrWhiteSpace(right) && + string.Equals(Path.GetFullPath(left), Path.GetFullPath(right), StringComparison.Ordinal); + + private string ProfilesRoot => Path.Combine(_root, "profiles"); + private string JournalPath => Path.Combine(ProfilesRoot, ".profile-import-journal.json"); + private string ProfileRoot(string profileId) => Path.Combine(ProfilesRoot, profileId); + + private static string NormalizeProfileId(string profileId) + { + var value = (profileId ?? string.Empty).Trim(); + if (value.Length == 0) + throw new ArgumentException("A profile ID is required.", nameof(profileId)); + foreach (var character in value) + if (!char.IsLetterOrDigit(character) && character != '-' && character != '_') + throw new ArgumentException($"Invalid profile ID '{profileId}'.", nameof(profileId)); + return value; + } + + private static string NormalizeExportPath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("An export destination is required.", nameof(path)); + var normalized = Path.GetFullPath(path.Trim()); + return string.Equals(Path.GetExtension(normalized), FileExtension, StringComparison.OrdinalIgnoreCase) + ? normalized + : normalized + FileExtension; + } + + private static byte[] ReadEntryBounded(ZipArchiveEntry entry, long maximumBytes) + { + if (entry == null || entry.Length < 0 || entry.Length > maximumBytes) + throw new InvalidDataException($"Archive entry '{entry?.FullName}' exceeds its size limit."); + using var source = entry.Open(); + using var destination = new MemoryStream((int)Math.Min(entry.Length, int.MaxValue)); + var buffer = new byte[81920]; + int read; + while ((read = source.Read(buffer, 0, buffer.Length)) > 0) + { + if (destination.Length + read > maximumBytes) + throw new InvalidDataException($"Archive entry '{entry.FullName}' exceeds its size limit."); + destination.Write(buffer, 0, read); + } + return destination.ToArray(); + } + + private static void WriteEntry(ZipArchive archive, string path, byte[] bytes) + { + var entry = archive.CreateEntry(path, System.IO.Compression.CompressionLevel.Optimal); + using var stream = entry.Open(); + stream.Write(bytes, 0, bytes.Length); + } + + private static void WriteFileDurably(string path, byte[] bytes) + { + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directory)) + Directory.CreateDirectory(directory); + using var stream = new FileStream( + path, + FileMode.CreateNew, + FileAccess.Write, + FileShare.None, + 4096, + FileOptions.WriteThrough); + stream.Write(bytes, 0, bytes.Length); + stream.Flush(true); + } + + private static string Sha256(byte[] bytes) + { + using var sha = SHA256.Create(); + return string.Concat(sha.ComputeHash(bytes).Select(value => value.ToString("x2"))); + } + + private readonly struct ProfileDocumentDefinition + { + public ProfileDocumentDefinition(string archivePath, string fileName) + { + ArchivePath = archivePath; + FileName = fileName; + } + + public string ArchivePath { get; } + public string FileName { get; } + } + + private readonly struct ProfileDocumentPayload + { + public ProfileDocumentPayload(byte[] bytes, int schemaVersion) + { + Bytes = bytes; + SchemaVersion = schemaVersion; + } + + public byte[] Bytes { get; } + public int SchemaVersion { get; } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileArchiveService.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileArchiveService.cs.meta new file mode 100644 index 0000000..a0adaf6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileArchiveService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5e106eda63b324fe391a950b76ec3c8d \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileImageStore.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileImageStore.cs new file mode 100644 index 0000000..7681a97 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileImageStore.cs @@ -0,0 +1,250 @@ +using System; +using System.IO; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + internal readonly struct ProfileImageOperationResult + { + public ProfileImageOperationResult( + bool success, + string status, + Texture2D texture = null, + string recoveryPath = null) + { + Success = success; + Status = status ?? string.Empty; + Texture = texture; + RecoveryPath = recoveryPath ?? string.Empty; + } + + public bool Success { get; } + public string Status { get; } + public Texture2D Texture { get; } + public string RecoveryPath { get; } + } + + /// + /// Owns the local profile avatar file. Imports are normalized to a centered, + /// square 256 px PNG so retained UI never decodes an arbitrary full-size photo. + /// + internal sealed class ProfileImageStore + { + internal const int OutputSize = 256; + internal const int MaximumSourceDimension = 8192; + internal const long MaximumSourceBytes = 32L * 1024L * 1024L; + + private readonly string _imagePath; + private readonly Func _utcNow; + + public ProfileImageStore( + string imagePath = null, + Func utcNow = null) + { + _imagePath = Path.GetFullPath( + string.IsNullOrWhiteSpace(imagePath) + ? PersistencePaths.ProfileImage + : imagePath); + _utcNow = utcNow ?? (() => DateTime.UtcNow); + } + + public string ImagePath => _imagePath; + + public bool TryLoad(out Texture2D texture, out string error) + { + texture = null; + error = string.Empty; + if (!File.Exists(_imagePath)) + return false; + + try + { + var file = new FileInfo(_imagePath); + if (file.Length <= 0 || file.Length > MaximumSourceBytes) + throw new InvalidDataException("The saved profile photo has an invalid file size."); + + var bytes = File.ReadAllBytes(_imagePath); + texture = Decode(bytes, markNonReadable: true); + if (texture.width != OutputSize || texture.height != OutputSize) + throw new InvalidDataException( + $"The saved profile photo must be {OutputSize} × {OutputSize} pixels."); + texture.name = "ProjectQuint Profile Avatar"; + return true; + } + catch (Exception exception) + { + DestroyTexture(texture); + texture = null; + error = exception.Message; + return false; + } + } + + public ProfileImageOperationResult Import(string sourcePath) + { + Texture2D source = null; + Texture2D normalized = null; + try + { + if (string.IsNullOrWhiteSpace(sourcePath)) + return Failure("PHOTO IMPORT CANCELLED"); + + var fullSourcePath = Path.GetFullPath(sourcePath); + if (!File.Exists(fullSourcePath)) + return Failure("PHOTO FILE NOT FOUND"); + if (!IsSupportedExtension(Path.GetExtension(fullSourcePath))) + return Failure("PHOTO MUST BE PNG OR JPEG"); + + var file = new FileInfo(fullSourcePath); + if (file.Length <= 0) + return Failure("PHOTO FILE IS EMPTY"); + if (file.Length > MaximumSourceBytes) + return Failure("PHOTO IS LARGER THAN 32 MB"); + + source = Decode(File.ReadAllBytes(fullSourcePath), markNonReadable: false); + if (source.width <= 0 || source.height <= 0 || + source.width > MaximumSourceDimension || + source.height > MaximumSourceDimension) + { + return Failure("PHOTO DIMENSIONS MUST BE BETWEEN 1 AND 8192 PX"); + } + + normalized = CenterCrop(source); + var png = ImageConversion.EncodeToPNG(normalized); + if (png == null || png.Length == 0) + throw new InvalidDataException("Could not encode the cropped profile photo."); + + WriteAtomically(png); + normalized.name = "ProjectQuint Profile Avatar"; + normalized.Apply(updateMipmaps: false, makeNoLongerReadable: true); + var result = new ProfileImageOperationResult( + success: true, + status: "PROFILE PHOTO UPDATED", + texture: normalized); + normalized = null; + return result; + } + catch (Exception exception) + { + return Failure($"PHOTO IMPORT FAILED // {exception.Message}"); + } + finally + { + DestroyTexture(source); + DestroyTexture(normalized); + } + } + + public ProfileImageOperationResult Reset() + { + try + { + if (!File.Exists(_imagePath)) + return new ProfileImageOperationResult(true, "USING DEFAULT K MONOGRAM"); + + var directory = Path.GetDirectoryName(_imagePath); + if (string.IsNullOrWhiteSpace(directory)) + throw new InvalidDataException("The profile photo directory is unavailable."); + Directory.CreateDirectory(directory); + var recoveryPath = Path.Combine( + directory, + $"avatar.removed-{_utcNow():yyyyMMdd-HHmmssfff}-{Guid.NewGuid():N}.png"); + File.Move(_imagePath, recoveryPath); + return new ProfileImageOperationResult( + success: true, + status: "PROFILE PHOTO RESET TO K", + recoveryPath: recoveryPath); + } + catch (Exception exception) + { + return Failure($"PHOTO RESET FAILED // {exception.Message}"); + } + } + + private static ProfileImageOperationResult Failure(string status) => + new ProfileImageOperationResult(false, status); + + private static bool IsSupportedExtension(string extension) => + string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase) || + string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) || + string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase) || + string.Equals(extension, ".jfif", StringComparison.OrdinalIgnoreCase); + + private static Texture2D Decode(byte[] bytes, bool markNonReadable) + { + var texture = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false); + if (!ImageConversion.LoadImage(texture, bytes, markNonReadable)) + { + DestroyTexture(texture); + throw new InvalidDataException("The selected file is not a readable PNG or JPEG image."); + } + return texture; + } + + private static Texture2D CenterCrop(Texture2D source) + { + var cropSize = Math.Min(source.width, source.height); + var cropX = (source.width - cropSize) * 0.5f; + var cropY = (source.height - cropSize) * 0.5f; + var output = new Texture2D( + OutputSize, + OutputSize, + TextureFormat.RGBA32, + mipChain: false); + var pixels = new Color[OutputSize * OutputSize]; + for (var y = 0; y < OutputSize; y++) + { + var sourceY = (cropY + (y + 0.5f) * cropSize / OutputSize) / source.height; + for (var x = 0; x < OutputSize; x++) + { + var sourceX = (cropX + (x + 0.5f) * cropSize / OutputSize) / source.width; + pixels[y * OutputSize + x] = source.GetPixelBilinear(sourceX, sourceY); + } + } + output.SetPixels(pixels); + output.Apply(updateMipmaps: false, makeNoLongerReadable: false); + return output; + } + + private void WriteAtomically(byte[] png) + { + var directory = Path.GetDirectoryName(_imagePath); + if (string.IsNullOrWhiteSpace(directory)) + throw new InvalidDataException("The profile photo directory is unavailable."); + Directory.CreateDirectory(directory); + var temporaryPath = Path.Combine( + directory, + $".avatar.importing-{Guid.NewGuid():N}.png"); + try + { + File.WriteAllBytes(temporaryPath, png); + if (File.Exists(_imagePath)) + { + var backupPath = Path.Combine( + directory, + $"avatar.replaced-{_utcNow():yyyyMMdd-HHmmssfff}-{Guid.NewGuid():N}.png"); + File.Replace(temporaryPath, _imagePath, backupPath); + } + else + { + File.Move(temporaryPath, _imagePath); + } + } + finally + { + if (File.Exists(temporaryPath)) + File.Delete(temporaryPath); + } + } + + private static void DestroyTexture(Texture2D texture) + { + if (texture == null) + return; + if (Application.isPlaying) + UnityEngine.Object.Destroy(texture); + else + UnityEngine.Object.DestroyImmediate(texture); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileImageStore.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileImageStore.cs.meta new file mode 100644 index 0000000..c980f36 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileImageStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0485f48def194877b8a9adb51649d521 diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileStorageMigrator.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileStorageMigrator.cs new file mode 100644 index 0000000..295f428 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileStorageMigrator.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + [Serializable] + internal sealed class ProfileStorageMigrationDocument + { + public const int CurrentSchemaVersion = 1; + public const string CurrentMigrationId = "global-to-default-profile-v1"; + + public int SchemaVersion = CurrentSchemaVersion; + public string MigrationId = CurrentMigrationId; + public string ProfileId = PersistencePaths.DefaultProfileId; + public long CompletedUtcTicks; + public List CopiedFiles = new List(); + } + + internal readonly struct ProfileStorageMigrationResult + { + public ProfileStorageMigrationResult(bool alreadyCompleted, int copiedFileCount, string markerPath) + { + AlreadyCompleted = alreadyCompleted; + CopiedFileCount = Math.Max(0, copiedFileCount); + MarkerPath = markerPath ?? string.Empty; + } + + public bool AlreadyCompleted { get; } + public int CopiedFileCount { get; } + public string MarkerPath { get; } + } + + internal static class ProfileStorageMigrator + { + private static readonly string[] ProfileOwnedFiles = + { + "records.json", + "courses.json", + "favorites.json", + }; + + public static ProfileStorageMigrationResult MigrateDefaultProfile( + string root = null, + Action logWarning = null) + { + var persistenceRoot = string.IsNullOrWhiteSpace(root) + ? PersistencePaths.Root + : Path.GetFullPath(root); + var profileRoot = PersistencePaths.ProfileRootFor( + persistenceRoot, + PersistencePaths.DefaultProfileId); + var markerPath = Path.Combine(profileRoot, "profile-storage-migration.json"); + var warning = logWarning ?? (message => Debug.LogWarning(message)); + var marker = new AtomicJsonFile( + markerPath, + document => document != null && + document.SchemaVersion == ProfileStorageMigrationDocument.CurrentSchemaVersion && + string.Equals( + document.MigrationId, + ProfileStorageMigrationDocument.CurrentMigrationId, + StringComparison.Ordinal) && + document.CopiedFiles != null, + warning); + if (marker.TryLoad(out _, out _)) + return new ProfileStorageMigrationResult(true, 0, markerPath); + + Directory.CreateDirectory(profileRoot); + var copiedFiles = new List(); + foreach (var fileName in ProfileOwnedFiles) + { + CopyIfSourceExistsAndTargetIsMissing( + Path.Combine(persistenceRoot, fileName), + Path.Combine(profileRoot, fileName), + fileName, + copiedFiles); + CopyIfSourceExistsAndTargetIsMissing( + Path.Combine(persistenceRoot, fileName + ".bak"), + Path.Combine(profileRoot, fileName + ".bak"), + fileName + ".bak", + copiedFiles); + } + + marker.Save(new ProfileStorageMigrationDocument + { + ProfileId = PersistencePaths.DefaultProfileId, + CompletedUtcTicks = DateTime.UtcNow.Ticks, + CopiedFiles = copiedFiles, + }); + return new ProfileStorageMigrationResult(false, copiedFiles.Count, markerPath); + } + + private static void CopyIfSourceExistsAndTargetIsMissing( + string sourcePath, + string destinationPath, + string displayName, + ICollection copiedFiles) + { + if (!File.Exists(sourcePath) || File.Exists(destinationPath)) + return; + + var directory = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrWhiteSpace(directory)) + Directory.CreateDirectory(directory); + var temporaryPath = destinationPath + ".migration.tmp"; + if (File.Exists(temporaryPath)) + File.Delete(temporaryPath); + + try + { + using (var source = new FileStream( + sourcePath, + FileMode.Open, + FileAccess.Read, + FileShare.Read, + 81920, + FileOptions.SequentialScan)) + using (var destination = new FileStream( + temporaryPath, + FileMode.CreateNew, + FileAccess.Write, + FileShare.None, + 81920, + FileOptions.WriteThrough)) + { + source.CopyTo(destination); + destination.Flush(true); + } + File.Move(temporaryPath, destinationPath); + copiedFiles.Add(displayName ?? Path.GetFileName(destinationPath)); + } + finally + { + if (File.Exists(temporaryPath)) + File.Delete(temporaryPath); + } + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileStorageMigrator.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileStorageMigrator.cs.meta new file mode 100644 index 0000000..6e2b0d9 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/ProfileStorageMigrator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7083e549418184d3a850b86f2278d6f5 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/SongRecordRepository.cs b/Assets/ProjectQuint/Runtime/Unity/Persistence/SongRecordRepository.cs new file mode 100644 index 0000000..2473e9b --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/SongRecordRepository.cs @@ -0,0 +1,606 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ProjectQuint.Core.Progression; +using ProjectQuint.Library; +using UnityEngine; + +namespace ProjectQuint.Persistence +{ + [Serializable] + internal sealed class SongRecordsDocument + { + public const int CurrentSchemaVersion = 9; + + public int SchemaVersion = CurrentSchemaVersion; + public List Records = new List(); + } + + [Serializable] + internal sealed class SongRecordData + { + public string ChartId = string.Empty; + public string PackId = string.Empty; + public string PackDisplayName = string.Empty; + public string ProviderId = string.Empty; + public string ProviderDisplayName = string.Empty; + public string DifficultyLabel = string.Empty; + public string DifficultyValue = string.Empty; + public int DifficultySortIndex = int.MaxValue; + public string Title = string.Empty; + public string Artist = string.Empty; + public int KeyCount; + public bool HasScratch; + public SongPlayRecord PlayRecord = new SongPlayRecord(); + public int ClearLamp; + } + + internal sealed class SongRecordRepository + { + private static readonly Lazy SharedRepository = + new Lazy(() => new SongRecordRepository()); + + private readonly AtomicJsonFile _file; + private readonly Action _logWarning; + private Dictionary _records; + + public SongRecordRepository(string path = null, Action logWarning = null) + { + _logWarning = logWarning ?? (message => Debug.LogWarning(message)); + _file = new AtomicJsonFile( + path ?? PersistencePaths.SongRecords, + document => document != null && + document.SchemaVersion >= 1 && + document.SchemaVersion <= SongRecordsDocument.CurrentSchemaVersion && + document.Records != null, + _logWarning); + } + + public static SongRecordRepository Shared => SharedRepository.Value; + + internal void Reload() + { + _records = null; + } + + public SongPlayRecord LoadPlayRecord(SongLibraryEntry entry) + { + if (entry == null) + return new SongPlayRecord(); + + EnsureLoaded(); + var chartId = ChartIdentityResolver.Resolve(entry).Value; + if (_records.TryGetValue(chartId, out var stored) && stored.PlayRecord?.HasRecord == true) + return stored.PlayRecord ?? new SongPlayRecord(); + return stored?.PlayRecord ?? new SongPlayRecord(); + } + + public SongClearLamp LoadClearLamp(SongLibraryEntry entry) + { + if (entry == null) + return SongClearLamp.None; + + EnsureLoaded(); + var chartId = ChartIdentityResolver.Resolve(entry).Value; + if (_records.TryGetValue(chartId, out var stored) && + NormalizeLamp(stored.ClearLamp) != SongClearLamp.None) + return NormalizeLamp(stored.ClearLamp); + return NormalizeLamp(stored?.ClearLamp ?? 0); + } + + public void RecordPlay(SongLibraryEntry entry, SongPlayResult result) + { + if (entry == null) + return; + + EnsureLoaded(); + var stored = GetOrCreate(entry); + ApplyEntryMetadata(stored, entry); + stored.PackId = FirstNonEmpty(result.PackId, stored.PackId, entry.PackId); + stored.PackDisplayName = FirstNonEmpty(result.PackDisplayName, stored.PackDisplayName, stored.PackId); + stored.ProviderId = FirstNonEmpty(result.ProviderId, stored.ProviderId); + stored.ProviderDisplayName = FirstNonEmpty(result.ProviderDisplayName, stored.ProviderDisplayName, stored.ProviderId); + stored.DifficultyLabel = FirstNonEmpty(result.DifficultyLabel, stored.DifficultyLabel, entry.DifficultyLabel); + stored.DifficultyValue = FirstNonEmpty( + result.DifficultyValue, + stored.DifficultyValue, + entry.Catalog?.DifficultyValue); + stored.Title = FirstNonEmpty(result.Title, stored.Title, entry.DisplayTitle); + stored.Artist = FirstNonEmpty(result.Artist, stored.Artist, entry.DisplayArtist); + stored.KeyCount = result.KeyCount >= 4 ? result.KeyCount : stored.KeyCount; + stored.HasScratch = result.HasScratch || stored.HasScratch; + var current = entry.BestRecord?.HasRecord == true + ? entry.BestRecord + : stored.PlayRecord; + stored.PlayRecord = SongPlayRecordStore.MergeRun(current, result); + entry.BestRecord = stored.PlayRecord; + Save(); + } + + public RatingSummary CalculateRating( + string providerId, + string providerDisplayName, + int keyCount, + bool hasScratch) + { + EnsureLoaded(); + var normalizedProviderId = (providerId ?? string.Empty).Trim(); + var matchingRecords = _records.Values + .Where(record => record?.PlayRecord?.BestRatings != null) + .SelectMany(record => record.PlayRecord.BestRatings + .Where(rating => rating?.HasRecord == true) + .Select(rating => new { Record = record, Rating = rating })) + .Where(item => + string.Equals(item.Rating.ProviderId, normalizedProviderId, StringComparison.OrdinalIgnoreCase) && + item.Rating.KeyCount == keyCount && + item.Rating.HasScratch == hasScratch) + .ToArray(); + var bestPlays = matchingRecords + .OrderByDescending(item => item.Rating.PlayRating) + .Take(RatingRules.BestCount) + .Select(item => new RatingChartEntry( + item.Record.ChartId, + item.Rating.Title, + item.Rating.Artist, + item.Rating.DifficultyLabel, + item.Rating.AccuracyPercent, + item.Rating.HardClear, + item.Rating.PlayRating)) + .ToArray(); + var calculated = RatingRules.CalculateBest30( + normalizedProviderId, + providerDisplayName, + keyCount, + hasScratch, + bestPlays.Select(item => new RatingContribution(item.ChartId, item.PlayRating))); + return new RatingSummary( + calculated.ProviderId, + calculated.ProviderDisplayName, + calculated.KeyCount, + calculated.HasScratch, + calculated.Value, + calculated.FilledSlots, + bestPlays, + matchingRecords + .GroupBy(item => item.Record.ChartId, StringComparer.Ordinal) + .Sum(group => Math.Max(0, group.First().Rating.PlayCount))); + } + + public IReadOnlyList CalculateRatingContexts() + { + EnsureLoaded(); + return _records.Values + .Where(record => record?.PlayRecord?.BestRatings != null) + .SelectMany(record => record.PlayRecord.BestRatings) + .Where(rating => rating?.HasRecord == true && + !string.IsNullOrWhiteSpace(rating.ProviderId) && + rating.KeyCount >= 4) + .GroupBy( + rating => new RatingContextKey(rating.ProviderId, rating.KeyCount, rating.HasScratch), + RatingContextKey.Comparer) + .Select(group => + { + var newest = group.OrderByDescending(rating => rating.RecordedUtcTicks).First(); + return CalculateRating( + newest.ProviderId, + newest.ProviderDisplayName, + newest.KeyCount, + newest.HasScratch); + }) + .OrderByDescending(summary => summary.ActivityPlayCount) + .ThenByDescending(summary => summary.FilledSlots) + .ThenBy(summary => summary.ProviderDisplayName, StringComparer.OrdinalIgnoreCase) + .ThenBy(summary => summary.KeyCount) + .ThenBy(summary => summary.HasScratch) + .ToArray(); + } + + public RatingSummary CalculateMostRecentRatingContext(int keyCount = 0, bool hasScratch = false) + { + EnsureLoaded(); + var candidates = _records.Values + .Where(record => record?.PlayRecord?.BestRatings != null) + .SelectMany(record => record.PlayRecord.BestRatings) + .Where(rating => rating?.HasRecord == true && + !string.IsNullOrWhiteSpace(rating.ProviderId)); + if (keyCount >= 4) + candidates = candidates.Where(rating => rating.KeyCount == keyCount && rating.HasScratch == hasScratch); + var newest = candidates.OrderByDescending(rating => rating.RecordedUtcTicks).FirstOrDefault(); + return newest == null + ? default + : CalculateRating(newest.ProviderId, newest.ProviderDisplayName, newest.KeyCount, newest.HasScratch); + } + + public void EnrichChartMetadata( + IReadOnlyList entries, + string packDisplayName = null) + { + EnsureLoaded(); + var changed = false; + foreach (var entry in entries ?? Array.Empty()) + { + if (entry == null) + continue; + var chartId = ChartIdentityResolver.Resolve(entry).Value; + if (!_records.TryGetValue(chartId, out var stored)) + continue; + changed |= ApplyEntryMetadata(stored, entry); + if (!string.IsNullOrWhiteSpace(packDisplayName)) + changed |= SetIfDifferent(ref stored.PackDisplayName, packDisplayName); + var ratings = stored.PlayRecord?.BestRatings; + if (ratings == null || ratings.Count == 0) + continue; + foreach (var rating in ratings) + { + if (rating == null) + continue; + if (string.IsNullOrWhiteSpace(rating.Title)) + { + rating.Title = entry.DisplayTitle ?? string.Empty; + changed = true; + } + if (string.IsNullOrWhiteSpace(rating.Artist)) + { + rating.Artist = entry.DisplayArtist ?? string.Empty; + changed = true; + } + var sameProvider = string.Equals( + rating.ProviderId, + entry.Catalog?.ProviderId, + StringComparison.OrdinalIgnoreCase); + if (sameProvider && string.IsNullOrWhiteSpace(rating.ProviderDisplayName)) + { + rating.ProviderDisplayName = entry.Catalog?.ProviderDisplayName ?? rating.ProviderId; + changed = true; + } + if (sameProvider && string.IsNullOrWhiteSpace(rating.DifficultyLabel)) + { + rating.DifficultyLabel = entry.DifficultyLabel ?? string.Empty; + changed = true; + } + if (sameProvider && string.IsNullOrWhiteSpace(rating.DifficultyValue)) + { + rating.DifficultyValue = entry.Catalog?.DifficultyValue ?? string.Empty; + changed |= !string.IsNullOrWhiteSpace(rating.DifficultyValue); + } + } + changed |= SongPlayRecordStore.NormalizeRatingHistory(stored.PlayRecord); + } + if (changed) + Save(); + } + + public void EnrichRatingMetadata(IReadOnlyList entries) => + EnrichChartMetadata(entries); + + public ProfileCollectionSnapshot CalculateCollection() + { + EnsureLoaded(); + var recorded = _records.Values + .Where(record => record?.PlayRecord?.HasRecord == true) + .ToArray(); + if (recorded.Length == 0) + return ProfileCollectionSnapshot.Empty; + + var packs = recorded + .GroupBy(record => string.IsNullOrWhiteSpace(record.PackId) ? "unknown" : record.PackId, + StringComparer.OrdinalIgnoreCase) + .Select(group => BuildCollectionGroup( + group.Key, + FirstNonEmpty(group.Select(item => item.PackDisplayName).FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)), group.Key), + group)) + .OrderByDescending(group => group.Played) + .ThenBy(group => group.DisplayName, StringComparer.OrdinalIgnoreCase) + .ToArray(); + var keyModes = recorded + .GroupBy(record => $"{Math.Max(0, record.KeyCount)}k:{record.HasScratch}", StringComparer.Ordinal) + .Select(group => + { + var first = group.First(); + var keyCount = first.KeyCount > 0 ? first.KeyCount : 5; + return BuildCollectionGroup( + group.Key, + RatingRules.KeyModeLabel(keyCount, first.HasScratch), + group); + }) + .OrderBy(group => group.DisplayName, StringComparer.OrdinalIgnoreCase) + .ToArray(); + var difficulties = recorded + .GroupBy(record => + $"{FirstNonEmpty(record.ProviderId, "unranked")}:{FirstNonEmpty(record.DifficultyLabel, "UNRANKED")}", + StringComparer.OrdinalIgnoreCase) + .Select(group => + { + var first = group.OrderBy(item => item.DifficultySortIndex).First(); + var provider = FirstNonEmpty(first.ProviderDisplayName, first.ProviderId); + var difficulty = FirstNonEmpty(first.DifficultyLabel, "UNRANKED"); + var label = string.Equals(provider, "unranked", StringComparison.OrdinalIgnoreCase) || + string.IsNullOrWhiteSpace(provider) + ? difficulty + : $"{provider} {difficulty}"; + return BuildCollectionGroup(group.Key, label, group); + }) + .OrderBy(group => group.DisplayName, StringComparer.OrdinalIgnoreCase) + .ToArray(); + return new ProfileCollectionSnapshot( + BuildCollectionGroup("total", "ALL RECORDED CHARTS", recorded), + packs, + keyModes, + difficulties); + } + + public void RecordClearLamp(SongLibraryEntry entry, SongClearLamp lamp) + { + if (entry == null || lamp == SongClearLamp.None) + return; + + EnsureLoaded(); + var stored = GetOrCreate(entry); + var current = NormalizeLamp(stored.ClearLamp); + if (LampPriority(current) >= LampPriority(lamp)) + { + entry.ClearLamp = current; + return; + } + + stored.ClearLamp = (int)lamp; + entry.ClearLamp = lamp; + Save(); + } + + public int MigrateDifficultyTableProviders( + IReadOnlyDictionary providerMap) + { + EnsureLoaded(); + if (providerMap == null || providerMap.Count == 0) + return 0; + + var migratedRecords = 0; + foreach (var stored in _records.Values) + { + if (stored == null) + continue; + + var changed = MigrateProvider( + ref stored.ProviderId, + ref stored.ProviderDisplayName, + providerMap); + var playRecord = stored.PlayRecord; + if (playRecord != null) + { + changed |= MigrateRatingProvider(playRecord.BestRating, providerMap); + foreach (var rating in playRecord.BestRatings ?? new List()) + changed |= MigrateRatingProvider(rating, providerMap); + changed |= SongPlayRecordStore.NormalizeRatingHistory(playRecord); + } + + if (!changed) + continue; + migratedRecords++; + } + + if (migratedRecords > 0) + Save(); + return migratedRecords; + } + + private void EnsureLoaded() + { + if (_records != null) + return; + + _records = new Dictionary(StringComparer.Ordinal); + if (!_file.TryLoad(out var document, out _)) + return; + + if (document.SchemaVersion < SongRecordsDocument.CurrentSchemaVersion) + { + Save(); + return; + } + + var migratedDocument = document.SchemaVersion < SongRecordsDocument.CurrentSchemaVersion; + + foreach (var record in document.Records) + { + if (record == null || string.IsNullOrWhiteSpace(record.ChartId)) + continue; + record.ChartId = record.ChartId.Trim().ToLowerInvariant(); + record.PlayRecord ??= new SongPlayRecord(); + record.PlayRecord.BestRating ??= new RatingPlayRecord(); + migratedDocument |= CopyDifficultyValueIfMissing( + record.PlayRecord.BestRating, + record.DifficultyValue); + foreach (var rating in record.PlayRecord.BestRatings ?? new List()) + migratedDocument |= CopyDifficultyValueIfMissing(rating, record.DifficultyValue); + migratedDocument |= SongPlayRecordStore.NormalizeRatingHistory(record.PlayRecord); + record.PlayRecord.EverFullCombo |= record.PlayRecord.LastCleared && + record.PlayRecord.LastMissCount == 0; + record.PlayRecord.EverAllPure |= record.PlayRecord.LastCleared && + record.PlayRecord.LastTotalScoringUnits > 0 && + record.PlayRecord.LastPurePerfectCount >= record.PlayRecord.LastTotalScoringUnits; + record.ClearLamp = (int)NormalizeLamp(record.ClearLamp); + _records[record.ChartId] = record; + } + if (migratedDocument) + Save(); + } + + private SongRecordData GetOrCreate(SongLibraryEntry entry) + { + var chartId = ChartIdentityResolver.Resolve(entry).Value; + if (_records.TryGetValue(chartId, out var stored)) + { + ApplyEntryMetadata(stored, entry); + return stored; + } + + stored = new SongRecordData { ChartId = chartId }; + ApplyEntryMetadata(stored, entry); + _records.Add(chartId, stored); + return stored; + } + + private void Save() + { + _file.Save(new SongRecordsDocument + { + SchemaVersion = SongRecordsDocument.CurrentSchemaVersion, + Records = _records.Values + .OrderBy(record => record.ChartId, StringComparer.Ordinal) + .ToList(), + }); + } + + private static SongClearLamp NormalizeLamp(int stored) => + Enum.IsDefined(typeof(SongClearLamp), stored) + ? (SongClearLamp)stored + : SongClearLamp.None; + + private static bool MigrateRatingProvider( + RatingPlayRecord rating, + IReadOnlyDictionary providerMap) + { + if (rating == null) + return false; + return MigrateProvider( + ref rating.ProviderId, + ref rating.ProviderDisplayName, + providerMap); + } + + private static bool MigrateProvider( + ref string providerId, + ref string providerDisplayName, + IReadOnlyDictionary providerMap) + { + var legacyId = (providerId ?? string.Empty).Trim(); + if (legacyId.Length == 0 || + !providerMap.TryGetValue(legacyId, out var table) || + table == null || string.IsNullOrWhiteSpace(table.Id)) + return false; + + var changed = SetIfDifferent(ref providerId, table.Id); + changed |= SetIfDifferent( + ref providerDisplayName, + string.IsNullOrWhiteSpace(table.DisplayName) ? table.Id : table.DisplayName); + return changed; + } + + private readonly struct RatingContextKey : IEquatable + { + public RatingContextKey(string providerId, int keyCount, bool hasScratch) + { + ProviderId = (providerId ?? string.Empty).Trim(); + KeyCount = keyCount; + HasScratch = hasScratch; + } + + public string ProviderId { get; } + public int KeyCount { get; } + public bool HasScratch { get; } + public bool Equals(RatingContextKey other) => + string.Equals(ProviderId, other.ProviderId, StringComparison.OrdinalIgnoreCase) && + KeyCount == other.KeyCount && HasScratch == other.HasScratch; + public override bool Equals(object obj) => obj is RatingContextKey other && Equals(other); + public override int GetHashCode() => + StringComparer.OrdinalIgnoreCase.GetHashCode(ProviderId) * 397 ^ KeyCount * 31 ^ (HasScratch ? 1 : 0); + public static IEqualityComparer Comparer { get; } = new ContextComparer(); + + private sealed class ContextComparer : IEqualityComparer + { + public bool Equals(RatingContextKey x, RatingContextKey y) => x.Equals(y); + public int GetHashCode(RatingContextKey obj) => obj.GetHashCode(); + } + } + + private static bool ApplyEntryMetadata(SongRecordData stored, SongLibraryEntry entry) + { + if (stored == null || entry == null) + return false; + var changed = false; + changed |= SetIfDifferent(ref stored.PackId, entry.PackId); + changed |= SetIfDifferent(ref stored.Title, entry.DisplayTitle); + changed |= SetIfDifferent(ref stored.Artist, entry.DisplayArtist); + changed |= SetIfDifferent(ref stored.ProviderId, entry.Catalog?.ProviderId); + changed |= SetIfDifferent(ref stored.ProviderDisplayName, entry.Catalog?.ProviderDisplayName); + changed |= SetIfDifferent(ref stored.DifficultyLabel, entry.DifficultyLabel); + changed |= SetIfDifferent(ref stored.DifficultyValue, entry.Catalog?.DifficultyValue); + if (entry.DifficultySortIndex != stored.DifficultySortIndex) + { + stored.DifficultySortIndex = entry.DifficultySortIndex; + changed = true; + } + var keyCount = entry.EffectiveKeyCount; + if (keyCount >= 4 && stored.KeyCount != keyCount) + { + stored.KeyCount = keyCount; + changed = true; + } + var hasScratch = entry.Metadata?.HasScratch == true; + if (stored.HasScratch != hasScratch) + { + stored.HasScratch = hasScratch; + changed = true; + } + return changed; + } + + private static bool CopyDifficultyValueIfMissing( + RatingPlayRecord rating, + string difficultyValue) + { + if (rating == null || + !string.IsNullOrWhiteSpace(rating.DifficultyValue) || + string.IsNullOrWhiteSpace(difficultyValue)) + return false; + rating.DifficultyValue = difficultyValue; + return true; + } + + private static bool SetIfDifferent(ref string target, string value) + { + var normalized = value ?? string.Empty; + if (string.IsNullOrWhiteSpace(normalized) || string.Equals(target, normalized, StringComparison.Ordinal)) + return false; + target = normalized; + return true; + } + + private static string FirstNonEmpty(params string[] values) => + (values ?? Array.Empty()).FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)) ?? string.Empty; + + private static CollectionGroupSummary BuildCollectionGroup( + string id, + string displayName, + IEnumerable records) + { + var items = (records ?? Array.Empty()).ToArray(); + return new CollectionGroupSummary( + id, + displayName, + items.Length, + items.Count(item => + NormalizeLamp(item.ClearLamp) == SongClearLamp.NormalClear || + NormalizeLamp(item.ClearLamp) == SongClearLamp.AutoScratchClear), + items.Count(item => + NormalizeLamp(item.ClearLamp) == SongClearLamp.HardClear || + NormalizeLamp(item.ClearLamp) == SongClearLamp.ExHardClear), + items.Count(item => NormalizeLamp(item.ClearLamp) == SongClearLamp.Failed || + NormalizeLamp(item.ClearLamp) == SongClearLamp.NormalFailed), + items.Count(item => item.PlayRecord?.EverFullCombo == true), + items.Count(item => item.PlayRecord?.EverAllPure == true)); + } + + internal static int LampPriority(SongClearLamp lamp) => lamp switch + { + SongClearLamp.NormalFailed => 1, + SongClearLamp.Failed => 2, + SongClearLamp.AutoScratchClear => 3, + SongClearLamp.NormalClear => 4, + SongClearLamp.HardClear => 5, + SongClearLamp.ExHardClear => 6, + _ => 0, + }; + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Persistence/SongRecordRepository.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Persistence/SongRecordRepository.cs.meta new file mode 100644 index 0000000..92a0bb0 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Persistence/SongRecordRepository.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 33f058a69162b4173ac3281aaec39dfe \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/ProjectQuint.Infrastructure.asmdef b/Assets/ProjectQuint/Runtime/Unity/ProjectQuint.Infrastructure.asmdef new file mode 100644 index 0000000..e3358b0 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/ProjectQuint.Infrastructure.asmdef @@ -0,0 +1,17 @@ +{ + "name": "ProjectQuint.Infrastructure", + "rootNamespace": "ProjectQuint", + "references": [ + "ProjectQuint.Core", + "Unity.InputSystem" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Assets/ProjectQuint/Runtime/Unity/ProjectQuint.Infrastructure.asmdef.meta b/Assets/ProjectQuint/Runtime/Unity/ProjectQuint.Infrastructure.asmdef.meta new file mode 100644 index 0000000..324234d --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/ProjectQuint.Infrastructure.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9bddd54fa81ae4d17a57cac5ed4531a9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Resources.meta b/Assets/ProjectQuint/Runtime/Unity/Resources.meta new file mode 100644 index 0000000..2bf63df --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 06f422db0e4c34196a5aa89d58a41ff2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint.meta b/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint.meta new file mode 100644 index 0000000..17896dd --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 82eb98f103c2c4a52af4c2876d8d4d98 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding.meta b/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding.meta new file mode 100644 index 0000000..1b569de --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1463c7e3772b4471e81aafa4ccc818c5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/FMODLogoWhite.png b/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/FMODLogoWhite.png new file mode 100644 index 0000000000000000000000000000000000000000..0675b5d13dda17d89b20189a9bfef2bcb9fa9c54 GIT binary patch literal 8803 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 P_ujf%V9g43$Cv*P29?9R literal 0 HcmV?d00001 diff --git a/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/FMODLogoWhite.png.meta b/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/FMODLogoWhite.png.meta new file mode 100644 index 0000000..79d50c5 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/FMODLogoWhite.png.meta @@ -0,0 +1,247 @@ +fileFormatVersion: 2 +guid: d53c19b99cdd645c7a195c2584d2582b +TextureImporter: + internalIDToNameTable: + - first: + 213: 3254892072582250316 + second: FMODLogoWhite_0 + - first: + 213: 8655615019453088085 + second: FMODLogoWhite_1 + - first: + 213: 295461772975993495 + second: FMODLogoWhite_2 + - first: + 213: -3503770552761754162 + second: FMODLogoWhite_3 + - first: + 213: 4385662376544641261 + second: FMODLogoWhite_4 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 2 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: FMODLogoWhite_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 144 + height: 192 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: c4fff784813bb2d20800000000000000 + internalID: 3254892072582250316 + vertices: [] + indices: + edges: [] + weights: [] + - serializedVersion: 2 + name: FMODLogoWhite_1 + rect: + serializedVersion: 2 + x: 105 + y: 0 + width: 301 + height: 137 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 5518903300cee1870800000000000000 + internalID: 8655615019453088085 + vertices: [] + indices: + edges: [] + weights: [] + - serializedVersion: 2 + name: FMODLogoWhite_2 + rect: + serializedVersion: 2 + x: 404 + y: 0 + width: 138 + height: 137 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 792f0f6cae0b91400800000000000000 + internalID: 295461772975993495 + vertices: [] + indices: + edges: [] + weights: [] + - serializedVersion: 2 + name: FMODLogoWhite_3 + rect: + serializedVersion: 2 + x: 550 + y: 0 + width: 144 + height: 192 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: ecd592cfe3b106fc0800000000000000 + internalID: -3503770552761754162 + vertices: [] + indices: + edges: [] + weights: [] + - serializedVersion: 2 + name: FMODLogoWhite_4 + rect: + serializedVersion: 2 + x: 685 + y: 0 + width: 43 + height: 43 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: de02dbaf1b00ddc30800000000000000 + internalID: 4385662376544641261 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: + FMODLogoWhite_0: 3254892072582250316 + FMODLogoWhite_1: 8655615019453088085 + FMODLogoWhite_2: 295461772975993495 + FMODLogoWhite_3: -3503770552761754162 + FMODLogoWhite_4: 4385662376544641261 + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/TeamDeniaMark.png b/Assets/ProjectQuint/Runtime/Unity/Resources/ProjectQuint/Branding/TeamDeniaMark.png new file mode 100644 index 0000000000000000000000000000000000000000..e8594b5312c76ba06a91cca8199de5a8e58884fd GIT binary patch literal 187231 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%0D^Iaoxb0n%4mrwq+dAFVCIIh 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

;N+TRbVcG}1px8qEnhVHU&q z1uNV)?9%(!eT820_c48yrTl^~wK6H_j}tbrDLD1(Fqun-=|toFIs1E> zc68x==p1K{Q+KfA5i`73(?BFK2u32Lg}+Y z0wEbr+9%MQY(f`ILf5xr9|H}Ui+MgjmL|H;R4tpkZa%__m7*0lW~CLqW~; zqDjDEWDB@*;XHGL?B3)(&qW@sLH}U*En5YQd#=_#6%w1 z!m8vLr~_o4b@}GHzzuXwW?(-gl%eJ;CNJPVlS1B587X_9_YanVRv$%I+iF;)_y5nt znvrJYU6lN$&r7m|ej-=i`iVIR#rA{oPtz4Y2bh~* z5reUu<}Ra0=JQ_XWLb9WSoNs5KbGEG#^nUEapa3QUuV=J+{X73vBqRZBl_QH`6O}* zfs2zSBLv9|Y%O}X+_)Gjg1GN`_6jt*)uNMKv7Ha|C53Lv(CtVJDZI~~3dnV2U%NYv z4d1gjNGyf|P~U1cQmsiIHM%>6!Ep)Tny;H$ZNSH`bH^KBZsPfmowN*xgL%v0SGwNR#9mnrsH7pENOaA-*XmOaOnjq))#xLC-N#Z`7K_j>)cEL$D;&Z(3kB> zZQ0*=sj)s)e7rpEoJsBlq0+D7r_=VVI$vd!`6=hz$YtO{MfYAC`fNIW2pLalX2S=h zTfk?h;V!qX%ks{tA*Rb97Dsxkvf}JqM-is_o7(){E2J))r>&1fl(86wuGZ55anhhy zM$9OdBkIot`ByFzGqhgXnJD^6|_MuBH}aK{qA7$hnY>D~+=@g0LQJ`-pO(&G0k|RG>VLizRD?R+oHO(4GZ0NMGtJtdqTi(%!68Z`r&AHw zSd&Hc7^MV8ny3x+5iMbNe8jJ);Cj1gCcmS9WN|2c2v&u@Z9XoA{rBOLhpw}@7f61& zYnBbw6dXev*z8(n*MOob=LT4Gx_n^_i~k&7f6XbG1UkGN<{`kpOUJREo`s{C86)NR z&I_Y6kSf@PY$%MhSBtQC>JRvsP=}MKa%3gH9EK8AkwovWLgTLus_10LM@?F$EHQ;E zwP~tXS*ET~sjN3y79@+rf3{GQVAA5wYN>6r%@{S9J26^CjIY{N_8QnfQdoK?)3dC9 zUu(A$)egq{SnzsS(lVp(kY#Mh+RrhyiT2xfwmG9-TLLogk7=)ymul?%nLAU`aC_fu zJE>a{Z`U|>$CYB_7IZ@JU5Ni@5OC4*?i}Y;vf~yhMO;v06O1I(J8$mOuBnl6t1g~~ z?1osW$6_?ltFP^|;D=OdBVQ97+Lx;s)$Od{?gms zba8Csn~_Q9n6X>l0SW%E(9VEMC8_pAO)5KFX@kOa4}psN2)NI`YkDR_S7AvdYF}(! z#_7n_kdl=ISo4_be(k%QJpl=qZBkwjcRl}Na_`%!|1iqU_^iF%N7G6^nSw)WCFSx2 zh~X@^RS54oev6mMc9yjiG5ck4XEoN<>$M8EMmqOibeT8*UJEldh&L0ZAt~MhZc9g>V7F*@YsP**;yBtlamBg!ZoQ(1T4^R4A*hE&7bTBoqfBH$YQeSG zv9#NxKPn#%ak1bl<9PO4#E;?#lgl=~ORVWoO?xzbz7CnK-853{IC?5Zzhz+QP}xiPg_SsPBMagC*m{TUs8hYh5V*3^I!Xt_fY z2rrpxnskrBCv`$yCVzfNRR7~64o$CX8rbXk_vw1do5r`JuN|U%3D%hKpS+4p-x8$w zyefGY6$Epv?yebFy@e_E@V6!O;ELwvmT#C6b75qI`C`xaH5X6KjX8*)&4bao2Gk_V~&5!J154F&*HjEi*eCw?J7rN)`+;?Gj{Wva_qAELcj;3(R|_R-!#t9 zUN`JYrod$`6*jUA>xPcfx#yWh9kRkDCRBB>JU7#8MitvCLT zYcPu(x%1h!YnVs`RUAAO8*8XXtgV2-Cm`ppv!;}88`XkD9FkUWOjH#y_(-tgNl^hu ztsP?0!2eA8NcfWqV$wob4~u&(x;{ZGe>0jj^Sw$2KDX0L+Ynkh^qj}CaZ(mFX+R&xoEshZW!TJ#xbtFPnnlxl`@;NQo`i5 zz?Dq*=%x!;FGZ@5fj!paPeDiualyHBT~ejz8`1JHL}+OXP4O^ z8I0Mnl8l{To+L=$UC3o(x@SYtvgW+gx?%sE?GhIQocc5|Mm98S>YoB8xVW~MzgoRy zL|4wFlT^+tuGsSYjjf#7<;whwUDIo%qyHiu_5GV&ug*iJ3%(Xn#oM;0t@lM1Z;oCm ztO5t(EL4}pecPIx| z7sN;)6fufc;q66ok{c|`$5DY#y7@t8ltoU9Tf7?VHQu`T^Ja)17 z7(V#yABDMD$67!3|K3Q9UDvs+``R#=|6VYIW!CKXN)T3k{;QKb9uN=p?)4}!py0$) z0o-)G5a$zl9L#iC0?iX^vOrug1@_pe5p;@NGFO7RD1&LhSK_cHf7DXL5HHG5Qd%?HvjTKlUh(U z2QbLgbbNsGqk9Y5mkV|j+EdeS7B|}`)w{7#55pFxn5RkIp9MA5ebqFFMX!pc;mileC7!jYzw`6x}?C-+gu6XB^_jmPa!xNRO9`*D0mB(su~Ezcr>8V@0!jZ)@sO@EJ!(UyLM5RK zH>L~z{O{Fj|MW5Pem!zfLtBEW8@B-Ia%Vo>Cxd9m-rWLJk%mIwAR%>w{B+hlNP15|_IsGs3<@@|8)m$~2K5KjtZ z46r^|x3w*6e-9z4p1oH6MY!=uDzeOuJhS({C&lMr?92C9L4l>(|DoL3QzPWPFWX;8 z#Pw-+T3HL{?;JY})1SaD>^PxK3dfXlB4zl>d;8dc?df1AgcJdoMkZ(a1=>+q9aY+r z?<>s%3K&>!af5Pu1wX=YIVenT@PcVJS}biSA2qVpTj$H$rN6xQh*%cmA^F`@9-Qei z1@18WIQX<~1@ChEUBk`<9y>qQ9H-BO`3YHmQuxU~JJ*3LiN0dtKE?iM2Jo{0J^i7# zMH2|<(aN=-BS^dHVu0erC|9DFVDsXMZ6BnJe2Jw4>PhufQ&enI6a=6qS- zQIHH2FEmS;l#7I1k&+d0h`tK=D(SCPO<(M0~Lv9ITB9pPFVMK3J>KEG$VY7iyIYP6O zbw;}k!*F7T?aG+610e8#OBuS8t@9v=%vRl3N%%4JK@SC$gRlq}&{Th^b~Agl^2~P{ z_I6$q3|}v5bjSjh%76>cUg3GQRLj;O$3s&Q>=XZ>@m!J4ua#qH2eqkQFL3h}ZGjZB zM`_ne$TZu@O@WvTe7a0jz*}+4GM9Zj7z^MOMU!*Cm8+W818)GgL{h?ERO-$uo=KY5 zJzv8mWs!d`{Ju{q(dSOKoe8~D=VK0kq4M|YdM{lkWyin|bJe~pd{>Pjn#?nYf3ygd z5+4RuO}sBUE`8#GgI6WMY~8lkL5W!`uP*|&XFMJuy@zKXzd0rSO9$s0N)`GET`Bbb zJA(^atu8Y5&@x}=@^&i>E5xQdXpKw}!_`dy094~J{*G;KfXeW8z8HmEP?i7zMC!@X zq@_FjRX7VkO1r5AEQa{u(?fgTZ)Q2bs#W=)#sS%JbpQ1cI?zDS&qyDK&#;yUa7Rlv zO?wC}B|j5A_A2vS-%i)pJhRBV%Hv?-5o!(s}$6 z{llX>hluX0XZEwxonZ|}5lP)%iS?5j(O5+GRyTgHn^&&eaj!$rVETg0%RU5S`A~2c zBMtcAmg;}*^#SvxeAmuK+vu3lY|atTZim44^M9-p=#8lK4&uS0RCZn(51z2#JE9+v zR{asbm<)vV2A{5!Djfz;KM%3h0cSeEOgSjHr$IevtXrDCWt&60jOtUDL!6(xr4*E* z`*>f?gPm0)b z07mG+Mt0Q--@jW|INkBm`&vC?S*T9GstUblcKIsO{F!dwRD3@K&mw9av$4HQkAW{b zUe+#rGF>kJFwUx}yZ+n7x}CaiOp~@V(x3bMC*oq^kIFL-lpjo3YX8@)WPUyd9v)#vggP zhS(6g-0YHZ1?=@o+W6jqGP%~->)uYX_?~+TJ8x)!E(@t6)0+}iWQR4nYPESDY}K1f zuhad8&?XlQ4_$!TV~s-cw@5Uga?2)kN4_u0N)yA5r|VDA}vT%({Yvvdkerj=UAv>oRph!Ll%8`^Bf z=UW~4)CZ*e4mdARG+D3r-g_u-x`v)YylM1bvNxS(SzC}kxG*PrP09!?)>)#6(>bji zmF~>UDPl~Xie}Fkzt}bV#KLj7+%IYAw9t7q^^OcC9x3zP*KF7DxjmF^jC6UMUA}Uj zo+@%DR?{m^e-*vI)Ck_MAfNUca8Bw>w+3{6?<$j`)xk5y2I2`Ya9|JpkVqOFs<$wd z;O)zD>wrOO;-whF?sa^L(n+1LTRirC$b?X})C~}0a!5npp?0O*TFqo3-4W= zgUM7@6^wG*%QE6VHJMz0G}eDnBw1h;)*bbxTUc5{%q|&u zz*DPsFLjE{!>5woSs6{qQh>6Xh+R%M?u)TupP9!B+J@+#k4y2SUHU0S87Bn?v1{ND z{8~rX-Xly}2PqR$CGoMgqD;^I=f3WLpFKI&&TSRz%HRKUJ7<6$wYPRdr5dzZ2y9|% z0nOoNInBctiwP0I{v63(>E3}(97`xxS_4~zkTal)y$4LSG9A3sCr463THpW@i1|js za8#C8IM5GXWMS+O)}XzI#GgQK)|B z4{)`iw%XO%f(q>jOIH%*HSw3%BZs3bppkWttTSMGlpE#u5cblJO;1Q~gt=Z3mcD0p zT+{0;hy2wIA;L|R+1W|N27&y~ThL}NL+?9FSLwfKRfEPF_bL<;sc=mYh7=PT|G@gL z^<8)#m~4=F>v##FmZt1*RK{BiKCM)WrA(#fY>R}vvO=7^+wOPq?DF1kDYJ+l+E9gL z3}sRk`rotKsQ%PTYhsPylZyQXb$8`Jega+*y1tFotb0&0``M-+uRMxQVQCqY;&7;y zz89RPh|u*+j$B7u?ghWRJ;vQ+*Fxb1b$YsgvnXoK)G=q(%%32Daf#l3?x>Z)-^=jf z!4RE+Lc{{&vM33`r9#=V9puy@y$SBby1Kb|+1x=t^;#q{#r5=D&`>wI4zO+)l5u

FSv@pErt;SkzUVs1JS1utmCsw8if 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�AJ+`;OIOV;ak_nSwN}8^Jf20@X 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%0A)I*DQYIlp@mXT(RREHB> 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 j{?7{j|7-=XGV6F88^hp%@W?Fy(2s + !string.IsNullOrWhiteSpace(DevicePersistentId) && + string.Equals(BackendId, backendId, StringComparison.OrdinalIgnoreCase) && + string.Equals(DevicePersistentId, devicePersistentId, StringComparison.OrdinalIgnoreCase); + } + + public enum GameplaySkin + { + IceBlue, + Rose, + } + + public enum GameplayNoteSkin + { + Bar, + Circle, + } + + public enum LongNoteVisualStyle + { + Thin, + Full, + } + + public enum BgaDisplayMode + { + Full, + Cinema, + } + + public enum BgaPlaybackMode + { + Full, + Static, + Black, + } + + public enum GameplayGearPosition + { + Left, + Center, + Right, + } + + public enum FrameLimitMode + { + Fps60, + Fps144, + Fps240, + Unlimited, + } + + public enum DisplayResolutionMode + { + Hd, + FullHd, + QuadHd, + UltraHd4K, + } + + public enum WindowMode + { + Fullscreen, + Windowed, + } + + public enum AudioDspBufferProfile + { + LowLatency, + Balanced, + Safe, + } + + public enum InputPollingRate + { + Hz1000 = 1000, + Hz2000 = 2000, + Hz4000 = 4000, + Hz8000 = 8000, + } + + internal static class GameSettingsDefaults + { + public const float NoteSpeed = 4.3f; + public const int JudgementOffsetMilliseconds = 0; + public const int AudioOutputLatencyMilliseconds = 0; + public const int MasterVolumePercent = 100; + public const AudioDspBufferProfile AudioDspBuffer = AudioDspBufferProfile.Balanced; + public const InputPollingRate InputPolling = InputPollingRate.Hz1000; + public const FrameLimitMode FrameLimit = FrameLimitMode.Fps144; + public const DisplayResolutionMode DisplayResolution = DisplayResolutionMode.FullHd; + public const WindowMode DisplayMode = WindowMode.Fullscreen; + public const bool ShowFpsCounter = false; + public const bool BgaEnabled = true; + public const BgaPlaybackMode BgaPlayback = BgaPlaybackMode.Full; + public const BgaDisplayMode BgaMode = BgaDisplayMode.Full; + public const bool VSyncEnabled = true; + public const bool LaneSeparatorsEnabled = true; + public const bool TimingMeterEnabled = false; + public const bool GaugeFallbackEnabled = false; + public const GameplayNoteSkin NoteSkin = GameplayNoteSkin.Bar; + public const LongNoteVisualStyle LongNoteStyle = LongNoteVisualStyle.Full; + public const int BackgroundDimPercent = 40; + public const Key CenterAuxKey = Key.G; + public const Key ScratchKey = Key.LeftShift; + + public static readonly Key[] LaneKeys = { Key.D, Key.F, Key.J, Key.K, Key.L }; + public static readonly Key[] FourKeyLaneKeys = { Key.D, Key.F, Key.J, Key.K }; + public static readonly Key[] SixKeyLaneKeys = { Key.D, Key.F, Key.G, Key.J, Key.K, Key.L }; + public static readonly Key[] SevenKeyLaneKeys = { Key.D, Key.F, Key.G, Key.Space, Key.J, Key.K, Key.L }; + } + + internal sealed class GameSettings + { + public const int LaneCount = 5; + public const int CenterLaneIndex = 2; + public const int MaximumKeyCount = 7; + public const int ScratchBindingIndex = MaximumKeyCount; + public const int CenterAuxBindingIndex = MaximumKeyCount + 1; + public const int BindingCount = MaximumKeyCount + 2; + public const float MinimumNoteSpeed = 0.5f; + public const float MaximumNoteSpeed = 10f; + public const float NoteSpeedTravelScale = 7f / 5f; + public const int MinimumJudgementOffsetMilliseconds = -200; + public const int MaximumJudgementOffsetMilliseconds = 200; + public const int MinimumAudioOutputLatencyMilliseconds = 0; + public const int MaximumAudioOutputLatencyMilliseconds = 500; + public const int MinimumMasterVolumePercent = 0; + public const int MaximumMasterVolumePercent = 100; + public const int MinimumBackgroundDimPercent = 0; + public const int MaximumBackgroundDimPercent = 90; + + public float NoteSpeed { get; set; } = GameSettingsDefaults.NoteSpeed; + public int JudgementOffsetMilliseconds { get; set; } = GameSettingsDefaults.JudgementOffsetMilliseconds; + public int AudioOutputLatencyMilliseconds { get; set; } = GameSettingsDefaults.AudioOutputLatencyMilliseconds; + public int MasterVolumePercent { get; set; } = GameSettingsDefaults.MasterVolumePercent; + public AudioDspBufferProfile AudioDspBufferProfile { get; set; } = GameSettingsDefaults.AudioDspBuffer; + public InputPollingRate InputPollingRate { get; set; } = GameSettingsDefaults.InputPolling; + public string AudioOutputDeviceId { get; set; } = string.Empty; + public string AudioOutputDeviceName { get; set; } = string.Empty; + public AudioOutputCalibrationProfile[] AudioOutputCalibrationProfiles { get; set; } = + Array.Empty(); + public bool AudioOutputCalibrationMigrationPending { get; set; } + public bool ReactiveKeySoundsEnabled { get; set; } + public GameplayGearPosition GearPosition { get; set; } = GameplayGearPosition.Center; + public LaneModifier LaneModifier { get; set; } = LaneModifier.Off; + public GaugeMode GaugeType { get; set; } = GaugeMode.Normal; + public bool GaugeFallbackEnabled { get; set; } = GameSettingsDefaults.GaugeFallbackEnabled; + public GameplaySkin GameplaySkin { get; set; } = GameplaySkin.IceBlue; + public GameplayNoteSkin NoteSkin { get; set; } = GameSettingsDefaults.NoteSkin; + public LongNoteVisualStyle LongNoteStyle { get; set; } = GameSettingsDefaults.LongNoteStyle; + public FrameLimitMode FrameLimitMode { get; set; } = GameSettingsDefaults.FrameLimit; + public DisplayResolutionMode DisplayResolutionMode { get; set; } = GameSettingsDefaults.DisplayResolution; + public WindowMode WindowMode { get; set; } = GameSettingsDefaults.DisplayMode; + public bool ShowFpsCounter { get; set; } = GameSettingsDefaults.ShowFpsCounter; + public bool BgaEnabled { get; set; } = GameSettingsDefaults.BgaEnabled; + public bool BgaStaticBackgroundEnabled { get; set; } + public BgaPlaybackMode BgaPlayback + { + get => BgaEnabled + ? BgaPlaybackMode.Full + : BgaStaticBackgroundEnabled ? BgaPlaybackMode.Static : BgaPlaybackMode.Black; + set + { + BgaEnabled = value == BgaPlaybackMode.Full; + BgaStaticBackgroundEnabled = value == BgaPlaybackMode.Static; + } + } + public BgaDisplayMode BgaDisplayMode { get; set; } = GameSettingsDefaults.BgaMode; + public bool VSyncEnabled { get; set; } = GameSettingsDefaults.VSyncEnabled; + public bool LaneSeparatorsEnabled { get; set; } = GameSettingsDefaults.LaneSeparatorsEnabled; + public bool TimingMeterEnabled { get; set; } = GameSettingsDefaults.TimingMeterEnabled; + public int BackgroundDimPercent { get; set; } = GameSettingsDefaults.BackgroundDimPercent; + public string SkippedUpdateVersion { get; set; } = string.Empty; + public long LastUpdateCheckUtcTicks { get; set; } + public string BmsLibraryRoot { get; set; } = string.Empty; + public Key[] LaneKeys { get; } = (Key[])GameSettingsDefaults.LaneKeys.Clone(); + public Key[] FourKeyLaneKeys { get; } = (Key[])GameSettingsDefaults.FourKeyLaneKeys.Clone(); + public Key[] SixKeyLaneKeys { get; } = (Key[])GameSettingsDefaults.SixKeyLaneKeys.Clone(); + public Key[] SevenKeyLaneKeys { get; } = (Key[])GameSettingsDefaults.SevenKeyLaneKeys.Clone(); + public Key CenterAuxKey { get; set; } = GameSettingsDefaults.CenterAuxKey; + public Key ScratchKey { get; set; } = GameSettingsDefaults.ScratchKey; + + public void Normalize() + { + NoteSpeed = Mathf.Clamp(NoteSpeed, MinimumNoteSpeed, MaximumNoteSpeed); + JudgementOffsetMilliseconds = Mathf.Clamp( + JudgementOffsetMilliseconds, + MinimumJudgementOffsetMilliseconds, + MaximumJudgementOffsetMilliseconds); + AudioOutputLatencyMilliseconds = Mathf.Clamp( + AudioOutputLatencyMilliseconds, + MinimumAudioOutputLatencyMilliseconds, + MaximumAudioOutputLatencyMilliseconds); + MasterVolumePercent = Mathf.Clamp( + MasterVolumePercent, + MinimumMasterVolumePercent, + MaximumMasterVolumePercent); + AudioDspBufferProfile = DefinedOrDefault( + AudioDspBufferProfile, + GameSettingsDefaults.AudioDspBuffer); + InputPollingRate = DefinedOrDefault(InputPollingRate, GameSettingsDefaults.InputPolling); + AudioOutputDeviceId = AudioOutputDeviceId?.Trim() ?? string.Empty; + AudioOutputDeviceName = AudioOutputDeviceName?.Trim() ?? string.Empty; + AudioOutputCalibrationProfiles ??= Array.Empty(); + for (var index = 0; index < AudioOutputCalibrationProfiles.Length; index++) + { + var profile = AudioOutputCalibrationProfiles[index]; + if (profile == null) + continue; + profile.BackendId = profile.BackendId?.Trim() ?? string.Empty; + profile.DevicePersistentId = profile.DevicePersistentId?.Trim() ?? string.Empty; + profile.DeviceName = profile.DeviceName?.Trim() ?? string.Empty; + profile.OutputLatencyMilliseconds = Mathf.Clamp( + profile.OutputLatencyMilliseconds, + MinimumAudioOutputLatencyMilliseconds, + MaximumAudioOutputLatencyMilliseconds); + profile.AcceptedSampleCount = Math.Max(0, profile.AcceptedSampleCount); + profile.MedianAbsoluteDeviationMilliseconds = + Mathf.Max(0f, profile.MedianAbsoluteDeviationMilliseconds); + } + BackgroundDimPercent = Mathf.Clamp( + BackgroundDimPercent, + MinimumBackgroundDimPercent, + MaximumBackgroundDimPercent); + SkippedUpdateVersion = SkippedUpdateVersion?.Trim() ?? string.Empty; + LastUpdateCheckUtcTicks = Math.Max(0L, LastUpdateCheckUtcTicks); + BmsLibraryRoot ??= string.Empty; + + GearPosition = DefinedOrDefault(GearPosition, GameplayGearPosition.Center); + LaneModifier = DefinedOrDefault(LaneModifier, LaneModifier.Off); + GaugeType = DefinedOrDefault(GaugeType, GaugeMode.Normal); + GameplaySkin = DefinedOrDefault(GameplaySkin, GameplaySkin.IceBlue); + NoteSkin = DefinedOrDefault(NoteSkin, GameSettingsDefaults.NoteSkin); + LongNoteStyle = DefinedOrDefault(LongNoteStyle, GameSettingsDefaults.LongNoteStyle); + FrameLimitMode = DefinedOrDefault(FrameLimitMode, GameSettingsDefaults.FrameLimit); + DisplayResolutionMode = DefinedOrDefault(DisplayResolutionMode, GameSettingsDefaults.DisplayResolution); + WindowMode = DefinedOrDefault(WindowMode, GameSettingsDefaults.DisplayMode); + BgaDisplayMode = DefinedOrDefault(BgaDisplayMode, GameSettingsDefaults.BgaMode); + if (BgaEnabled) + BgaStaticBackgroundEnabled = false; + NormalizeLaneKeys(LaneKeys); + NormalizeLaneKeys(FourKeyLaneKeys, GameSettingsDefaults.FourKeyLaneKeys); + NormalizeLaneKeys(SixKeyLaneKeys, GameSettingsDefaults.SixKeyLaneKeys); + NormalizeLaneKeys(SevenKeyLaneKeys, GameSettingsDefaults.SevenKeyLaneKeys); + CenterAuxKey = NormalizeCenterAuxKey(CenterAuxKey, LaneKeys); + ScratchKey = NormalizeScratchKey(ScratchKey); + } + + public static float EffectiveNoteTravelSpeed(float displayedSpeed) => + Mathf.Clamp(displayedSpeed, MinimumNoteSpeed, MaximumNoteSpeed) * + NoteSpeedTravelScale; + + public static float ConvertPreV12NoteSpeed(float previousSpeed) + { + var converted = previousSpeed / NoteSpeedTravelScale; + return Mathf.Round( + Mathf.Clamp(converted, MinimumNoteSpeed, MaximumNoteSpeed) * 10f) / 10f; + } + + public bool AssignLaneKey(int lane, Key key) + { + if (lane < 0 || lane >= LaneKeys.Length || !IsAssignableLaneKey(key)) + return false; + + var existingLane = Array.IndexOf(LaneKeys, key); + if (existingLane == lane) + return false; + + var previousKey = LaneKeys[lane]; + if (key == CenterAuxKey) + CenterAuxKey = previousKey; + else if (existingLane >= 0) + LaneKeys[existingLane] = previousKey; + LaneKeys[lane] = key; + return true; + } + + public bool AssignCenterAuxKey(Key key) + { + if (!IsAssignableLaneKey(key) || key == CenterAuxKey) + return false; + + var previousKey = CenterAuxKey; + var existingLane = Array.IndexOf(LaneKeys, key); + if (existingLane >= 0) + LaneKeys[existingLane] = previousKey; + CenterAuxKey = key; + return true; + } + + public bool IsKeyBoundToLane(int lane, Key key) => + lane >= 0 && lane < LaneKeys.Length && + (LaneKeys[lane] == key || lane == CenterLaneIndex && CenterAuxKey == key); + + public Key[] KeysFor(int keyCount) => keyCount switch + { + 4 => FourKeyLaneKeys, + 6 => SixKeyLaneKeys, + 7 => SevenKeyLaneKeys, + _ => LaneKeys, + }; + + public bool AssignProfileKey(int keyCount, int keyIndex, Key key) + { + var keys = KeysFor(keyCount); + if (keyIndex < 0 || keyIndex >= keys.Length || !IsAssignableLaneKey(key)) + return false; + + var existing = Array.IndexOf(keys, key); + if (existing == keyIndex) + return false; + var previous = keys[keyIndex]; + if (existing >= 0) + keys[existing] = previous; + keys[keyIndex] = key; + if (key == ScratchKey) + ScratchKey = previous; + if (keyCount == 5 && key == CenterAuxKey) + CenterAuxKey = previous; + return true; + } + + public bool AssignScratchKey(Key key) + { + if (!IsAssignableLaneKey(key) || key == ScratchKey) + return false; + if (ContainsProfileKey(key)) + return false; + ScratchKey = key; + return true; + } + + public static int NormalizeInputKeyCount(int keyCount) => + Mathf.Clamp(keyCount, 4, MaximumKeyCount); + + public static AudioDspBufferProfile CycleAudioDspBufferProfile( + AudioDspBufferProfile current, + int delta) + { + var value = Enum.IsDefined(typeof(AudioDspBufferProfile), current) + ? (int)current + : (int)GameSettingsDefaults.AudioDspBuffer; + const int count = 3; + return (AudioDspBufferProfile)(((value + delta) % count + count) % count); + } + + public static int RequestedDspBufferSamples(AudioDspBufferProfile profile) => profile switch + { + AudioDspBufferProfile.LowLatency => 128, + AudioDspBufferProfile.Safe => 512, + _ => 256, + }; + + public static string AudioDspBufferProfileLabel(AudioDspBufferProfile profile) => profile switch + { + AudioDspBufferProfile.LowLatency => "LOW LATENCY", + AudioDspBufferProfile.Safe => "SAFE", + _ => "BALANCED", + }; + + public static int CycleInputKeyCount(int keyCount, int delta) + { + var normalized = NormalizeInputKeyCount(keyCount); + var index = normalized - 4; + return 4 + ((index + delta) % 4 + 4) % 4; + } + + public static InputPollingRate CycleInputPollingRate(InputPollingRate current, int delta) + { + var rates = new[] + { + InputPollingRate.Hz1000, + InputPollingRate.Hz2000, + InputPollingRate.Hz4000, + InputPollingRate.Hz8000, + }; + var index = Array.IndexOf(rates, current); + if (index < 0) + index = 0; + return rates[((index + delta) % rates.Length + rates.Length) % rates.Length]; + } + + public static int InputPollingRateHz(InputPollingRate rate) => + Enum.IsDefined(typeof(InputPollingRate), rate) + ? (int)rate + : (int)GameSettingsDefaults.InputPolling; + + public static InputPollingRate ParseInputPollingRate(int storedHz, int legacyProcessingMode = 0) + { + if (Enum.IsDefined(typeof(InputPollingRate), storedHz)) + return (InputPollingRate)storedHz; + return legacyProcessingMode == 1 ? InputPollingRate.Hz4000 : GameSettingsDefaults.InputPolling; + } + + public bool IsKeyBoundToGameplayLane( + int keyCount, + bool hasScratch, + int gameplayLane, + Key key) + { + if (hasScratch && gameplayLane == 0) + return ScratchKey == key; + var keyIndex = gameplayLane - (hasScratch ? 1 : 0); + var keys = KeysFor(keyCount); + return keyIndex >= 0 && keyIndex < keys.Length && + (keys[keyIndex] == key || keyCount == 5 && keyIndex == CenterLaneIndex && CenterAuxKey == key); + } + + public IEnumerable GameplayKeysForLane( + int keyCount, + bool hasScratch, + int gameplayLane) + { + if (hasScratch && gameplayLane == 0) + { + yield return ScratchKey; + yield break; + } + + var keyIndex = gameplayLane - (hasScratch ? 1 : 0); + var keys = KeysFor(keyCount); + if (keyIndex < 0 || keyIndex >= keys.Length) + yield break; + + yield return keys[keyIndex]; + if (keyCount == 5 && keyIndex == CenterLaneIndex && CenterAuxKey != keys[keyIndex]) + yield return CenterAuxKey; + } + + public static string DefaultLaneKeyNameAt(int lane) => GameSettingsDefaults.LaneKeys[lane].ToString(); + + public static string NormalizeLaneKeyPreferenceName(string storedName, string fallbackName) + { + var fallback = Enum.TryParse(fallbackName, true, out Key parsedFallback) + ? parsedFallback + : Key.None; + return ParseLaneKey(storedName, fallback).ToString(); + } + + public static Key ParseLaneKey(string storedName, Key fallback) => + Enum.TryParse(storedName, true, out Key parsed) && IsAssignableLaneKey(parsed) + ? parsed + : fallback; + + public static bool IsAssignableLaneKey(Key key) + { + if (key == Key.None || !Enum.IsDefined(typeof(Key), key)) + return false; + return key != Key.Escape && + key != Key.Digit1 && key != Key.Digit2 && + key != Key.Numpad1 && key != Key.Numpad2; + } + + public static string FormatLaneKeyLabel(Key key) + { + switch (key) + { + case Key.Semicolon: return ";"; + case Key.Quote: return "'"; + case Key.Comma: return ","; + case Key.Period: return "."; + case Key.Slash: return "/"; + case Key.Backslash: return "\\"; + case Key.LeftBracket: return "["; + case Key.RightBracket: return "]"; + case Key.Minus: return "-"; + case Key.Equals: return "="; + case Key.Backquote: return "`"; + case Key.LeftShift: return "L SHIFT"; + case Key.RightShift: return "R SHIFT"; + case Key.LeftCtrl: return "L CTRL"; + case Key.RightCtrl: return "R CTRL"; + case Key.LeftAlt: return "L ALT"; + case Key.RightAlt: return "R ALT"; + case Key.LeftMeta: return "L CMD"; + case Key.RightMeta: return "R CMD"; + case Key.CapsLock: return "CAPS"; + case Key.Backspace: return "DELETE"; + case Key.Enter: + case Key.NumpadEnter: + return "ENTER"; + } + + var name = key.ToString(); + if (name.StartsWith("Digit", StringComparison.Ordinal) && name.Length > 5) + return name.Substring(5); + if (name.StartsWith("Numpad", StringComparison.Ordinal) && name.Length > 6) + return $"NUM {name.Substring(6)}"; + return name.ToUpperInvariant(); + } + + public static int TargetFrameRate(FrameLimitMode mode) => mode switch + { + FrameLimitMode.Fps60 => 60, + FrameLimitMode.Fps144 => 144, + FrameLimitMode.Fps240 => 240, + FrameLimitMode.Unlimited => -1, + _ => 144, + }; + + public static int EffectiveTargetFrameRate( + FrameLimitMode mode, + double displayRefreshRate, + bool vSyncEnabled, + bool displayLinkedPresentation) + { + var requested = TargetFrameRate(mode); + if (double.IsNaN(displayRefreshRate) || + double.IsInfinity(displayRefreshRate) || + displayRefreshRate < 1.0) + return requested; + + var displayMaximum = Math.Max(1, (int)Math.Round(displayRefreshRate)); + if (vSyncEnabled) + return displayMaximum; + if (!displayLinkedPresentation) + return requested; + return requested > 0 ? Math.Min(requested, displayMaximum) : displayMaximum; + } + + public static bool DisplayRefreshRateChanged( + double appliedRefreshRate, + double currentRefreshRate, + double tolerance = 0.5) + { + if (double.IsNaN(currentRefreshRate) || + double.IsInfinity(currentRefreshRate) || + currentRefreshRate < 1.0) + return false; + + if (double.IsNaN(appliedRefreshRate) || + double.IsInfinity(appliedRefreshRate) || + appliedRefreshRate < 1.0) + return true; + + return Math.Abs(currentRefreshRate - appliedRefreshRate) >= tolerance; + } + + public static Vector2Int ResolutionSize(DisplayResolutionMode mode) => mode switch + { + DisplayResolutionMode.Hd => new Vector2Int(1280, 720), + DisplayResolutionMode.FullHd => new Vector2Int(1920, 1080), + DisplayResolutionMode.QuadHd => new Vector2Int(2560, 1440), + DisplayResolutionMode.UltraHd4K => new Vector2Int(3840, 2160), + _ => new Vector2Int(1920, 1080), + }; + + public static FullScreenMode FullScreenModeFor(WindowMode mode) => mode switch + { + WindowMode.Windowed => FullScreenMode.Windowed, + _ => FullScreenMode.FullScreenWindow, + }; + + public static float BackgroundDimOpacity(int percent) => + Mathf.Clamp(percent, MinimumBackgroundDimPercent, MaximumBackgroundDimPercent) * 0.01f; + + private static void NormalizeLaneKeys(Key[] laneKeys, Key[] defaults = null) + { + defaults ??= GameSettingsDefaults.LaneKeys; + var used = new HashSet(); + for (var lane = 0; lane < laneKeys.Length; lane++) + { + var key = laneKeys[lane]; + if (IsAssignableLaneKey(key) && used.Add(key)) + continue; + + for (var candidate = 0; candidate < defaults.Length; candidate++) + { + var defaultKey = defaults[candidate]; + if (!used.Add(defaultKey)) + continue; + laneKeys[lane] = defaultKey; + break; + } + } + } + + private static Key NormalizeCenterAuxKey(Key key, Key[] laneKeys) + { + if (IsAssignableLaneKey(key) && Array.IndexOf(laneKeys, key) < 0) + return key; + + var fallbacks = new[] + { + GameSettingsDefaults.CenterAuxKey, + Key.H, + Key.Space, + Key.S, + Key.A, + }; + foreach (var fallback in fallbacks) + { + if (IsAssignableLaneKey(fallback) && Array.IndexOf(laneKeys, fallback) < 0) + return fallback; + } + + foreach (Key fallback in Enum.GetValues(typeof(Key))) + { + if (IsAssignableLaneKey(fallback) && Array.IndexOf(laneKeys, fallback) < 0) + return fallback; + } + return GameSettingsDefaults.CenterAuxKey; + } + + private static Key NormalizeScratchKey(Key key) => + IsAssignableLaneKey(key) ? key : GameSettingsDefaults.ScratchKey; + + private bool ContainsProfileKey(Key key) => + Array.IndexOf(FourKeyLaneKeys, key) >= 0 || + Array.IndexOf(LaneKeys, key) >= 0 || + Array.IndexOf(SixKeyLaneKeys, key) >= 0 || + Array.IndexOf(SevenKeyLaneKeys, key) >= 0; + + private static TEnum DefinedOrDefault(TEnum value, TEnum fallback) + where TEnum : struct, Enum => + Enum.IsDefined(typeof(TEnum), value) ? value : fallback; + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Settings/GameSettings.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Settings/GameSettings.cs.meta new file mode 100644 index 0000000..1c83db0 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Settings/GameSettings.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 97c6a4273798c4bc78a729ac41412bfc \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/Settings/GameSettingsRepository.cs b/Assets/ProjectQuint/Runtime/Unity/Settings/GameSettingsRepository.cs new file mode 100644 index 0000000..58613ac --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Settings/GameSettingsRepository.cs @@ -0,0 +1,332 @@ +using System; +using ProjectQuint.Core.Charts; +using ProjectQuint.Core.Gauge; +using ProjectQuint.Persistence; +using UnityEngine; +using UnityEngine.InputSystem; + +namespace ProjectQuint.Settings +{ + [Serializable] + internal sealed class GameSettingsDocument + { + public const int CurrentSchemaVersion = 12; + + public int SchemaVersion = CurrentSchemaVersion; + public GameSettingsData Settings = new GameSettingsData(); + } + + [Serializable] + internal sealed class GameSettingsData + { + public float NoteSpeed; + public int JudgementOffsetMilliseconds; + public int AudioOutputLatencyMilliseconds; + public int MasterVolumePercent; + public int AudioDspBufferProfile; + public int InputProcessingMode; + public int InputPollingRateHz; + public string AudioOutputDeviceId = string.Empty; + public string AudioOutputDeviceName = string.Empty; + public AudioOutputCalibrationProfile[] AudioOutputCalibrationProfiles = + Array.Empty(); + public bool AudioOutputCalibrationMigrationPending; + public bool ReactiveKeySoundsEnabled; + public int GearPosition; + public int LaneModifier; + public int GaugeType; + public bool GaugeFallbackEnabled; + public int GameplaySkin; + public int NoteSkin; + public int LongNoteStyle; + public bool LaneSeparatorsEnabled; + public bool TimingMeterEnabled; + public int FrameLimitMode; + public int DisplayResolutionMode; + public int WindowMode; + public bool ShowFpsCounter; + public bool BgaEnabled; + public bool BgaStaticBackgroundEnabled; + public int BgaDisplayMode; + public bool VSyncEnabled; + public int BackgroundDimPercent; + public string SkippedUpdateVersion = string.Empty; + public long LastUpdateCheckUtcTicks; + public string[] LaneKeys = Array.Empty(); + public string[] FourKeyLaneKeys = Array.Empty(); + public string[] SixKeyLaneKeys = Array.Empty(); + public string[] SevenKeyLaneKeys = Array.Empty(); + public string CenterAuxKey = string.Empty; + public string ScratchKey = string.Empty; + public string BmsLibraryRoot = string.Empty; + + public static GameSettingsData FromSettings(GameSettings settings) + { + var data = new GameSettingsData + { + NoteSpeed = settings.NoteSpeed, + JudgementOffsetMilliseconds = settings.JudgementOffsetMilliseconds, + AudioOutputLatencyMilliseconds = settings.AudioOutputLatencyMilliseconds, + MasterVolumePercent = settings.MasterVolumePercent, + AudioDspBufferProfile = (int)settings.AudioDspBufferProfile, + InputProcessingMode = settings.InputPollingRate == InputPollingRate.Hz1000 ? 0 : 1, + InputPollingRateHz = GameSettings.InputPollingRateHz(settings.InputPollingRate), + AudioOutputDeviceId = settings.AudioOutputDeviceId ?? string.Empty, + AudioOutputDeviceName = settings.AudioOutputDeviceName ?? string.Empty, + AudioOutputCalibrationProfiles = + settings.AudioOutputCalibrationProfiles ?? Array.Empty(), + AudioOutputCalibrationMigrationPending = + settings.AudioOutputCalibrationMigrationPending, + ReactiveKeySoundsEnabled = settings.ReactiveKeySoundsEnabled, + GearPosition = (int)settings.GearPosition, + LaneModifier = (int)settings.LaneModifier, + GaugeType = (int)settings.GaugeType, + GaugeFallbackEnabled = settings.GaugeFallbackEnabled, + GameplaySkin = (int)settings.GameplaySkin, + NoteSkin = (int)settings.NoteSkin, + LongNoteStyle = (int)settings.LongNoteStyle, + LaneSeparatorsEnabled = settings.LaneSeparatorsEnabled, + TimingMeterEnabled = settings.TimingMeterEnabled, + FrameLimitMode = (int)settings.FrameLimitMode, + DisplayResolutionMode = (int)settings.DisplayResolutionMode, + WindowMode = (int)settings.WindowMode, + ShowFpsCounter = settings.ShowFpsCounter, + BgaEnabled = settings.BgaEnabled, + BgaStaticBackgroundEnabled = settings.BgaStaticBackgroundEnabled, + BgaDisplayMode = (int)settings.BgaDisplayMode, + VSyncEnabled = settings.VSyncEnabled, + BackgroundDimPercent = settings.BackgroundDimPercent, + SkippedUpdateVersion = settings.SkippedUpdateVersion ?? string.Empty, + LastUpdateCheckUtcTicks = settings.LastUpdateCheckUtcTicks, + CenterAuxKey = settings.CenterAuxKey.ToString(), + ScratchKey = settings.ScratchKey.ToString(), + BmsLibraryRoot = settings.BmsLibraryRoot ?? string.Empty, + LaneKeys = new string[settings.LaneKeys.Length], + FourKeyLaneKeys = SerializeKeys(settings.FourKeyLaneKeys), + SixKeyLaneKeys = SerializeKeys(settings.SixKeyLaneKeys), + SevenKeyLaneKeys = SerializeKeys(settings.SevenKeyLaneKeys), + }; + + for (var lane = 0; lane < settings.LaneKeys.Length; lane++) + data.LaneKeys[lane] = settings.LaneKeys[lane].ToString(); + return data; + } + + private static string[] SerializeKeys(Key[] keys) + { + var result = new string[keys?.Length ?? 0]; + for (var index = 0; index < result.Length; index++) + result[index] = keys[index].ToString(); + return result; + } + + public GameSettings ToSettings() + { + var settings = new GameSettings + { + NoteSpeed = NoteSpeed, + JudgementOffsetMilliseconds = JudgementOffsetMilliseconds, + AudioOutputLatencyMilliseconds = AudioOutputLatencyMilliseconds, + MasterVolumePercent = MasterVolumePercent, + AudioDspBufferProfile = DefinedOrDefault( + AudioDspBufferProfile, + GameSettingsDefaults.AudioDspBuffer), + InputPollingRate = GameSettings.ParseInputPollingRate( + InputPollingRateHz, + InputProcessingMode), + AudioOutputDeviceId = AudioOutputDeviceId ?? string.Empty, + AudioOutputDeviceName = AudioOutputDeviceName ?? string.Empty, + AudioOutputCalibrationProfiles = + AudioOutputCalibrationProfiles ?? Array.Empty(), + AudioOutputCalibrationMigrationPending = + AudioOutputCalibrationMigrationPending, + ReactiveKeySoundsEnabled = ReactiveKeySoundsEnabled, + GearPosition = DefinedOrDefault(GearPosition, GameplayGearPosition.Center), + LaneModifier = DefinedOrDefault(LaneModifier, ProjectQuint.Core.Charts.LaneModifier.Off), + GaugeType = DefinedOrDefault(GaugeType, GaugeMode.Normal), + GaugeFallbackEnabled = GaugeFallbackEnabled, + GameplaySkin = DefinedOrDefault(GameplaySkin, ProjectQuint.Settings.GameplaySkin.IceBlue), + NoteSkin = DefinedOrDefault(NoteSkin, GameSettingsDefaults.NoteSkin), + LongNoteStyle = DefinedOrDefault(LongNoteStyle, GameSettingsDefaults.LongNoteStyle), + LaneSeparatorsEnabled = LaneSeparatorsEnabled, + TimingMeterEnabled = TimingMeterEnabled, + FrameLimitMode = DefinedOrDefault(FrameLimitMode, GameSettingsDefaults.FrameLimit), + DisplayResolutionMode = DefinedOrDefault(DisplayResolutionMode, GameSettingsDefaults.DisplayResolution), + WindowMode = DefinedOrDefault(WindowMode, GameSettingsDefaults.DisplayMode), + ShowFpsCounter = ShowFpsCounter, + BgaEnabled = BgaEnabled, + BgaStaticBackgroundEnabled = BgaStaticBackgroundEnabled, + BgaDisplayMode = DefinedOrDefault(BgaDisplayMode, GameSettingsDefaults.BgaMode), + VSyncEnabled = VSyncEnabled, + BackgroundDimPercent = BackgroundDimPercent, + SkippedUpdateVersion = SkippedUpdateVersion ?? string.Empty, + LastUpdateCheckUtcTicks = LastUpdateCheckUtcTicks, + BmsLibraryRoot = BmsLibraryRoot ?? string.Empty, + CenterAuxKey = GameSettings.ParseLaneKey(CenterAuxKey, GameSettingsDefaults.CenterAuxKey), + ScratchKey = GameSettings.ParseLaneKey(ScratchKey, GameSettingsDefaults.ScratchKey), + }; + + for (var lane = 0; lane < settings.LaneKeys.Length; lane++) + { + var stored = LaneKeys != null && lane < LaneKeys.Length + ? LaneKeys[lane] + : GameSettings.DefaultLaneKeyNameAt(lane); + settings.LaneKeys[lane] = GameSettings.ParseLaneKey(stored, GameSettingsDefaults.LaneKeys[lane]); + } + RestoreKeys(settings.FourKeyLaneKeys, FourKeyLaneKeys, GameSettingsDefaults.FourKeyLaneKeys); + RestoreKeys(settings.SixKeyLaneKeys, SixKeyLaneKeys, GameSettingsDefaults.SixKeyLaneKeys); + RestoreKeys(settings.SevenKeyLaneKeys, SevenKeyLaneKeys, GameSettingsDefaults.SevenKeyLaneKeys); + settings.Normalize(); + return settings; + } + + private static void RestoreKeys(Key[] target, string[] stored, Key[] defaults) + { + for (var index = 0; index < target.Length; index++) + { + var name = stored != null && index < stored.Length ? stored[index] : defaults[index].ToString(); + target[index] = GameSettings.ParseLaneKey(name, defaults[index]); + } + } + + private static TEnum DefinedOrDefault(int stored, TEnum fallback) + where TEnum : struct, Enum => + Enum.IsDefined(typeof(TEnum), stored) + ? (TEnum)Enum.ToObject(typeof(TEnum), stored) + : fallback; + } + + internal sealed class GameSettingsRepository + { + private const string NoteSpeedKey = "ProjectQuint.NoteSpeedV4"; + private const string JudgementOffsetKey = "ProjectQuint.JudgementOffsetMsV1"; + private const string AudioOutputLatencyKey = "ProjectQuint.AudioOutputLatencyMsV1"; + private const string MasterVolumeKey = "ProjectQuint.MasterVolumePercentV1"; + private const string ReactiveKeySoundKey = "ProjectQuint.ReactiveKeySoundV1"; + private const string GearPositionKey = "ProjectQuint.GearPositionV1"; + private const string LaneModifierKey = "ProjectQuint.LaneModifierV1"; + private const string GaugeTypeKey = "ProjectQuint.GaugeTypeV1"; + private const string GameplaySkinKey = "ProjectQuint.GameplaySkinV1"; + private const string NoteSkinKey = "ProjectQuint.NoteSkinV1"; + private const string LaneSeparatorsKey = "ProjectQuint.LaneSeparatorsV1"; + private const string TimingMeterKey = "ProjectQuint.TimingMeterV1"; + private const string FrameLimitKey = "ProjectQuint.FrameLimitV1"; + private const string DisplayResolutionKey = "ProjectQuint.DisplayResolutionV1"; + private const string FpsCounterKey = "ProjectQuint.FpsCounterV1"; + private const string BgaEnabledKey = "ProjectQuint.BgaEnabledV1"; + private const string VSyncKey = "ProjectQuint.VSyncV1"; + private const string BackgroundDimKey = "ProjectQuint.BackgroundDimPercentV1"; + private const string LaneKeyPrefix = "ProjectQuint.LaneKeyV2."; + private const string CenterAuxKeyPreference = "ProjectQuint.CenterAuxKeyV1"; + private const string BmsLibraryRootKey = "ProjectQuint.BmsLibraryRoot"; + + private readonly AtomicJsonFile _file; + + public GameSettingsRepository(string path = null, Action logWarning = null) + { + _file = new AtomicJsonFile( + path ?? PersistencePaths.Settings, + document => document != null && + document.SchemaVersion >= 1 && + document.SchemaVersion <= GameSettingsDocument.CurrentSchemaVersion && + document.Settings != null, + logWarning); + } + + public GameSettings Load() + { + if (_file.TryLoad(out var document, out _)) + { + var settings = document.Settings.ToSettings(); + if (document.SchemaVersion < 12) + settings.NoteSpeed = GameSettings.ConvertPreV12NoteSpeed(settings.NoteSpeed); + if (document.SchemaVersion < 3) + settings.AudioDspBufferProfile = GameSettingsDefaults.AudioDspBuffer; + if (document.SchemaVersion < 7) + settings.AudioOutputCalibrationMigrationPending = true; + if (document.SchemaVersion < GameSettingsDocument.CurrentSchemaVersion) + Save(settings); + return settings; + } + + var migrated = LoadLegacy(); + Save(migrated); + return migrated; + } + + public void Save(GameSettings settings) + { + if (settings == null) + throw new ArgumentNullException(nameof(settings)); + + settings.Normalize(); + _file.Save(new GameSettingsDocument + { + Settings = GameSettingsData.FromSettings(settings), + }); + } + + private static GameSettings LoadLegacy() + { + var settings = new GameSettings + { + NoteSpeed = PlayerPrefs.HasKey(NoteSpeedKey) + ? GameSettings.ConvertPreV12NoteSpeed(PlayerPrefs.GetFloat(NoteSpeedKey)) + : GameSettingsDefaults.NoteSpeed, + JudgementOffsetMilliseconds = PlayerPrefs.GetInt( + JudgementOffsetKey, + GameSettingsDefaults.JudgementOffsetMilliseconds), + AudioOutputLatencyMilliseconds = PlayerPrefs.GetInt( + AudioOutputLatencyKey, + GameSettingsDefaults.AudioOutputLatencyMilliseconds), + MasterVolumePercent = PlayerPrefs.GetInt(MasterVolumeKey, GameSettingsDefaults.MasterVolumePercent), + AudioDspBufferProfile = GameSettingsDefaults.AudioDspBuffer, + ReactiveKeySoundsEnabled = PlayerPrefs.GetInt(ReactiveKeySoundKey, 0) != 0, + GearPosition = LoadLegacyEnum(GearPositionKey, GameplayGearPosition.Center), + LaneModifier = LoadLegacyEnum(LaneModifierKey, ProjectQuint.Core.Charts.LaneModifier.Off), + GaugeType = LoadLegacyEnum(GaugeTypeKey, GaugeMode.Normal), + GameplaySkin = LoadLegacyEnum(GameplaySkinKey, ProjectQuint.Settings.GameplaySkin.IceBlue), + NoteSkin = LoadLegacyEnum(NoteSkinKey, GameSettingsDefaults.NoteSkin), + LongNoteStyle = GameSettingsDefaults.LongNoteStyle, + LaneSeparatorsEnabled = LoadLegacyBool(LaneSeparatorsKey, GameSettingsDefaults.LaneSeparatorsEnabled), + TimingMeterEnabled = LoadLegacyBool(TimingMeterKey, GameSettingsDefaults.TimingMeterEnabled), + FrameLimitMode = LoadLegacyEnum(FrameLimitKey, GameSettingsDefaults.FrameLimit), + DisplayResolutionMode = LoadLegacyEnum(DisplayResolutionKey, GameSettingsDefaults.DisplayResolution), + WindowMode = GameSettingsDefaults.DisplayMode, + ShowFpsCounter = LoadLegacyBool(FpsCounterKey, GameSettingsDefaults.ShowFpsCounter), + BgaEnabled = LoadLegacyBool(BgaEnabledKey, GameSettingsDefaults.BgaEnabled), + BgaDisplayMode = GameSettingsDefaults.BgaMode, + VSyncEnabled = LoadLegacyBool(VSyncKey, GameSettingsDefaults.VSyncEnabled), + BackgroundDimPercent = PlayerPrefs.GetInt( + BackgroundDimKey, + GameSettingsDefaults.BackgroundDimPercent), + CenterAuxKey = GameSettings.ParseLaneKey( + PlayerPrefs.GetString(CenterAuxKeyPreference, GameSettingsDefaults.CenterAuxKey.ToString()), + GameSettingsDefaults.CenterAuxKey), + ScratchKey = GameSettingsDefaults.ScratchKey, + BmsLibraryRoot = PlayerPrefs.GetString(BmsLibraryRootKey, string.Empty), + }; + + for (var lane = 0; lane < settings.LaneKeys.Length; lane++) + { + settings.LaneKeys[lane] = GameSettings.ParseLaneKey( + PlayerPrefs.GetString($"{LaneKeyPrefix}{lane}", GameSettings.DefaultLaneKeyNameAt(lane)), + GameSettingsDefaults.LaneKeys[lane]); + } + settings.Normalize(); + return settings; + } + + private static bool LoadLegacyBool(string key, bool fallback) => + PlayerPrefs.GetInt(key, fallback ? 1 : 0) != 0; + + private static TEnum LoadLegacyEnum(string key, TEnum fallback) + where TEnum : struct, Enum + { + var stored = PlayerPrefs.GetInt(key, Convert.ToInt32(fallback)); + return Enum.IsDefined(typeof(TEnum), stored) + ? (TEnum)Enum.ToObject(typeof(TEnum), stored) + : fallback; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/Settings/GameSettingsRepository.cs.meta b/Assets/ProjectQuint/Runtime/Unity/Settings/GameSettingsRepository.cs.meta new file mode 100644 index 0000000..ba0f128 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/Settings/GameSettingsRepository.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 96d00ce11e946447cb1123834d2f123b \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/UI.meta b/Assets/ProjectQuint/Runtime/Unity/UI.meta new file mode 100644 index 0000000..0fa717a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 215259fe976b24da3a05b38c302cbe2a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/AssemblyInfo.cs b/Assets/ProjectQuint/Runtime/Unity/UI/AssemblyInfo.cs new file mode 100644 index 0000000..3898698 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ProjectQuint.Bootstrap")] +[assembly: InternalsVisibleTo("ProjectQuint.Tests.EditMode")] diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/AssemblyInfo.cs.meta b/Assets/ProjectQuint/Runtime/Unity/UI/AssemblyInfo.cs.meta new file mode 100644 index 0000000..bb994cb --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/AssemblyInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d2fae6ba8c82f4447aea17397aab86b6 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/BpmDisplayFormatter.cs b/Assets/ProjectQuint/Runtime/Unity/UI/BpmDisplayFormatter.cs new file mode 100644 index 0000000..bd8f8ad --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/BpmDisplayFormatter.cs @@ -0,0 +1,41 @@ +using System; +using System.Globalization; + +namespace ProjectQuint.UI +{ + internal static class BpmDisplayFormatter + { + public static string Format( + double initialTempo, + double minimumTempo, + double maximumTempo) + { + var initial = IsValid(initialTempo) ? initialTempo : 0.0; + var minimum = IsValid(minimumTempo) ? minimumTempo : initial; + var maximum = IsValid(maximumTempo) ? maximumTempo : initial; + + if (!IsValid(minimum) && IsValid(maximum)) + minimum = maximum; + if (!IsValid(maximum) && IsValid(minimum)) + maximum = minimum; + if (!IsValid(minimum) || !IsValid(maximum)) + return "BPM —"; + if (minimum > maximum) + (minimum, maximum) = (maximum, minimum); + + var minimumLabel = FormatValue(minimum); + var maximumLabel = FormatValue(maximum); + return string.Equals(minimumLabel, maximumLabel, StringComparison.Ordinal) + ? $"BPM {minimumLabel}" + : $"BPM {minimumLabel}–{maximumLabel}"; + } + + private static bool IsValid(double value) => + value > 0.0 && + !double.IsNaN(value) && + !double.IsInfinity(value); + + private static string FormatValue(double value) => + value.ToString("0.##", CultureInfo.InvariantCulture); + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/BpmDisplayFormatter.cs.meta b/Assets/ProjectQuint/Runtime/Unity/UI/BpmDisplayFormatter.cs.meta new file mode 100644 index 0000000..74fbd47 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/BpmDisplayFormatter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c22f1095e578147a582f3c33d9f30aea \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/ChartFeatureRadarElement.cs b/Assets/ProjectQuint/Runtime/Unity/UI/ChartFeatureRadarElement.cs new file mode 100644 index 0000000..b5c0f6a --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/ChartFeatureRadarElement.cs @@ -0,0 +1,340 @@ +using ProjectQuint.Core.Analysis; +using UnityEngine; +using UnityEngine.UIElements; + +namespace ProjectQuint.UI +{ + ///

+ /// Retained UI Toolkit radar used by Music Select. It briefly repaints + /// display-only values after a chart change, then stops its scheduler. + /// + internal sealed class ChartFeatureRadarElement : VisualElement + { + private const float OverflowDisplayScale = 0.18f; + private const int EdgeGradientSegments = 8; + private const float TransitionDurationSeconds = 0.20f; + private const long TransitionTickMilliseconds = 16L; + + private static readonly ChartFeatureAxis[] Axes = + { + ChartFeatureAxis.Notes, + ChartFeatureAxis.Peak, + ChartFeatureAxis.Chord, + ChartFeatureAxis.Long, + ChartFeatureAxis.SofLan, + ChartFeatureAxis.Tech, + }; + + private static readonly Color LowFeatureColor = new Color(0.10f, 0.74f, 0.92f, 1f); + private static readonly Color ReferenceFeatureColor = new Color(0.42f, 0.30f, 0.94f, 1f); + private static readonly Color ExtremeFeatureColor = new Color(0.96f, 0.36f, 0.43f, 1f); + + private readonly float[] _values = new float[Axes.Length]; + private readonly float[] _targetValues = new float[Axes.Length]; + private readonly DaylightFloatTween[] _axisTweens = + new DaylightFloatTween[Axes.Length]; + private readonly Vertex[] _fillVertices = new Vertex[Axes.Length + 1]; + private readonly ushort[] _fillIndices = new ushort[Axes.Length * 3]; + private readonly IVisualElementScheduledItem _transitionTick; + private bool _hasCurrentFeatures; + private bool _hasPresentedFeatures; + + public ChartFeatureRadarElement() + { + pickingMode = PickingMode.Ignore; + for (var axis = 0; axis < Axes.Length; axis++) + { + var indexOffset = axis * 3; + _fillIndices[indexOffset] = 0; + _fillIndices[indexOffset + 1] = (ushort)(axis + 1); + _fillIndices[indexOffset + 2] = (ushort)((axis + 1) % Axes.Length + 1); + } + + generateVisualContent += GenerateRadar; + _transitionTick = schedule + .Execute(UpdateTransition) + .Every(TransitionTickMilliseconds); + _transitionTick.Pause(); + } + + public void SetFeatures(ChartFeatureVector features) + { + var hasCurrentFeatures = features?.IsCurrent == true; + var hadCurrentFeatures = _hasCurrentFeatures; + var changed = hadCurrentFeatures != hasCurrentFeatures; + _hasCurrentFeatures = hasCurrentFeatures; + for (var axis = 0; axis < Axes.Length; axis++) + { + var next = hasCurrentFeatures + ? Mathf.Clamp(features.Value(Axes[axis]), 0f, ChartFeatureVector.MaximumValue) + : 0f; + if (!Mathf.Approximately(_targetValues[axis], next)) + { + _targetValues[axis] = next; + changed = true; + } + } + + if (!changed) + return; + + var animate = + hasCurrentFeatures && + hadCurrentFeatures && + _hasPresentedFeatures; + var now = Time.realtimeSinceStartupAsDouble; + var isRunning = false; + for (var axis = 0; axis < Axes.Length; axis++) + { + if (animate) + { + isRunning |= _axisTweens[axis].Start( + _targetValues[axis], + now, + TransitionDurationSeconds); + } + else + { + _axisTweens[axis].Snap(_targetValues[axis]); + } + + _values[axis] = _axisTweens[axis].Current; + } + + _hasPresentedFeatures = hasCurrentFeatures; + MarkDirtyRepaint(); + if (isRunning) + _transitionTick.Resume(); + else + _transitionTick.Pause(); + } + + private void UpdateTransition() + { + var now = Time.realtimeSinceStartupAsDouble; + var changed = false; + var isRunning = false; + for (var axis = 0; axis < Axes.Length; axis++) + { + changed |= _axisTweens[axis].Tick(now); + _values[axis] = _axisTweens[axis].Current; + isRunning |= _axisTweens[axis].IsRunning; + } + + if (changed) + MarkDirtyRepaint(); + if (!isRunning) + _transitionTick.Pause(); + } + + private void GenerateRadar(MeshGenerationContext context) + { + var rect = contentRect; + if (rect.width < 20f || rect.height < 20f) + return; + + var painter = context.painter2D; + var center = rect.center; + var referenceRadius = Mathf.Min(rect.width, rect.height) * 0.365f; + + painter.lineJoin = LineJoin.Round; + if (_hasCurrentFeatures) + DrawGradientFill(context, center, referenceRadius); + + // Draw the complete reference grid over the translucent data fill. + // This preserves the original hexagonal scale even when a dense chart + // covers most of the radar. + for (var ring = 1; ring <= 4; ring++) + { + var isReference = ring == 4; + painter.lineWidth = isReference ? 1.8f : 1f; + painter.strokeColor = isReference + ? new Color(0.42f, 0.30f, 0.94f, 0.46f) + : new Color(0.25f, 0.43f, 0.82f, 0.14f); + DrawPolygon(painter, center, referenceRadius * ring / 4f, null); + } + + painter.lineWidth = 1f; + painter.strokeColor = new Color(0.25f, 0.43f, 0.82f, 0.14f); + for (var axis = 0; axis < Axes.Length; axis++) + { + painter.BeginPath(); + painter.MoveTo(center); + painter.LineTo(RadarPoint(center, referenceRadius, axis)); + painter.Stroke(); + } + + if (!_hasCurrentFeatures) + return; + + DrawGradientOutline(painter, center, referenceRadius); + for (var axis = 0; axis < Axes.Length; axis++) + { + if (_values[axis] <= 0f) + continue; + var normalized = DisplayRatio(_values[axis]); + var point = RadarPoint(center, referenceRadius * normalized, axis); + DrawNode(painter, point, _values[axis]); + } + } + + internal static float DisplayRatio(float value) + { + value = Mathf.Clamp(value, 0f, ChartFeatureVector.MaximumValue); + if (value <= ChartFeatureVector.ReferenceValue) + return value / ChartFeatureVector.ReferenceValue; + + var overflowRange = + ChartFeatureVector.MaximumValue - ChartFeatureVector.ReferenceValue; + var overflowRatio = + (value - ChartFeatureVector.ReferenceValue) / overflowRange; + return 1f + overflowRatio * OverflowDisplayScale; + } + + internal static Color FeatureColor(float value) + { + value = Mathf.Clamp(value, 0f, ChartFeatureVector.MaximumValue); + const float violetStart = 80f; + if (value <= violetStart) + return LowFeatureColor; + + if (value <= ChartFeatureVector.ReferenceValue) + { + var referenceBlend = Mathf.InverseLerp( + violetStart, + ChartFeatureVector.ReferenceValue, + value); + return Color.Lerp(LowFeatureColor, ReferenceFeatureColor, referenceBlend); + } + + var extremeBlend = Mathf.InverseLerp( + ChartFeatureVector.ReferenceValue, + ChartFeatureVector.MaximumValue, + value); + return Color.Lerp(ReferenceFeatureColor, ExtremeFeatureColor, extremeBlend); + } + + private void DrawGradientFill( + MeshGenerationContext context, + Vector2 center, + float referenceRadius) + { + _fillVertices[0] = GradientVertex( + center, + new Color(LowFeatureColor.r, LowFeatureColor.g, LowFeatureColor.b, 0.08f)); + + for (var axis = 0; axis < Axes.Length; axis++) + { + var color = FeatureColor(_values[axis]); + color.a = _values[axis] > ChartFeatureVector.ReferenceValue ? 0.25f : 0.20f; + var point = RadarPoint( + center, + referenceRadius * DisplayRatio(_values[axis]), + axis); + _fillVertices[axis + 1] = GradientVertex(point, color); + } + + var mesh = context.Allocate(_fillVertices.Length, _fillIndices.Length); + mesh.SetAllVertices(_fillVertices); + mesh.SetAllIndices(_fillIndices); + } + + private void DrawGradientOutline( + Painter2D painter, + Vector2 center, + float referenceRadius) + { + painter.lineWidth = 2.8f; + for (var axis = 0; axis < Axes.Length; axis++) + { + var nextAxis = (axis + 1) % Axes.Length; + var start = RadarPoint( + center, + referenceRadius * DisplayRatio(_values[axis]), + axis); + var end = RadarPoint( + center, + referenceRadius * DisplayRatio(_values[nextAxis]), + nextAxis); + + for (var segment = 0; segment < EdgeGradientSegments; segment++) + { + var startT = segment / (float)EdgeGradientSegments; + var endT = (segment + 1f) / EdgeGradientSegments; + var colorT = (startT + endT) * 0.5f; + var interpolatedValue = Mathf.Lerp( + _values[axis], + _values[nextAxis], + colorT); + + painter.strokeColor = FeatureColor(interpolatedValue); + painter.BeginPath(); + painter.MoveTo(Vector2.Lerp(start, end, startT)); + painter.LineTo(Vector2.Lerp(start, end, endT)); + painter.Stroke(); + } + } + } + + private static Vertex GradientVertex(Vector2 point, Color color) + { + return new Vertex + { + position = new Vector3(point.x, point.y, Vertex.nearZ), + tint = color, + uv = Vector2.zero, + }; + } + + private static void DrawPolygon( + Painter2D painter, + Vector2 center, + float radius, + float[] values) + { + painter.BeginPath(); + for (var axis = 0; axis < Axes.Length; axis++) + { + var normalized = values == null + ? 1f + : DisplayRatio(values[axis]); + var point = RadarPoint(center, radius * normalized, axis); + if (axis == 0) + painter.MoveTo(point); + else + painter.LineTo(point); + } + painter.ClosePath(); + if (values != null) + painter.Fill(); + painter.Stroke(); + } + + private static Vector2 RadarPoint(Vector2 center, float radius, int axis) + { + var angle = (-90f + axis * 60f) * Mathf.Deg2Rad; + return center + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius; + } + + private static void DrawNode(Painter2D painter, Vector2 point, float value) + { + var radius = value >= ChartFeatureVector.ReferenceValue + ? 5f + : value >= 80f + ? 4.4f + : 3.8f; + painter.BeginPath(); + painter.MoveTo(point + new Vector2(0f, -radius)); + painter.LineTo(point + new Vector2(radius, 0f)); + painter.LineTo(point + new Vector2(0f, radius)); + painter.LineTo(point + new Vector2(-radius, 0f)); + painter.ClosePath(); + var featureColor = FeatureColor(value); + painter.fillColor = new Color(0.94f, 0.95f, 1f, 0.96f); + painter.Fill(); + painter.strokeColor = featureColor; + painter.lineWidth = 2f; + painter.Stroke(); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/ChartFeatureRadarElement.cs.meta b/Assets/ProjectQuint/Runtime/Unity/UI/ChartFeatureRadarElement.cs.meta new file mode 100644 index 0000000..6a2afac --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/ChartFeatureRadarElement.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7c1ba9858a8c48b69838dcc8fd502bf3 diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/CourseClearLampView.cs b/Assets/ProjectQuint/Runtime/Unity/UI/CourseClearLampView.cs new file mode 100644 index 0000000..074d6c1 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/CourseClearLampView.cs @@ -0,0 +1,55 @@ +using ProjectQuint.Library; +using UnityEngine.UIElements; + +namespace ProjectQuint.UI +{ + internal enum CourseClearLampState + { + NotPlayed, + Failed, + Cleared, + } + + /// + /// Shared course-certification lamp. The stored course record remains the + /// only source of truth; this view only maps its three visible states. + /// + internal static class CourseClearLampView + { + private const string NotPlayedClass = "pq-course-lamp--not-played"; + private const string FailedClass = "pq-course-lamp--failed"; + private const string ClearedClass = "pq-course-lamp--cleared"; + + public static VisualElement Create(string name = "course-lamp") + { + var lamp = new VisualElement + { + name = name, + pickingMode = PickingMode.Ignore, + }; + lamp.AddToClassList("pq-course-lamp"); + SetState(lamp, CourseClearLampState.NotPlayed); + return lamp; + } + + public static CourseClearLampState Resolve(AeryCourseRecord record) + { + if (record?.HasRecord != true) + return CourseClearLampState.NotPlayed; + return record.Cleared + ? CourseClearLampState.Cleared + : CourseClearLampState.Failed; + } + + public static void SetState( + VisualElement lamp, + CourseClearLampState state) + { + if (lamp == null) + return; + lamp.EnableInClassList(NotPlayedClass, state == CourseClearLampState.NotPlayed); + lamp.EnableInClassList(FailedClass, state == CourseClearLampState.Failed); + lamp.EnableInClassList(ClearedClass, state == CourseClearLampState.Cleared); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/CourseClearLampView.cs.meta b/Assets/ProjectQuint/Runtime/Unity/UI/CourseClearLampView.cs.meta new file mode 100644 index 0000000..71e2da6 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/CourseClearLampView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 04beceba6b7f64e65bf3cdc39b19c800 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/DaylightFloatTween.cs b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightFloatTween.cs new file mode 100644 index 0000000..3adcf22 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightFloatTween.cs @@ -0,0 +1,91 @@ +using UnityEngine; + +namespace ProjectQuint.UI +{ + internal enum DaylightTweenEasing + { + EaseOutCubic, + SmoothStep, + } + + /// + /// Allocation-free display-only interpolation for retained UI values. + /// Authoritative application state must never be read back from this type. + /// + internal struct DaylightFloatTween + { + private float _from; + private float _target; + private double _startedAt; + private float _durationSeconds; + private DaylightTweenEasing _easing; + + public float Current { get; private set; } + + public bool IsRunning { get; private set; } + + public void Snap(float value) + { + _from = value; + _target = value; + Current = value; + _startedAt = 0d; + _durationSeconds = 0f; + IsRunning = false; + } + + public bool Start( + float target, + double now, + float durationSeconds, + DaylightTweenEasing easing = DaylightTweenEasing.EaseOutCubic) + { + Tick(now); + if (durationSeconds <= 0f || Mathf.Approximately(Current, target)) + { + Snap(target); + return false; + } + + _from = Current; + _target = target; + _startedAt = now; + _durationSeconds = durationSeconds; + _easing = easing; + IsRunning = true; + return true; + } + + public bool Tick(double now) + { + if (!IsRunning) + return false; + + var progress = Mathf.Clamp01( + (float)((now - _startedAt) / _durationSeconds)); + var easedProgress = _easing == DaylightTweenEasing.SmoothStep + ? SmoothStep(progress) + : EaseOutCubic(progress); + Current = Mathf.LerpUnclamped(_from, _target, easedProgress); + if (progress < 1f) + return true; + + Current = _target; + IsRunning = false; + return true; + } + + internal static float EaseOutCubic(float progress) + { + progress = Mathf.Clamp01(progress); + var remaining = 1f - progress; + return 1f - remaining * remaining * remaining; + } + + internal static float SmoothStep(float progress) + { + progress = Mathf.Clamp01(progress); + return progress * progress * (3f - 2f * progress); + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/DaylightFloatTween.cs.meta b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightFloatTween.cs.meta new file mode 100644 index 0000000..20303f1 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightFloatTween.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 059216a17e6a41d5975e752ba112a024 diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/DaylightHeaderView.cs b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightHeaderView.cs new file mode 100644 index 0000000..8830060 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightHeaderView.cs @@ -0,0 +1,114 @@ +using System; +using UnityEngine; +using UnityEngine.UIElements; + +namespace ProjectQuint.UI +{ + internal enum DaylightHeaderVariant + { + Screen, + Compact, + } + + internal enum DaylightHeaderTone + { + Dark, + Light, + } + + /// + /// Shared Daylight Prism title rail. Screens and modal cards provide only a host, + /// content and tone so title geometry cannot drift between presenters. + /// + internal sealed class DaylightHeaderView + { + private readonly Font _multilingualFont; + private readonly Font _displayFont; + + public DaylightHeaderView( + VisualElement host, + string title, + string subtitle, + DaylightHeaderVariant variant, + DaylightHeaderTone tone, + Font multilingualFont, + Font displayFont, + string titleName = null, + string subtitleName = null) + { + Host = host ?? throw new ArgumentNullException(nameof(host)); + _multilingualFont = + multilingualFont ?? throw new ArgumentNullException(nameof(multilingualFont)); + _displayFont = displayFont ?? throw new ArgumentNullException(nameof(displayFont)); + + Host.Clear(); + Host.AddToClassList("pq-daylight-header"); + Host.AddToClassList( + variant == DaylightHeaderVariant.Compact + ? "pq-daylight-header--compact" + : "pq-daylight-header--screen"); + Host.AddToClassList( + tone == DaylightHeaderTone.Light + ? "pq-daylight-header--light" + : "pq-daylight-header--dark"); + + var accent = new VisualElement + { + name = string.IsNullOrWhiteSpace(titleName) + ? null + : $"{titleName}-accent", + pickingMode = PickingMode.Ignore, + }; + accent.AddToClassList("pq-daylight-header__accent"); + + var copy = new VisualElement + { + pickingMode = PickingMode.Ignore, + }; + copy.AddToClassList("pq-daylight-header__copy"); + + Title = new Label + { + name = titleName, + pickingMode = PickingMode.Ignore, + }; + Title.AddToClassList("pq-daylight-header__title"); + Title.AddToClassList(DaylightPrismFontPolicy.DisplayClassName); + + Subtitle = new Label + { + name = subtitleName, + pickingMode = PickingMode.Ignore, + }; + Subtitle.AddToClassList("pq-daylight-header__subtitle"); + Subtitle.AddToClassList(DaylightPrismFontPolicy.DisplayClassName); + + copy.Add(Title); + copy.Add(Subtitle); + Host.Add(accent); + Host.Add(copy); + SetContent(title, subtitle); + } + + public VisualElement Host { get; } + public Label Title { get; } + public Label Subtitle { get; } + + public void SetContent(string title, string subtitle) + { + DaylightPrismFontPolicy.SetText( + Title, + title ?? string.Empty, + _multilingualFont, + _displayFont); + DaylightPrismFontPolicy.SetText( + Subtitle, + subtitle ?? string.Empty, + _multilingualFont, + _displayFont); + Subtitle.style.display = string.IsNullOrWhiteSpace(subtitle) + ? DisplayStyle.None + : DisplayStyle.Flex; + } + } +} diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/DaylightHeaderView.cs.meta b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightHeaderView.cs.meta new file mode 100644 index 0000000..7476084 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightHeaderView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d0b68142a0c9c4a20a4e02b7dd309466 \ No newline at end of file diff --git a/Assets/ProjectQuint/Runtime/Unity/UI/DaylightMetricDeltaView.cs b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightMetricDeltaView.cs new file mode 100644 index 0000000..88b54a3 --- /dev/null +++ b/Assets/ProjectQuint/Runtime/Unity/UI/DaylightMetricDeltaView.cs @@ -0,0 +1,112 @@ +using System; +using UnityEngine; +using UnityEngine.UIElements; + +namespace ProjectQuint.UI +{ + internal enum DaylightMetricDeltaTone + { + Hidden, + Improvement, + Regression, + Neutral, + } + + internal readonly struct DaylightMetricDeltaContent + { + public DaylightMetricDeltaContent( + string value, + string delta, + DaylightMetricDeltaTone tone) + { + Value = value ?? string.Empty; + Delta = delta ?? string.Empty; + Tone = tone; + } + + public string Value { get; } + public string Delta { get; } + public DaylightMetricDeltaTone Tone { get; } + } + + /// + /// Shared value/delta binding for Daylight Prism metric rows. Layout remains + /// owned by UXML while font selection, visibility and semantic tone stay + /// consistent between results, profile and future comparison surfaces. + /// + internal sealed class DaylightMetricDeltaView + { + private const string ImprovementClass = "is-improvement"; + private const string RegressionClass = "is-regression"; + private const string NeutralClass = "is-neutral"; + private const string PositiveClass = "is-positive"; + private const string NegativeClass = "is-negative"; + + private readonly Font _multilingualFont; + private readonly Font _displayFont; + + public DaylightMetricDeltaView( + VisualElement root, + string valueName, + string deltaName, + Font multilingualFont, + Font displayFont) + { + Root = root ?? throw new ArgumentNullException(nameof(root)); + _multilingualFont = + multilingualFont ?? throw new ArgumentNullException(nameof(multilingualFont)); + _displayFont = displayFont ?? throw new ArgumentNullException(nameof(displayFont)); + Value = RequireLabel(valueName); + Delta = RequireLabel(deltaName); + Root.AddToClassList("pq-metric-delta"); + Value.AddToClassList("pq-metric-delta__value"); + Delta.AddToClassList("pq-metric-delta__delta"); + } + + public VisualElement Root { get; } + public Label Value { get; } + public Label Delta { get; } + + public void SetContent( + string value, + string delta, + DaylightMetricDeltaTone tone) + { + DaylightPrismFontPolicy.SetText( + Value, + value ?? string.Empty, + _multilingualFont, + _displayFont); + DaylightPrismFontPolicy.SetText( + Delta, + delta ?? string.Empty, + _multilingualFont, + _displayFont); + + Delta.EnableInClassList(ImprovementClass, tone == DaylightMetricDeltaTone.Improvement); + Delta.EnableInClassList(RegressionClass, tone == DaylightMetricDeltaTone.Regression); + Delta.EnableInClassList(NeutralClass, tone == DaylightMetricDeltaTone.Neutral); + var normalizedDelta = (delta ?? string.Empty).TrimStart(); + Delta.EnableInClassList( + PositiveClass, + normalizedDelta.StartsWith("+", StringComparison.Ordinal)); + Delta.EnableInClassList( + NegativeClass, + normalizedDelta.StartsWith("-", StringComparison.Ordinal)); + Delta.style.display = + tone == DaylightMetricDeltaTone.Hidden || string.IsNullOrWhiteSpace(delta) + ? DisplayStyle.None + : DisplayStyle.Flex; + } + + public void SetContent(DaylightMetricDeltaContent content) => + SetContent(content.Value, content.Delta, content.Tone); + + private Label RequireLabel(string name) + { + var label = Root.Q

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

Gp% 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� z=NO?ypH9v7%27F?4Ch65-V2!TP0!wzvbn-CxUA(#rPB>hqoUS8UGO};nO?hyWw;}^ z$?NJ`k-6Axl4(ZM)T%?7q%_i^w#$*Z3zI z%vbl4+IiCow@8da&s=r^^~ykMVqUAfLmWuWn@13ahr^qBIAA6&hT@Q!)RnOy^H-~w z(Wz9-Gy^U@`mUxS|L{t78EFyt-FP>9>~8Q>Utl+lmP#pSNsLh$X|uPnbrv%s<~*)L zmQ{N@sj%617S`akb+2L&6?iZLh9ota86GzQBOeD1#|JY^(n<3M(-utA2n84t_@wCT zkfPY(OX%3p%RxznN0A&!Xma5`eY3O#-ce{19?lG1prX&}22$`UJ^C;EHey({Bm*Ko zcEZ&-Ual%G;R&(nN<$Ck)g0V13vv)(cMey~-FirLhh25q8fC6HDoc|xI}-9xSJvP$JZSiYA z1QJwk@M*)^rD=$!!rXLn`ZlV0A{mOQNrDR9lPPh%Rso$Gu--kl#)4tR0Pw8=4BQ1L zPUq4dAOC;mH5+DxP%%8MptDz3bj|zF6JIB{LxgvSY{oO`Q9@`Iu^KNjL9L7|9zX(IUfcsq8#I{_ZV#%CxSK6m!STXNt|gTbum%j$n^GCe({{Q zW61{L-r>=~55M=jdGBr46!@#uLO50F*T{K8$;u>JM?}Hi z-udaPljCPcd>qj6v#Sffk5ji42&07-{W-pkP7_@#vIC5DVfeP!jtXv+_c12Q)<(f%Zmo9qjDWwo^3l zdR&RFc6y-ogy(+BRq=$CiNwp%xS9%2#S@|^b@L2l0fvu;Y0EmxVi?yHMRbGW;=`|= zv0f{ZAOs9!9!aT zB5inJv<_=z8>bZ%D}eGtVZDZiW|>?i?~&XV;waqAQiCL`#Fx}{Wo35N28y?D8o-O2 zWRGoY7gP)7El}U<8*F?N&yLxNrZ2AutFm;3A~$DC>;zdw)@sLLjB6H1o3hP%r6#Ej zK;+Qah%A>*$;}r6cpiMcAK zDya)s1O>^FTNS^+1FnY05|fw-M8gQVJXA>}c_KF^cAf@Ob-d%Mud=S~NRp#7p3WKl z`SWYFFbaK@W2Pq^4%dm`KlFz8OfAe<_>J^_~uMz08W& zunvzXB~h-<`RLD#Ty*02?*B)8&dmAQOa99=6{naoZ{9N&1j<;jHId<(EyEWHObke1 z8r&i>NiHw%-hbonqc>kXf5uSq=7@vC!;@E^oxJp`0Lm^NH&%tbA@~`aKjj7hl$*#U z+9KSPCs=FBes%KlhkxhM*Z#>fzB+`g`D~=qi$@>)+Uc{u;e6`%;FxVH;s|Z1nFJlw z933)ah|@`H5KESk%LrPH(`O$az4LQ-9)0!r>F@FK(8 z-GfVFXd(a2rBWlUK-SJBd3p7pfip~TRMA|42hf1Cgbiw!22wYI4ge|T0G5NkO%_3G z*7rdn?pPr9N7%4R1xM~{j6SVR2Z}5>+~~B2{nOH=-06u7_=6Be z_U5N)%(K!v1OsE3ASe_GfSPqaIAWQp=By=3e-{YCHY220?n9$ycH9NL5RY>sxbMik zw$|koqx2&}$&y<-)}A zK~);5rA;d-B7YdP(?~)g3nEl=WHTEOerRpHKwPpHOD|GE%@Js%+Ji+yvNM>ECb)*&i`%mqRrmb zIWH7&HFa*W=3}+3x@0hwm(Vgv5vm}JdLvKj8VJprqO%4dyH73&ay_F#-Pt;6Wowi) z(rWSaPeiRPr#cO#0O#P_O}+~G$%HDtit>p=-3LQJv9j+lG5OS;gQGmKX1Go+&w04K zS!%eqe{cWplPkXGf+x#~l!{0!K{Q4(W9mfmAp({TidM0l$z6IDEh#fP`)|JgO}7B?09;D;bG}<6I(_!t`|p0X ze|V423uE@3zIyiH&0o0l_-iN6{=fro?)<#YFc)_Nw;5bRWiS=8*tK98dRe5PAwti> z?DNUQXD>gy^XSd9<0qFVA8EAsGJ~(qp6wsK&79&zfJR1a7zHtmy{1S;yd-FYJ~@f(NBo2|3hg>+0^k#BB)I zm>^W0eMKDS4OTj0)(d?pkj>gZCx6SVmB%H16BID&!yrgmFXaZy%~&z()f}T$!0HZsIVZ2Pv}D!C8%yV-$ZM|0P7c zlBum+vLr&a;ybL;vQ4(Q`yWDXd8yZ@CPYcy6bWlFhkgsPA|)GYrAvZ{3Oyb_h$h>K zEmU=^N9bT+(HBz_{@4Li>ev)o zr8_3r#&0R&qGtxhaZggBmkGu_AVj55s)Lv-xC|DO*jrG!)!&c+C0>}^SQ%L#O?W2u z@sK15JU3!ia#kNdy1G-gYiA)W9;2vvZZJ`d7+<#2&BrZe$aNp22h zIzpp#B^K6nX~i;pD@9`Av@q+`f1BzBA+qDBS&+4*VGtd0Nds-A%0zSNdaM^dTaull zyEfKqXlOdj@eOAT8`^H#UD4(rLip%U+3|e;g=JYm&Z~y4Q`N?Pb05Xxpa$55d1V?*&ypan5lidH+ zQ0@dMer$cQOt~|GDBaJZftDKGtA)|b+AynoycW}d*SYDSSW|h2GiNElw-AO-hR#&0 z;Ji%iMv+`v%3B!CXd}ky6gDLCw1082y;=`3voT1tL}(t zYb4o*%dw?xiKUxlH2dOGw7Du>xhJsB{2Hwr);R{1s;nVE2kUIKRO0g|d~#!ufUvci zHO)3+SMqk7Fxs@;b#_{ZBM~gyGLIcAnGHy&h-Su-CuWf}QZ@;imEH!V&Eh$4mZ-vK zNy0I^OnJ__&fy;np6BR6JL{ZB!zvPJ1$owEDc#|z8vrTt5DQMEdJV(0oB%@9BRQvw z)cj>AYu5d3+jT0`_;bSS_RIK9>knu{(IZgq0aRar`qrEf*V&+wKTWc{z?(3}bxL za(NG4M|_pSY#V`%mUNI9{!7q8TS~*>X5GqR210{LDvbp$Y-@UhVFAa*PC&vd!zw|0 zYO)qVQRk=fpk=k1dfb%TCaWAJf)Ztg`q;`C#8Ldp8fv@^siymdE;7o#KMdB$#%N^O z1U9L!U2$o>3Ne;YOGYcQ)z3_1ihE5U@P`_>dw9S27VH4^t#2b0cbzr!D&C--vfvk>=kvVv$hWKBjyZwgZ%&7_3T+-bC8ElBg?R!w)`^W!f=fLY!G)CBo1 z*UQg7^i9-0$zq=Z%IY}mSu-zSmXXbTYE`4|suKsBv1CkYiUS66=YXAJ_* zz3kDW!+US=JpPi;yTnMM3A|&4sQD&b{JX+htSZBiH*+`ET7vADlk>-mU#R`sVl`tmqD$)lF07Hj>w zpk|jBC(l2=|K?YY9=vn*{QD`c#Yh1zug*W+JGgtjcfiL1?Hye4|I}6|^)RA;>M-C5yGJzp4t8lJOW?1STj8HC5`rd`RCsZ|2yGda;2pNfrAbXC!t-gY| zQ=^a&)eluKY5ABdDf5Sm0OB{%igIM`dM8&)xYoO<$OF}yOs z%CJ6XGCD9EPII`-vy=fL7TM22r$IL+0$b-iauJU{OVYIRNreQF6=iWN5Z0?Ab(EpU zM2CzVB~|A{%!uyF)nKuyLrSDGGS-Vo=0-Y>U^)8PP&{wwD+#Tiky8^rhq$HoHwWq+ zE|L^enlaYFj`z;PzHyK>Gbw59U`1j(q#CqsV$+<=rWWOi%ejwlG(?;m^5Eoc2W`f0Pv5lK0Uwn#;@Gw zjL)aB;ma{rqhRyFGVT#Roc0jsViwVLy3nMNlaiF+Z{W zY>tms(ryS2;sEaDiBLw;fc$dF>)3e&k2M~u!SPiLS=%)>FJ$yg4-F6FLncKx{^xL@+d4E|;K+5+;DzF&}Ko0-;!PHFLfU3dBa0dC2&}g+90d z=$roIOPg(!valwC=xj@C7=Q*hn}~cnP;L*rT;cyvi=$24xKD5vZQ+du2V0I-aq7Jp>&ahuunEbCupm_9Iwm7oy0Km>EuO?^sXNZRwr zUyGcMLt7H4d6DeFLmvNs@cik=+=Cg;A0q$&KmbWZK~!O|e{^(l_UgqaA0~t&Ch`}T z0E{@CzGwo%7u|p2D*#q_fu`+<_}k5w!6O?bGvo63`u3f>Pu}N$q?Ph{vR9Y)pS=6( zlizoqJ6AGdtxR}z0Gph;v@9LNNgEKCPPv&nkl}~*<@p)^aLlJBU7S4gBSCAc6TZF{ zxOaG;sC;ZIgKFKG3M}p|TBN3pf``M(PeM9P0}}I?DDR<6nWo@l&aX5rkQ=AP)|nLECe71UzZ#4Gvi%@mzJ!YjfB@#q=qQ*E`DDxO?>Z)k)$FVdC!hU|1 zHrt3bn;F$wM52|4~D6AY2Fx}uv=!K7mu*Phsqg|g>?=QZj*&8;UdT^X!7+y z?0nJI5VrV@ec8{|s#0yTb|h9io{Akoem&atsft#{8%xR6uJA% zwAHHDQg)k*;4TH+>7(0y-QqX*b_$g;CfWV%}#_iyU5R$q*6rJ&diEc(egKvx+~0Y z)dm+KI}xTv?w`}1{$L6b@Y>K81ZoJHc2Y?CGYgH1w}{ zZLISV@-0{fBCuo0PM&UzU?Fea@u;8S49Lm@i@JeJ8o~$~hY4d*YN)Y#Pk%N=VxdAe z43s1qLIc@uc&?UCZk_yCM?M;bdw{vQ%0v2EdFctLkZ|jf~mQi zzFKR$Ff~iQd3hl(G7Ly7AfS$z-M;3V0xq9_pYML-6#yrIKg_Gp?8+i3#0%=_b*mhZ zOrXp|-7;Tjpu1t5?#wj9&fcd)!+>ayI}yAhYAj`7(U~1b0ShvZGb++^6(j@kn-C$9 zG1iRU=o*r}cmL?FarUmR&YtrR0P!rI91rilwSVWq`6>VVd+H7uH!(%px>8e=`XJ4L zFF|9UqQRZxq@{_Wm8A8-FMW&AK0EV*mVcVr-`{`w!{0f5{(~ItftB>lrR-V%k(SN= z^EUur3b$D|tuJu+LTEF-eR1;H{U=|!eQ@{EH>B-foSqy#eCzuB)%lB${4A;_LOGbL zJ@ur7jy4m9TCH&;oMZ27m?3nIW{J(*QC(h~AKib|AWfOIVsCp7vIhR$O;uqi51=AVdu8nrZoc0zmltdW&L@3pp2=NpzIe@(7 zjFq>@g$VmerJH0Ew?uLe8CUc%q1s9UtVAdd4Z_-z*=%~A-Xc$RJR-#aFQW}Y{8~-q zfDAWnTy&|UNATI1BgfdsMK!?+;g(=I$1FpEV=e-f*T)QWg)FeN;u~UOk}W^%fH{kZ zuBT2C!kk`&BZ_0?8g$VX{fH+E`dcf`TI3jI!#GVHK4uh=hhP>u8HJT84%T0laXc0n zYd5L9drh=JFC(o9W4Wec^@=`N=&UqofRC?d3)%=HlCheYMn|@0Pce|_ zqr8i~Sqvdn43V1cb-K;+CYxc1osdbp~mJ`?(G;~Wg}@}a~2^+6=E3?GhL0; z!Tw5bcT_^ArNOB|W*b3T-Fymww*;AD3Wd6?XlQlH?c`QdV)}vgHQKRmC1DZBFJtM z=O&&a3X-T+FOc!9yVaS{0n~Vwq`W$DOh!sBPKm%w zbqcOojDkUPVbygP(s6l>&lq5@PZyoTbqO{x{4Z~vKgD2=N2D2n3)n2|U&zkGfSbD9`gK9#cMK@7;U5lm9@P*o1m>vnCP2Z1zrTmd$&0D zUB37(AGDt9PKH>Ej-})6bhJiRG8%;}nW|Fiz#HB??6p;sy2K0*komJgc`Zez*9Mzj z%%m{kIGt?x6samIJ-qi0 zx{K3S#dpY1<_}3I5TwU_bUze9B zpM3atK?;qs*sX`wPVlp~HpkE302H8V*8NRZKP&V?;NM)%Ze5(;fAbp`xwqtQiFefT z*+wrv`i>LPdo|!Kh<9zRVa7hmqC{%iF>5btEoY(NIehcXg%_>P!MgwGy{mJ+guriX zzQLGJJG}7yNced{%x=G~w~!b+m|m07bt0A+QzH11%s|IjYkUOrI?hW>k91}vw@j_E zu^)*z>f)14cay4@IG8bUmq4nKtZKlTjt4e$fi8YP5bVq%LAhQ1ml^ANbe+0ey6$u4 zumaiObh7Foc_hg@W&#;ckF?n~{gvcSE*V2;JIg2qM2yfRGbnZp>z~GMs*E)1Xl7Nb z?7NzK7;_sC*9n^O?05-HS+YUzpvYhbJs~?%1rEDlSa!?;X`wwb#?$s(jG;wT5F2UQ zZ88zy7ssvUZNzB|;+JV;Kz}O{jxjS4GfLDJMvSD~A;L73I)2&xQb9WjV`W#h(1!fPX}ZGi^O#@JBRwfid+4I_Vb+U!z%y*CX?+0`V9 z86XqcK!THQTiKm(ax{%lxUC_aaTP>3f;qK}-O5PI!h(a)c|gnG9xZ}VsM;Y&x9db7 zMcR66u~2F3+66H*0;e6?#@n?O>c^1BD>kvz zv@YtSt%-IOXh3Jkjd5U9EU0ZvHTGCnFF|XPJz4Vr$5lFT3cOOa**#)LKu1RRSS4g^ zW{*GsLZ5wTS%6w-97h3KNndM=nRYXYBGb`BmG%vqs0#Bpd)$IAw5n;f5#vM~+S@h# z9M+l)#yNJ4ujF(Rt&m$gw4$3(HIY&)lZNhMwUc%v!!J79HuA>`CO4Le<8|cOXSdGJ z_V2#oz<7zQtwnRc%!Mho?7Li{daOp7D}Ec)v!(M2J`s@Q!dFGE{(WYeD_SwVDCdaA zZs$nkO43c$FA;Oq3w#DJ7q||bI4LFvHd84RPvvF>NZ!bVomTuBG!sil)A5!d zuWv+7*JMS`y^SvMO76Z)K|`DT#!QfAL5PBcyf-1SZ&0%Ee;c+!MLT3*4phigVix^a z5rnx5G97}>YX~LJEr(C~xn=@6Z`sl)IIJ%H@cWae7tcS;X~qD+I%r_|$6~J=2zZ%c z$H`BCwq0wdp#@&PotcWdvmmD_S{(JU8ahlY4F`6Ba@NM90kp00AT)$zf*eB(VjiIm z(u$XIrmCaCW8yvD{qG#$<9(i4w=7y)2L6@xP5!&}{N%+o|4nMv<0wQ~8NkVtY z!QT-6bv5;fAVy4pz5NgVp>LgE@ZmpB2rkSHkB&b6;kVCTenbg&_0jzEOOpG%@DJ(B zBg-Fl1Mr9V|12tUyEuDsbpHeXXX28t4ZqFD0GuA)dvta2^8DGy)Vc!z6|XHMlxkSW zz&m(0kaqN9iHLFQWU1UycQC*|JGpcJ@xk4P=dV7yzCIg&@t6wytIa-N+s4GFvUwFO z0i05fk72~|$Q1at*v#kz6-k9!$w%-=C*uYoIM6N)dL8Wa!l_4ORtYEl7H6qYm1!K|dsA)gd11`oz738e8SgxRe6h40@^| zD?T zpUle`Vq(w4yv9*QrMV&dW;U^du9Z3Wc0h>JcT zgBCv8>MFY!tJKyh$|P)RTDc76Hc6Strm_{cE+ogNs8$b^z=eOO==odEhu#Z9~y22%%;18U+xmy0qriKsN)*p7umOWgWMcqTzwH5ak*-1BJJr=fL2 zvB`XL|0-6dY`l~t^+L!iJYpD`1PmuET3hZ7HMCdLy2rX>=X^qH(1M37#R1I};>#`+KqZpUTQpGYA)7~_7pD>C9Y3|Y~vIZVg zV%&|&9?)0tpp25uP{PW|BdljFw<`mC2A+*J!$%9f^u4)@$u^|KnZ>Z_#z$5fHE48b zw=~+&L8CPf$GzPYJdn=Yl8e2oHPeXA94<&qk_r7l>83Q5R5NunW)T)(18C_VI*<7J z8q2kkAdJ{ek5B_UftCx&w;42CgX7jE-p1OzRznGn+R#j|tx8-kgTQ3y1*)7)-B`iZeN9MleeI@QJZC)GRL}>EE;STL zq?9h4qck9ZJVh#^?lFvMP3*@vY?c>9z)lbA_WGmNGF{b7IQXZPQ z$RgFFK`$~kWR?_lsf;yt?&`5*y^L2Q{^~c7bT;4{cCn~wtTz`a3v>IThb-VvDV>K~!0dHT?|>BZKe}R!94LhZf@UByawSPau)`v7is$ z^s2qFsLY)mm6xLHs;`B|c3-6bqTDN)rUy1FEyKlUAarXTD)sLvvgusON2%Y|T|K10Duu}dT`101p1&5}NlVKbjM?^29 z2A^FrLs$d!*;lm7v?&D~`N>p@uSz8TiYiv{({&&gXJU8_su2SmO`SFb!ubQZ&NG8U zDP9%h&I5@UYQ>Ux0H!u!Ai5-YB_#R{Be8j%F>{(n8!?>tu;POS!i{;J^2o(lECdmd zM#;;_*A@qw1_XptVcU(=6w+yV6vb@NVk2ONMy=+8>F=m`VTRP2MMtLyG~9iPy`umB zq`e7~CChQ8*Ii3jb@$shJOBfb9BQZ;qm5*gY5V^_fTl6ij7%ocAQB5Hysh`zt8~8a zc;vm+-R}_tMcvAba6f*0iO9TpcTftAocS|Tf`$?$xr^Coc+plwAZXODuwzocN!G}l z2{6ye2AVL4#GHj0vq{OPn)ysIei%}^g zGr`4*5+u{TvLJ}8td&a(BTT1pK8`O>R!I7Qd-npaDd^In#gW7qYuhSiyF8OVa2*>-c|ym?gr{zxNQq|z(UQ#s zsdW}v6IWpjI>m@T&n@+(OTsD}!l84ikl~nCWqdjvhYd24;)2RLP&h#u5pgEac^-#K zPmAAFPg`fUiB$;*5`04GKe946fLw$Tj)WqISksLcA=4L2Jc?Vr<8ZW!rbSL`s7BFbuLmoxQm?#u93?YGz9j&`umtH)0{hPZt z#}5vlOh*~xFhuJa&6gwUb)gUk_T(2TrhY?UFm z)plPdbzsQWR1bJe40o7t3RHh)_U zJ$Pn_k46#}?xP~erex0FkSav%f(an+j=nm}=eao>M9$)DDj4X4<0_ZKA?+XW;TCjP z8c1_sOC;v2F2;sBcNr#^_hWl*z)=sw?9%~*Pcot5fVTE@$cjsj_+K!hB%~~Z8uwU3 zO~B;JU0oW95px~@-sUTJ^6r1G9d0jg^4Z^=7pxM)qsO1-%Oo$(U*|byo*X2pZj8`4 z;vz_LCMNEtBc8Y-2Ttpb94RAVr0Spi;;$cn@agks-|_VS-QCggqnF?R-mm^N7ZZhz zEZDx=4B=hzPO=$p$aL~eL)&oJcNX`1d%*jO9~)7{)F2-Q{)dk~{)=0#_iu7Fd7j?| z`|{tNegE4i9vtV_7N}szDV?!2AcUzQE0YpM*tA8`ZREAxMv-5<{Qmgl$&-(N@#^x; z4KHJDEx$*2es}Tg;PgvAmVEpukGH(e3l(+6Q<|=Dx-KP^IjyMD42cUwBV!E2NB}IF zg5`_0IdJKTyvNd<=}aj?rKD9Vwg~&@I_pSi!<;z_4zGHwB5Nzz>87Vgbq0!*(BZDH z^p~K*p@P!mhk0cE1`j2fryqvNI@9D3{;1hVNk1s&7fWMc48W!`J?_aW5JMM(Mg@m1 z>l3!9n<((sf=?0p3=%BB?@36>8+d+eMKUEl#sBq^ZsqB5{F zGlCPZNq0hR$KY7gg8PtzbC>6%z;$XI*$^4UGbpPQ^PCfF4G9741?sTE$fKibl28)_ zR1B3$FB3yzMhfAIF43`@$!bjH?pZsutbCnE8=e$aS{#^o>q8ZYf!#!lmY{GI7Y(WX z%77cEpb{dgkOH=m38gV50%)>`1yHqgbb+Aw*{eWmqU`K2iJB9#=|h326G05zTbTJ` zE~E?F^2uOXCToM2$RHaZky}Gu^8^yjk<(sXQ3NA{wLy7&D> zmjm3(m{8yxAlM;|4qAGVv6T;E z`!Tf}6e-|~gC#()onD|iC{Gl+Eo-G6u0Fvb^wq{al{}MJ=(IN7NRn z;A}mR(e6kDZ+@JhaY!6i8*>pzwq^>OBsoqKx#N%2G|?`T(eBAr0@-h&+!pH4hhcNH zRchH(^DZFnqS``YeaE%lXYFj9JCH@_&?1!NZQwR;E+$yy3To|x%&mkjxhN29r*v~sYg|#;Vajr1dIJ(l`0*UKIgWbArv)#haAYC(UyHS{OI3XAIWJQXIBAWF zqf*%>?+|}Nf?OeCR9|%H;&mpXM`+&N@6|)Twf=S90EC}uxZ%w|Cm&s1U*xMlbE!kf zo(Y;8TU@y#oLw0zk2gg5=a`M2k|@;b_9|~&&-;Jgyne|CGQCd8!|hi;{FZmA5bVSZ z_55S_XEzv^agQiQ#7bE2Z_d!$s<+_x3F0Er=QXLkm+YUfU;pm#=(rWg*8v?o%ufRR zdpK{Jq0Gz?)io4?@v<_iOjcGO4IJm!T=zJ7$9u+(d99B?>;2}CTVM)Zb^~|A<*cN1#}(35^<0S z#ehw`tTe)@ISj!>Wi>Qm2@%ewN=FH1yEaoJluJ^Q0WkG2F-W?83lAY2<`vpFbj@P=AEPBejR z*{0(uZi_$@kW$tueJ7D(r7dq}5eO3IY)FVAlG@VU>a-xne8}6Q8wYZDs+UPW)NkF? z4ni3=jOR#^!a^Y(V0IP(V#9`yWp52!XEB*7g^p@ikh;ZesL;*XL$AxN2WBvnK10ef zA%uH;Wi}1hz!0wD#Zp=Lbr*uX1i^~3s2tW5!QoFeaaSnVG z34eJ2*<5Jp@yaT0IAOLgS2WOhJT_J>brT?RUK?jkI-zpT5`{W1@U9FKr%q#HF@-j7 zoJL4QG52qYeD9clwmB9uX_pr<$*o`%vz@kVgG#d!@ z_tv=%Z?z1UcL18lHgax~w6l-Ye4N5#2})QtsfKrx0|Tjm;OX-;u*=1vBH0{PrNN57 zK{j6v8!EvhLhAv4B2PI#PCl5l3yag(Lrhu~(s(0rz65C6iukaAVt})py!N0=OX9K$l3X`WR-b z7^jUjof_h6&03FbRB4SR&jshO3hCX=o4f1Rqrq3&-NE6>$>UG+?((biH$L>4ASk%! z0DpzKOLsXoDB~Ow93@7IQ&xN<^U(VAXa6==YZvEdxp<1`@c8)b)i-A^{|LJH{gm$a zrF+k2*}X@#FWP+uAO^d#?L+SO_d>d__^Hvu+pCMmpZuFV@1DhwQeR!2efa6m&tHCX zeeohcq)c7o2A4&uYej_W{{GiHBd7|HAwCgndLxkuJZ%Hho>KYc6E7nd!0{g zAm*78{UW3S7A56vI1NBILyg&paGV(v?oaRMkbgJud*L0x7Hmux&t6u_B zBoqA=3CB3@8g?~nzj~Y&h)^y(rM`s!Izf(>h3)17iMN7;Ka6yvy9+$ zp^9nf>Vl0yjA+{8VeYha72*KQP8b(D%OJF?U#Fyn|5h@6Gi{L!-3s6cj9YyL#%x8K zyDP=kDm?>^#|qKiZ^kGU&gvHg3D*fBDA@rjNSsAszBwr27Dm0nv62QLL88^Ho8EgA zKB>W(X2rCjjFokpK{=EXj4?-;8SH9l77_{$Xq!F)XmS;9>F+udHvD>;kQ`ES^Qu!q z+Dnrlm=g^5J&sQgkOr)sr6^nQQy8*sKU+i1T?XB>t|;a}4;8W{BiwGG)_|3RkP_vV z5yQE@=@LeweJIw#q~}Y7vSyP*Ll@dY1VBQ38kHjeaq7gT>+ey%;mCByBThEAw z58k%euYPr#0>`5wx@r}$Lf_l4kZ%K&ptdm~;=NYwJyEsdQmFuX)7EgRTA_{YJql}C zEY-2s&2qiLso|>97NjUG_pFMniZnKg#d&Z4)7pE`A0MpHO4eGfU?%`nDzbEW&cane zg{B|%M9S>X(M`@?-S2Hn8xf!y3jcb@H+F{()eB?kEExB;0Dg z-yeSjS}nYi=85Tp%UP;`w*{?k`69{{Or0{_vl#FJF&QUH-@xs`s_` zNL&8<;N1g&`|`I(%`MV&DAsD5d)}`3;N*k3anK z?D=o=pekR!nquZ5SQc3)$WY^qEZlgW^=wFqtdrHJhfN!M;Q7D4JUcu-IeGf|_4%vY z+Y8V8gW~C5ei`5iA5P*65F_B%#bXzynWDX=7uC)|;}g6K43CcR&80x^!Xs*MT<;=f znP8fhYzeb&t70k1qGHar0!mM0Q0oh>f|VjR^ib#ZIqz>!n~z45!^BaVUMI0S35gw3 z5YN4?f^)M?-}$jHgo0AL!9d|Lu)C8Z3^1H;Q7z}~6`$q7u>>7|Y3}F&M-&|sgUG_B z=fEVuXcq#);_0+V(uET)y~Z$q#VYPJjZ5Ugsi0&Hh9}#MA!8RAdbHksXfqo_BaGD{ zj5Oc`m3m^E&}5cXDojXUaQZ$|Q7fp@VxjZwh<^oajA2(>u?E0#%Dyp#oPpZ$!;ts> z;yRn{636g`TLdRXndTYR{ZtqZ1PocatPgw}bkYYM2|{DSshKh!*TEOI65+_OR@7K+ zw=mG$Wy1m>awJSsSX?55gVact5#eb&Bmz{*Piznd!&-xRF&Qe}ie6h2)B-?Fn=Z&X zyf-W9W4d^1vRtFEj5;k_!cRn7F>4zGA~XiJv8s2Gku(W}$}ty4T9K5IfuXp?otTx; z@JCiuIuJ78u^ia^+cY$!^rrs1R7nze`F*HHnmhB7V9$D^#ZhkQ=&c_f)#X^loY@Hi z5GC4XD#HA2D!O2-P#mBOe{9Gi@-&!h1rTN&&(v#>1v^-g5>m*z&JOcLt$oNDD?+R{ z!X+D8cNoxzg`0Gaib5D4Nsg`H!I3Ue=N%M;m8Ls;s)(LDksfFz7ba2UEpA43k#^RM zBy6Oh0nB91Hv5!s6^)7_GHedR+U9r_tU!);HhNQPIHT8~+3YMfT18<7K-6{SaEFd& z+CESETGeJj)_y}GNG@s3RHnG{?`=m>#!zr)K$x7%mk};Wgp`FXE5CtmuF;MRuQTa% z_oQpIS;6KF6ivi^6TnMeJn1aSGQd7e1=E9UOw)@bF|%Z{xE7zCY$9nT^dV>%HxJr8Ij8r|60Z<3Uu3|H)hs3adEwoW0q$X;nNp#BCA(+b0WYC zsz^ec@4r4gewuNf2fn#@mhb*IWe{!o2FpiJKg%aqFVElLkAQS)a6LkC(=x?Ig!?G< zVi1*cCy8OUQRPbQ`tb1R-~6}#OTM`N`ufr@<9_hy(aFmne*5yxM8{b2N+*j`NX7sanA1L3}o>yzKZH=c~L3@bKs)KM#EO zfFBRvuzX8E+EKpsDVHw!-Qv7eiP^&8;)y0xSR=qtdzNK+I)8&5RsgniYM60n$N3)e zSw39fkXNm`@Budre4*MXeP!UJEh4GXlAPhZ zu9S>W0dUvx0-=}&4soyZIEIQhb&dsnv{ooRU$D~wL(_Km}FR9jx+&tV$BE(5N+|A;Sy9eqaOGftg%=Qt~70`Lu?#S zZD+N;G|V5HsBtz2B{dnQ8ZvG<>v5vVPH(#+dgz*RLoq_gHVZ47s>H_@Ym~kgC!Og0 z4(pzc)-&oncTZz zS|OOPf4OQpTfv6C%*s9V)P0(g_DfTS>doBZPEhr@jB9Jqt4ktF4J&e4QXB?^L9@?j zK$Qp?q{h`R+k$&op{YE*w>%=>7irfGW_qQgyWMsZHLMJ`TY3{K`-qwEWuC4nE<6e# z7;PYhS!rg^vDS0Mnu!>6^%8sSb*Zpt8|ge6Bh0PONg0fuYnO8-4Gw#pE7CPaoyD&t zhGG=pxXzaX;z-o}JEk@X(%1v=aa~~SR7QKiJZm+Mt_~qYx^w}?8JpVo~M8qf$ zNAhKr-~7|x=Q(L2B#WPFZvAylPC z`DWA0+nei?Pk+U40&uT;aCLqC_@giLN$0!ki~KA=9>{Ig^G*U&p6x)&Ch4tn_j0qv z5v&u@;$x5>8_rKEJiL4S;pchApZ6LC1Ld@Q59z~$llggI-v}fimXw{{0=n<|BM|1c z+RY>G#)3GrFu@S6DUIW%5NV*S%QndHh1-6pO9^*k3xWxdHO_sMuR1M$6`b_vAd#(0 zzv~%BNw|nao4CMopiRK!g{EGjP(!i-OuAUbjCEPKs*uI@zIzSu8gv8ObO$0kx7 zGowRdZu;&Vu+c#k#1jz)VaCh=2pU=#Aa|=C4Xu)qFIeS{1d;3tc#PQX5jEC=;WWG% zuGl(6#edk1lRdY#aa6#lBm=LNI5<2}$b;P6)?9nkW3lRxPH%x_($il8_Es#OLz4UN zP61*XSh+{c3?XbV9zpao72s$Ht(B;ycz#bRFJ_Uxb&CF#YKxn^kK zbUN}`OuRGBEnUOiphk`z7*r6EZx;^?x`WG*dNjofyIp(=+>n1d zl!LbR_7xdewdCFcgEXPx43gA#%}hZ-7^_i}X2zo($44i88J3N~0WEKobn_ zJ9%{a@bFPc?rzR+^6Pba)i#@$96Wmb+0mn?7x~$*Yku}C#3gbIm2o8JU1Pb96VBe& zp6JS0L&m|}+~kWzfBxV65Bc3+-WeE4o*h3u`R)(@$Hl8ZmSo5KC-khq;L_g>VgAv^ zlP(+dJwUZn(jO%uzq!5Rta38uJ3wFMr__!feVi8oQtmuQpATOjKRUg5{)Z_FbI_|> zd|J)yW9sDe?h^$VS6g^sjyl_{O9phX%DT<3HXIy1I(hm@e!BVg`fTsqm$>q+30H@Q zPxA&OY;xI|wD34Cx&3jnl>JD8yQP#9Hi zEX*;MmSGoaq)n!P3^^E$4_>s{s;pl!Ps3$RQ0L#QmMGA!)EbUSV1R{ZBYjC17jceU zz&a72FBMu1-Ns4%Hij*%U^E45>9R|jbIz>24kAYZy<71sXsH9H9IXRRchD9h%86lo ztyrffahpF&*5sKnhbi=|183Jba3CGiI$!y0&skccrAQzqiDF^!xUtb8lj?(^MF!lSU6>=vAF6JA8Qp<`rDl*e$7-!$vLv#G!2tJc zWuF*aSJ5HsaU-Eat6mKUS}TxkG6htI*%E2+CWpu;N3f*U|;iY5!j3=y>jU!z_fN3h`s9{Lyv=X+FY5|gE2_XDVor>beaZ-foy)3wBTR&cHc%Kh_}ho6YU@-Sye&Cx4Oa|6FV* z=i8gh4?g|+`pvi3XZer-FM=2$4tkc9)Pm+7LMgh`YbHVZ202c$vv2|AQYk+?c71(( z`e8ojadq}QAM?h1DjVc=YJGV4IQ7m~=5Q}~!(+<1(rQ+aW%9F*8k^*@80rj(V^zU| z^J-U0x1m|^PKEr~vvM#4VlM+%PabP-lcs%;2*B3_yNx-Onuimxrm4vW^0%AowfE>S z0u42Eelyn?V_E)k5Gn~oD)00&UJq$nArSiQ}Bzh7S`0h=>*@zgC@cNn+5%_ zl_LA1=)l`*qt~dNtj^_$lbT_Nkc#;NNXaJ08selk&Y_b`eb#Us8BD=AG)XIf`Ss1=yJBaI|(TOP);{CB~J%#B95{ewi}`_hAPvkME?yWn#gKRkT7OnqF{8FPjjd}SFw8|~>kD_KB}&Bb?b6ph7P^_J*p(kR)ENQthLYG;k@bxMt>D5L z%dg+a5U>p>ft9TXvM!MLKgjHuzR8O~T)o@LVd$J4>ktETtW(w95WI~VP^sBQR@LO< zI1(I9z&WoX3##g`Ln6;g&T60zL=#5*6^5T=OL%xGUczRC z`(t)s9FCkl(H>&_fx!6IKCfPjrLzk;fd0T2i2q@m&20fV~MUHs_=3y~of{snL zDq7v0F-leKsHmhKM$AqoOR>xqmRTGoxYnhTwb?)u#|kXNeeho2Vq+e@cQI(WbvItL zHp&Xp#dun~VzAwCBU!L}`=xWrwMw}y=}7LC&ay|+BJ2_EMR6|`AaM~jmAl_INfm3j z57TtZqa>7{wkNIt7drBsgS#9HWnJ6NY;zI=uD7?~^azW~sKoR)f3*}AR$&}F8xo*P zs0=+G;dfD^obw6e&;&b3nbSwyMA`)eGXFUc_!9Vh1T;rau0UVpqO_j3ZN5R~W!#$JT7?qopUiZRFsq#&gd1uNOzxvzPFQ0i}9_Acycy#dn zAO7>z`7_enLizvEd5h$&VTpN*;d>Q3zyFFFU?FP)cc$<3@^wIO4o;pNKK(ME1I`t4 zF3=z5k=WBuFMjyl-QD%Po*TOUID{v?PIjp%jm62P^SN_Di5&rO_G8Y6JFfD{<9r%8 z&r0Sch!kJLhMdnMU*zpp509USJxmPTQDtG=PX0=n7Fs2rH94}j(|4G#f;_jF=xc0 zUq_wyqQ;DJ4)o?7q-=PjN91qC2b-+31KGc@@es+R^lJFl7z_B=Bs=nOOk4yZf&0iU zE?~&09@yNLd@**f)@c+JoR7BUoQ+J&D$tlDh7>stFlU!90mOe_KqFNA@8aB@FLXul zs_EDS^t7UeuxSAVn8yI&Phe(>5b11+W7w9O@UicSY!i0{Gl|h)yRnf$8(R7y@4_%^ z0ze-tmtw0mg++ljpfvj%KROvSkKf&JN*8Ud*s5or|3<_hY?{{^1`aXAPOj#J#-}o( zB+WvRWy~<$`~hW?R##}z69bDcO_NB+h9Ne=SIM&ot3^~=NcMVGCNW0}G*nf?Wo+Dk zjH_yNYu@!Ulg~h-u(+2qbi_ajHenOX5FEkM4JLb#ZU<~1zU{_3c92lVR)JpR?6o8W zg~(r`#To(xI9=MQd=aC{fUp}G8p7l4d`@d^9dUGVltmv?Y-DHOHja82YJ$Xh3qUJ( zU7U(S2_UpkFyo6eopmY!vm}LU4yW_V7CD~X8vq+`?tyTN2|LRZLcRfmJ92GpilaeTIn) zN2>N6mf@{!gXdjtoOZI!eO#7eI+)szBW6ZJkCAkHPr)j5W^^B+aL89 zEPs{yo7wAIeULYZ_SkZitQd%>;S(s&=MoQ1D>j61@Gx)v=i6U$`G4^uKl`;~E8@Y? z$p>E@96q{y^YSjg6`MDcW2P59K$GJ|Uh|w2&5A@vTc*IU2tI&}@|83Bbir4D`FD9= zbG}?CuSG<8^yv8PfFpMc=DFeb#dZ_>G0n*002n)cI?{3#KH zby&DexjXyj|9p7$;((t8c#xl8JAd)R(UZ?kzW8FBWmDe_EOo^1tZ7~WjH<-KKzGZjXX1~FZSUfpe>AmbFgx320_c{Utjyf7q5mvsg_ z0pBJoSB)^hL>=-5lHHLLzGy&WI2qMgxy;#c^NNEWC9hY-F@!!s5)Yv9ggM85O3jNN zc?3f*u$Z)A#fV+D5%TZTTZAX8I?Y&c~nFAe}@F4=-&Yz`szX#muq- z;@XTD#arrHvc;6OTk~v*77kOUYoO#?r4`T&V^>S_)^xkpEHk|Jma%a$z)wn%8)23K z*JZ9dhQ`Alj3f8364EAwl2j*fcrMg1~Fp48h@neFvs1Sw+Nd z@Rb#`XN6aU*~Nv6s}h8SzW_mPzfsUvz?Mrfvl*%qHo*p%iHI;Hb7WbD1lE&mLn6R9mYnVR*dj*8GLjJS|N7`Sb%fEc)qIV0A-u=PkrQUrY%dNIZ@fP z%Aqqh_SGAS5H^C@2vA}{$&eA-t+Ndj?s!{{rcgKC61M>~2GbZexi>z8!%bbKQ)~s0 zbyn@PI{3^@oQsL!SfMbw%}Z1jDyQoWoHo1cYXUoNsp@LVCc-Gwx&T5|x)_wK+L71X zK{ssIIZXdOk8#hJebf*dMZR9=!e#;JtXeGV2}^Xw`Hgor8I>@(yV?1iS8tyG@i$!KaR?^I_cUCV_dUv=6#cl>?nA)uC76)kt5Z0s z|2Uu;VTPY_U7zLC)Q>*=MSiI^SABdM`11V2&wqLG{9p3=051ZJ4f(RdrW}+`%~qYv zH%Aou-19ooog>Q}jSDWIy9fC&z|rZ4r;k6qzIc6mlUIE1l_Vbs&d)tOc~`TXX|+H?Nu;X zy>6p62%T$37JkG~OJk!HAxfWcgUXOFyz<5c5|(ANWMp2AVr}Dz=O^SbFfy}a=v%sj zZF60rTDwZAA4LJ`>c#W&vQO#r-Hc?L*Ffb6~lx%W=G(hVBeiIXp zel*M@imeer{$m(GX(z-$@EAiV^&i6oleLEjnt_AI6GVM@2|hxdu=O7S^*2L_5wvnq z0PcEDS2prkF~FP<$}|b8#w-mRcD4f3HB62=qOU!n2H2Rfq%G&0-%1Utsae>Jc^tuY zFg8>yQ?OfjxbG6rKA3GQR7aIduU8AwAz_gL8?z}{2Gp&h;dB^D_AX0zt?8Kn06+jq zL_t)t!A?u}5*Y%<^$&)r#{@&jawjLJTq}aA2vu0c*f|U12#2}N;#`=PF%wk(fzOg*^GSHmA3`K|wVS2yS1 z`DHsn4sF~Y9zA{Z^z)m1gXG0)YD##<=2B%j8pjgCP4q0(6a8L6mb=TGQkAo-5 z9|4fppU^~QlY(%Px<3Em_{kTCryu3jki7fpCZ7h*t1=&c_WIlZ$14*mmQ1NB6;DO_ za4?-#M|IB*Qn%)5v}^=J(0*(OZ_tbv~bvhkg9OI^g)> zB+C)2ElkT@Y@#o0?gdyC4II4gy#$0!A5vl0v2^rpjj>wc^TgF?^;^=}vgkZq#}~i^ zkpJeR7rWexm{GX|7Dj3cQy8;QY{i!s>YqM2&=Nd51mxf2EFBDmM9o1Us&!~QK~N1- zBXUqw0^n}j+TfE(FXd1{iB-W6lA)k;L^e(<( zqb?wrQ1E&vS1l24DYLXXF^Kh;kW<(-=U6sEwHgAfRJMr?m5z#+S$Kpvr4%^svLR`J z1BMUMeyi%(G zz0nW=glQBn%|IgSgKY^FMUmVE4-OZ4!i)~vb#cE*S|oM)ATkN}SZX^*w{rc~c}NKg zaSZbk_6k^WE<8sKqRiIBlD1qiUN(VPoh>{%4k%{hY52(zVfjw6jd6zsE{I@Lg+)pU?nY|RBojB*&VT=OT94oXc%+^Q8#3^lB;plKPT z+0!dA!;!7l>huyd9)YISD!wa_rCqaIO-XZVyI49d?o&tKmlvXTSlvZ zQ;^~-<$5%f8#^Nmw}w9l$YtzC7`3F4saE^jtBFc;d<%tmfG)!^=nHeZxFa-d@rhB?07^;!WYzomYFVr7B47rUjPL))~sez5P zuMbV_vGM587XN7tgieQ*bXC)@c(gjxgeB|Uh*pgv&>pwt&~l%^lPY*LD45LVIQ;4& zX%k8&hX==pd9ybU#PTfk_kQa?&H&gR9y~hz;LAJ*eR=kBt^hd_2hz1u@3@{ISE)SH zd%)t@_kaWi&@kw74}JIG>%aORGsv^vykml^|KrQ^7tj9rf2Y=)vj+6OEsXcuyOR6* zpBnw^4*;;9>XOY{&ItHj>7K_bcQqcihu3d@c>L+F9y~b7cZ22?kgM~v(~rN%+i~(b zz(HQ1;pC_*?0GWG!$IS$P6`#ptW9RW2Z*=5zeX~3r5_|FK=QY)w~$k= z^0gvpOQ8u}oijHRu%W+@wMZmi$HA#!1BlZ z8<{hsV4}l-*1#JNcule8CTJxazcpeCC~4FL368}hoPH!5ub~2#7??4ESq0Ey9*tK{xi9&HuzDqdntnoIq& z?B%avy*GnwNpRwUgl0NxAae)G2?Jl2+D`BW$2Nh{FK4YV7~<0F4X2QF+xQnQ!YN1r zuF`$vOoO?zU^K7EaoSUeL!3=W4*A)wCx1^&? z>m541d&DiFqN!W}Rsy8?pfu??kI-%nGCIhWml-#w$!z6`YeYmq&V<)Z#f;Gr6U&=B zEC#Iq!+h8@Z#ap=oAdAQu3wAYZ5|#RJ^tXUd_Lgv{B@oz&aoy{BE>!d%slF7?gdCE zW3sS5q@s6%^3^;7!6F+jL2o|&*b%HHa&sO0!we63if`;DcOcVL~RWGA2eGQSC& zIS<3t)#Z~GC!qjtE-M!dBZ=;(0B=NJcnST)VC%U@V54%kw}_ zKlnUz<1Yg+xvS;f?pI;S_XXu)+F`yG==R2^nOQ>6al*}NU>(#Kt1CTZsQW+3;#U>y zuKPxb`PK{)nBZlSNV`luKMU_WU-IfO&fw!XSsqgq;*$rN2P)nzj`))7yPFt*b?HKz zJK%27rcAe4YnNNtcq*WE}a`M}nxgejl^Nh(W_F8Q?rM2KYWJIuIPVD$#Q zxp?WOT*$~vRTo4DBkx0SmP69o5j@Qb)g=RB(WJE03<+%d6aonXkeaaM!tV%4l^L@J zpo>$N2u4O`D$4>P8nNxfN}t$T5yX@dCmKi19-!9`b|%NYrKq_GFEu+?@G#?B%eY=b zPqY4nM-vww1Hx~e!v zeQmq8BiUvF6qxF@89pTCkKSdEgRJO8Bi?W$qm7@epNY0M42fg~)`MqbLji;~Z1WTq z6NIAxEJ(VP#Xu2HxzL-SlZ?I!WjyBIQ1C2Y^l>=BHK}ctRdoQkNqpqk(u)8)|RvnWf z6&>O%IomlC>SxS@+=$ZyY#VZYZ9)JTFK_KTadgqR&!Te#e)ArUZ7~zf^dsM58y7Xy zQ&&jtqgf!X7FUYph(mGQk)Tpn61qelrwE3^a^?Ql^;IAkz}gZ<*Qd6Ugu821 zKIx1C^Qn0>lz_dfXBU=B_me|~iO!R6VT>#Os`pkD}0Go7r%RifTa z6oKPZFVT1-9g{;$aB-dgoU`(v;`D=0zxua-clL@e{&#geJU)5!;+x-`zxbWp0d78s zYS##3*Y*ed{T<>@?)Cl{c)~7Mw#jF$q_>%@2*SWN-zmkr_-d&J|NAXtrvCidZ?0ba z_UI_5_!v7 z#NiV~RRMP0)G%`u>bhlxH6(f>NvVB2M?GS{aj55%;P~3wQH9DH0K|Z;S;+!}DI}Yi z<-sBoT`UZ+XY2fRh>T%)%ODsRGX!s7W9yeO^C3<#(YYGyn3RYKTO^Lzf!T$P7>r=B zff}byod=XL!8eTo8+v?WQaa=*UVRjzigW>)BsP6S4Oyt68Cp6pbiKg9Z0KSK!*iP2 z*_I@d4Qo_2mm545T17IURD@hNSmmGMcz?4O7*P^AxJ516qt(S z1fs-%qLCT?G>VfN@JHLt5ne}HH(CQz1E&^68D|mI$GK|V5lx1vLS&V@Cp;Q{0P>fS z`5@_SVKV9=%ECqmJR3@6!#P!}HhQzW(7KgJ|dhZXN8m)-)Z4 zEttY!ZcR3%(Z}ze;C{F3V`R+zz4l{vJNcrX2pHyeFKoaX+>$BUzrB2O@=@Lh zbdb|nu4yjMGyL-I>g?+EclpqxqnBUJVjllW^u!*m?Em^ufd9TvQz7y+AkDO#fuan>g(<*?LR3Pr3a5z1df(H6+oN~P0Q>NAZh^HVl{kR zaXxhqu$j6Yn&^7h14(-G;MhFPw>|lF6B^&3KTeA;FAjk31(N|o!)R<%D{4pKc|?UD zQcA-`1kg;p=_jrrcr**l%=8lm4qc=Wq>G*nP8ta0%tfnE0{u}8;20E+S!8kJYf|G* z-u8{C&uZhN(=rqxnHM)3#@tQMDJ=HoArWU}nHj@`ZJI_d7#FA$-Q?W;P&N3;!P?>l zY|4_2raF#Tf=DWDnN1-qxG{ZENifh1ch5nDNtG1{ zmO|5@8T;rHDTatCcMhWTIg}7&vBfI0S=VgizY8NBOd{#=pn+lG$7O~`O{&8|f{BL- ziCpl63&}Fd?Z7CEo{-e*2{r&{K$yRy!f0z$1JW(*Y9=_=d>X3=4BL=IQWLv<`W9J; zEH7dKO@VWZhWN5Lf%3GmX~qy`O*&YZir3+v8@BMGlyG`kEaPQfVCwsGV-m3~2zUH%p(`GQ$yv&;ynL{*_qOO(r%UTU^#&8qKe z2{hi!ae37~WOFChxU!X}@lnA1IjwqcY((I}TMD*`W1C+XE0c}H(n5^cZp#d$$zO=$ zk*L8UZ2{@Sp>$tJZ8{#XVFTZq)yoL`S;H;?1XDwdXp3pdD~{GsI|`;?%(aS|^(0$P zHR)^^^c~xwyIU*5BC>5JOEB`;t=UGKA{*!q+3@x(2pA+&vn{WPrt;0yVP|tkwXC|Y zTrw8#9JyT;DLg^P)t(DtpaQyY-&h$72e4}*sa``}Rscw0y~IT15pedyf)lSE*h*np z3(&TQ{V0WC?)&u?yTFV6DZJRY+p6~fAB zTXxVU20~j`mW@+K9u6kHvq%`cV9EQ>KmO{kKL7gPz5M>0yz3_%xz9L0ddL_5pXEo0 zy>c_ApWaY`y=Fn~$!0**_vD+=zBrh-9{>dRmR$@g>Ev!igWTs+w<4JNDn7#PW@Q~w zm)H5&_0iMM^V`78TJAJ%uAhALv&)zN@9otYZ}iM$E*JFd6Hl}e+}z#zr9cn)g`0592!ND zv8T}bjKy>~{-UTkVf3Zs_ziyU_EjPot_DSF&6of}$OxhyB#CJnOEse{5|?rE(k~X_ z9@u^MStGGL~yFU_MZq0DxP%Hs`G{^~{V zD>$W*lS5<^m7b7lvo4k9i{ZJ85lUu5K^EUqI$$*@47FjH;8qlRQcpSc-J2$!#>{M*Jh{@EN0@v* z0x7ZBkzQFvD;-6KBYKE>5W%(<9hY&gBFu+V@YY{R5ekjDcbnm|NwplIk8CDHJcVQh zdPIv4kMrWZ%_At8tVvyjre}cqgmwy2r`Z}8m0tY?o#K#VS|j2PNV!K+PfGao%!Xoa zcn_cROHwF|6EBX~y0mt4*OM5l!f@bXa%1AT1U{3AgvvyP500Q1#DsqNtJ)%mF1l=q z3gs}%fQ%ohV6Y1&L+PuniQ9nYfEBzuDoIf)!}GZAaEz;rYrG51CLN>07FqtuB>)~6 z6Axi)z{awx1>2UMRg$+cs6ao2>Gp<;Kx!Mzu~*W538l&=GQ<^@vwgNp zmwto9=lzfbu{=x8epx}9eq(0ma5_P8tP8<*!DP?S1r2BV-lnth>nUg!3JcD$Ajq`L z%E=6S$n0n&K(WU}D8@BR(P0bsEOj_VMYfE=#5bOuvu!1<6&tCK=z--t97|8siz2pg zWxI9aE^qwLjez*tpC9h{_7|M%Hk-S{!;{A!ex0`nUA%do-;DAADGl3X%W1@XSMb&b ziN`9SJ-37&XZOw+`LVH+(+|G>>%Y5r^XB^c$_D_q$0sLeFaG%AkN<$dpVo{y@$6b9 zyYYVSr*ssrt>>wIJNnUXx9ax2Rx%8Ws6uR+m|AZ#I+gG7OsTHUzI*ie^L#Dn&CLzp z2y}IMcbxA?{o>-;@4ZH5?P}YSXDXTd9_F5?qU8{+BwPG5@W0{FB?MPk;0Jsl_&Og5 z&SQYb`3N>&*e-y$!=DcX-{fVQlPnAF53=m?Isj=w3;?cP%4bz1fVmr!B%6jz)bz3Q z*kD znGLPq9-Y86Fik24RqFOo>Pr%9ps9za4$3>L_@``7=Z*c0Nc*v(Orf`!a1?U3NHbR&&?5cnY0 z+&HVpzh_m%p(2b@W@x45FCB1*G7=tW;;j^M$aD)405WS{k;Ep(fi=MZkcg|CuBX*( z0WuK)xXH!1d^Knoi^nnr*-z30+VHU^#AuL(%|P1EA=7)4UF~WHHBP8AHaM1{@9g5$ z^PwToXtxN1!$cdOmc^r0abZmuFy?p>$u}TN)~xE7Xq|pGC2ahvfrp7RQrhZh5)x#d zBLQvoM=E7`N`XkmKzoAKSDiGx@L`evbmEaZbbyo+DF6(VCfF%U7~`NYnOZt)t1a=P zfDiPTJO7;1)>(Rd5oeG zgoRD65T?YWo@e~D94=tgp$M3hh%peCN!FEHD;9b8V>u;3I6-wGbr9ky9@!caie;q7 z$&>dn5@Jh2)HH=*Mw10vMftcJFtP_ zswCZ+4kQwdswgasXX+Y+vQ9x7s}sH^kyjqF zbobUOrgPN8KqV3pONC(v+4Z0#gj`#ec#emKVY#wEG@j@ILV{2#77P4=Ri?vnbq|Yw zPHt0jaTw)vad>!qbbN~9?)v8ZS)St82OhlTy+J3BKlnvnA~<{V;yND?_5OkQCURUS zhP>@JPVz*f=4KsE_PNibP=uNwlqpsKI!_b4S7$>p1crr zK?md#VuA>OS>n8%`Oe2epMLbk!w2~qrsqsKj6<3)2F-)Tg99G}6w+kLr zVReKZ40?gZF&OUHHY1`iHbNgz!7A)7jCz77(u{UgKxNJzY3|E0pzB_^#SuBmmI8@T zqy%T2>6@&>vK0tcooQGE?JHdv<+yI^kl2?jc{d)tAkE~FLMyUYBN$>wp~xo#+5%K* z*Z>J$jB>sKrgy{OPekQ1Ga_J~zGFgj>L#rQSeH>r zFis=v32X|0Zs`>T*M_+%I5d0!hF_D!&`uCU7Nt~nk%XE=V4`6i0Exwpe}jQ#1GM8- zp?IMLCbCwGblDm=CjNVu1|C!H-Vo;|0ZS2yFGnMFl^RVH;5DOAd zBPMY%Bq_}c3$}H~QVV9nNrYMvjA>IT9aI8sxl9L$#=7HyM|}|}a-eis3Gt%L4fy_% zkI_=y&HIK-Cw68>bRD{5W9gNJW{SgVe5)CNFvuw8wjrHn0jF$hi=wLHcY{VE zhvqvkn(Wlu&`5|SofTh(3iX9-kODIZC|gI_WH46_ z@T?%Vj!0??bY~^DrPj#VA>idipP*>Up&mA_Nfhsbk9IusbYc zC1!OSz(f=Kv_>=w2+p)EO}m9x4g(gh6vAb>846|@2e2sRgpm8h{M2l&{_{4Ad_wHz z;@SOQf5I?t^M3r~%cIk$7w509&tLcXE1VL8RQTokGou7CG0t4-ng9yU-ma4LOfuIC zw-0WvzxvDn?!(W1{_@#(M@L6_uY2BDe*Ea@+u!|{%h%sb(L~GeU$-%haY-ou_04+% z&EB$4NIwzPJuow}@}3CZZ_1@f7vx@`dF%i6`M2la{{v5^#Odwz(SyT_SI%Gx$KcxIK?F&H^< zCfUddG=9;BWz6G~SdeoKBqWcJHz^(Tl#99B64WP386gO%POChAChGy=k0;YKdJ^yoRCvEQVuOiQy9ug2MBWT+2aWuASkJ7b`=wVBX-!CPfQbRqY&o;i%9Qk z7ej1%J!jU?%FNo3uo7pnSR|ziinxg_6u`7JBZD?%n7oOsYgm^U zxGvxkYN}!DEl8IAa@qGnRZ#y+GrX)|BY~w3M&sHJYb&=p~s7Z zxQ*662Dxz`Srw6J)NRt)C^+(=iUyfGyRt9E=yg$ap-eE;ORqTqi8vTk5mlTbgky}+`E%bm`Ms>n0R8ZdR1R@KDj#mVGQ%xT6&^AcNE-%D}tD%;BV>1=h`P2#Uf1hdvERi<7uv zVBN$fDu-&#ODc{v%qIAZ5W6crf%i$PExYkxqL>qkGWUdMhpRODbN0*81kfd6xC}Y) z^9j;}<5P>AH*>taz0PmHZml*CPo8{seEQ+l#hZK<)O!QvoGi#Sk+IG=4>0Wpz@lm{ z1mv1rkz$Kz@01m2-c$7WMvhC{~@mo@<#Xk-0Y*1*WdsC{Mqj~hj4+BJA%&S z7U7@M`SU1tuK*-6lK9C+<-NTHeb=y(a;`}l>igi1?rtw$9OsQdM^7K-p<4{{w)?xg z(~rMAfBw(;dVZf}apL2cS@1v%UzS6cpbIIROhK#3WCUxxtOM*65xO&)*Zl7u~8WS!TRWu2uiQcVq)2(mE)EWieZ<< zEruvEnGYYjLZ@nR7=RX3U>yBwh2eOHAyiu zUNBCUVQmgt8f;)_!e$*}3a(Z2l?q6PxiK_sBxKaVM#69(5EN@tPeTjCYB{5kOF2Q& zT~&|=zlhwlFOR@7OD}4#!r&>O_jzR&9q;jo7XbeA1XcN!prR~hmskuhRUt@;(u2hn zLPRkT6Mb3+RK57lXdOjFrx1JkErItdlOvA!*O`GCXMpPuuRF4O2d%Kmu1Wf^SyJ`D zx=JkvCKDJmoGuu~KZ(24GD~ z4Max_tc*vGZ14CY1vEmL5{GP8tCwI{qB!tBRoERLwT2>QBD!K3a1@h8dYZGYpa$tV zK^pXP2tc6w_(@k_bY4|9tZY%}3MLln#9KKU09~9Q+YoL*84N5+j)2w2JIID%${LkZ zrgV*#a^-r6?YQ)4npII*W3IR9XTY_WggU}mEW>SCh@3&1QAJ7w>7ynt15&SWXQc|` z{n-gy>1w7HV<=bK^>#uclG^pXLxhdl)V|E<)U#dvXgabGpOzUc7j@%83y4xk2u{`sQRT;uFwQfSR=XTYsY7_)#;ZmD;0TeZPV!v z=ZLe}+gR1 zpKfk07stQ8mvLZ?uY>lZ-QxMzx$Yyjw3ES#{63=M|Fy)NqXJlz$kkb zNxYG8-!i!_yvvkv#7kyS+U;`XI7QC-2d| zx#4Y|EC^4<&5dP2Y_Xxo2A|nw-4JX!k!i_&Jn?`@S*!N0pti2{sB4x(px`KN7-Fs3 z#kzBfCZti~mlz@uV=rkWZlr-^#}S)tKnVgqjJuGrM<`Sf=>(E~?KOm-%eY=}FYYi1Zcc7R>O)+i-8us)-qo}!j2y{fXxsMkLAph-W>ljY~C{4#~it=PvlX^(x zp5n2)-z5qp;lXUSmn#Wvw>Gl4uo?;@UwB_v~EUkB`SW-bjPM=@GrV5kv$ZrQ-VT4j*5Irm=%`*&vj8dh_^*7B61%sakN1e-a^qLlYjadQSDYHYHf;9 zRffc=B|BKUqeBMS6Jd6gD?lYgp?p9nCK47!fJ*3QprF0rp=qlM=4FJ({yD)qi{FTi zAwDHr4v34_gK&`ltYzK<90nSm8wIvvq=!E=9_OZ}_dwJ(JP3yp+q9Y@v=Gi(RJof{ zKX}3I=h+xnM(&2->?&aaHH9r@$aU3yb1`;;q|TcDD|0EvwVI;7J8HuIWP zj<4$Vg3YutFc$9`aH51v*7v9;&PA|zWbl{@W$acwd!6NC-~D2XixpHufgvngtqMnW4jHeHo$f*qg{GST(+7o*}eV7vG)Mr z9$+^B+C}@WpB-v?8?QzP8^pe;c@BQJ8gkh(|Ff)xq9A1gQbbUtAElK zjAT^twFtfVVYF*eO2VCGIU7c8NSQyf%Qk@;7~G7vqA?Idp{lJ!ILMjkSRe{bK9d9o zK^ng0)d>5($e&)&^7ZWjF^n0>V!XUo5;6E_hFDL6dI3x9V-jf33-GN#z{lQtC|0F2 zHG1rO24W_x{#+igU~Us?EEKRY#6ZH);VG)7NM5~|Fj*~Ywe!3qcAX4cq@~pjHY_2e z#KvM^dK=i#;XMB`DrRI2D=e;$9u5#XHijqy#z!^{ z%<$JRQryr8ikp2c04W^V+QCL`2J9%hF>Pm`ZK-4)LI{G1YO!?R#4_n;R(8UxqC|kXA`log>`Z^oMrLYV|gZahOr=RCt3-<)*8QVGUF-pG-&75mqjF z1Jfk(EovyXA}mjf*80~OhT{RB++dS$mlhK7@ME3kQmttuEER498IYWyh(l}V#dDo7 zNzxNhNse95z%Di?Q)_y3N(!Dcl}=>FL!|jy!UxAEPom7*Zf~#OT<^Z}KPF&8esJ_8 zSO0m+->?12XK=j+*4|`2bO^0kY=xQi575RDM}5{`%shFaP3azy8~2 z-+u$#^JTt${?+r}zxd|=(r|B{h|ME-ys;x8|Tix;}sQ=<$b#C!gG2-=zL|Bye?g{q*x+U%veI=Hk`C(UHeb&fk=U zx~KKvodatQ`z{JjSVi)94y*=a=>kHUrEqt1l?NNAPd_<%^6}-_tJ|A%7^dOo?l$is z%lpdu7$9NLaz7IY6w+kRG#eGJ8Fy9_25Tamgae>QLJWCiA}~S#x(y21U`tg|4<77e zrAaoc8Ue0Vw4{={i4I2z4DPhhdk9f7;5le|J%mQ`;;@!E!Pl3a*wg~=%k9*`RO<_s z{@3XkQY2gek-NT^r^G^#HaM--5>Raqwk61ZJol26IE`j9Bn`$Bb@Rm7qY-jd#82R` z#YaPcBLAo)4_Zis>F`cIO!BUSN!Jonzjh>Sw_v|K%NfliyLE)@Hl$38Z?@!~Jx>^9S9+N;nyN;K zFATfTSw+rV34zwe5IOfGs%je&W+Fg>(!tsrOr1snW{@a)ixU*8fHc~$^m$)b7M!9A z;K32giG@nbR)%yc(33L5L1@Iwpn8N@im@YKB9szlqztmf31fjOSSm!n;j=zT#QDWc zW;=Mnkux<4*!7N>m5~wmE0jnL#JWgl-F>KvmZ>o;L(PDLE)~-nzZPPuK}k6_BWx+E zH7Gx9o&&H;shE%#9lJPmMhIz|T>>U*T1az?Ky$V%+9;eOb^r)2eH!yHU{GnSa~oZG zEfoe#iUag@I6a-#sz*LcP=n+rbI%7BULLJ9Zff>GIg>CSzuJp@*Bx0N{7Y4}^ zvehbG#>*`G%GstBJA;ZyZhGAr&*opem*=pxjcJ$eVMm=9LCHu2{ba{ZY7s}&0B;?v(?NKzTjC)W~%u} zuCMZJ@~{8>|N8R9^Ssq3pGUa8xjj00e0}lq+kg6d-rwHidYNdoH?TVay)BN%cSJua zAb}q@**yRV$y>0uhU4Y^(jN^WaF)&c+B-}U++3eMJIU9w9zDLzw}KuX-{g@%e#H5c zuikw3kGvGicS~7y=G^;2k3i?i>Di7)mew|ZnW5mAFr;i}X?D%!2M2DiFRw2zo_ze( z>FI~(ufM;`PZ6)XLwK@yZ_l&I=LNFz@-kCj7nV;-ow3BtH!kA;|Ksh=pDanT>%6Mm zYwhmYdb($@2{4KTA(IH1jHL1Z|CJ&nnqV}eNdz#!EWOmyU6qw<>3rY09`9u>jTxFu zo-ZTZ?{e)yj?JCjB{TmTq8>Co%>dHSl!g65#Bje+CH;sR9`p(F&+ zc6>bUF$QRNXC+dIwt0sZ7tdM)d?&)yI@U=i6;r2#6d|X2th`vAPx+{ zMu-L5x`PB(gz%u*%Z6|`X{r!$tRW*k@;gqIo5vY?R<=m5t5W6f@m`A$$NsC@lK0F@CYvG94BKjS{B;ijYS)u&eD*#Ek!#$i*H(=m=W}L0C>P zLz0B*h9@oCf{ec!avi- z1|31WY8+7_9OFT3uS>WBNF&PtP6+H2gMqmAYyMKN}zBcTq+s@kA40Cu~zIm^+My@ z5HSQ0M^8$QkO{?zfVG}Jpn9=e9b`q^bClt7hIWnEauX=5vLb{mfFUE2eOyrG2n8I6 z&J+-%Q>b3llAJ);$20E3iUq>L=&ba>A0yF;VQu3Omlio-;8VaYsMTO`9P+tQSlglmNv)jgU1cKM-GIZW##0dVK&qgJyiHBd z7^zGz?mg#fOz2p!B+@f(VU_|hCxQ8M*u~g7$9tW@sOUDIFs!j`T@uLrj|eG|_9V`D zS0bcbmXd7M^X`#6N6QmJ*XK98tN&RxfzDUCJb3cO;mO0i@BixJl^D5jVtI6pD1iV$ zcZ(!LL9mO4E$l+4+qzYYBVPjWCx7{G@83Uu_2T*Q$;sQByfNu0>3scJZCdE5Qv#hZLeH;Q)MtPGV6pRi#w~V z10ExK!4hSQ(5+`ipa|V%Bm_)nHM#ugmn11##pokxpGbgY5&M^w^VWQ z^MwdianPWHm}3tN((WlsT=FQ{vm$jxnQi_hFUe*Ri3SY|RSMy;PibZt$4O9gc>=C6 zxN6DD42dNvQ_LWcptcF64-T-yACK6eRKOI7=1RyD)$5D)g^qBAB7>3!rY?kQ*wo@} zp9nVyJRP=FREe6W87!+MZh4CJ zT$CykIVgk()vh0gOaSHp5?o9jGlnunQi#>)*;(Vc)~O2P{LeKrUQ9}a-Wr2?$(&8P z89*;MK8RL^3SF4t58>=*R4REk0Z;g15?kJ51~Tc;(-*glj8ROKEC7vhECLu)3M4`x z(~||EFdcL>K(kFS>p61nzjxtGjP$Ui1t)Ru!R!2}h5Hq6d8 z4^_~)@>O>(%x@$+#2w^?b7Z&jJGEUOu ziQcB2ZWu29W07HyiRNasD`SSO(%RoC21f(&y(YMW633PreXN`q$nbucMvFZrLU2B1 zplZ!hJmW_NxQ33({yRI$KBXxcGSXfPhB?b0 z+{>qav(N##`Y!ML=cu&l2+;k5lZQ{gt}z*9sS}~h9BM< zu2G4#^_F#rwJiZu)3)I|o%a!H;BPs-WBY@{k45;H9I%%7E-ZyzK9X}_LGu9M{=w17 z<1hTyB(AWpE-oJa=ok69fU8&E+&?-NP`H_lxTWyspSc4U1&CQ`Ihw_Wg;LAp{da6y zGG;u>^5FU+-vxB___LqowWQ0}FZ1bu!sr|Ke6Q1+Y!8lkrHRi2#PKFC=oos#&v1p7 znNf(4|8n4{&Y0E4em8HNN)jvr$eMIdQU~;K6TfNOpJ;76SfeDct z2kpX0JOO8`*27*uLKaF8kFNOJR0LPBkw8pj2$l(l3X_Cxm!`AQCk` z(i^e`c2Kyeg_WBFXasUa8K?_zx7|S*FveD-(+Atcg`_EVoR80%$|vrp7P@$+&gF3DIOXUdoJ)BZmh} zhKDv85Cb9GGSno9J&>v)PA%zZY|MC>`l8`pIMM^Uu=FEV;=Zu4F?i)TztifpsU0@D zWeLjMAx*S--ETk}bjvXHDI?XUm%N%<{3DVO2TBz?1y)U0MOsu zv22hSaKp~)9zA01%Bqp|Lci?V-_Z7B3^6ecppWob2+@x5AeScuK+|mEbZD9t(@i50 z{=*PC_hcDnCCx=!l7rZ;-D0(F3f=ygorHaO`EH2AQhQEd1Tzf39t z88t$8qjiYmG~MYB96h?tV{p;aP!xCB!!`j-YPLpAoVk*J{l&)TF|G)h|APOEygN)2R74>C>P5 z(U*VrzdZlutJq<1d;8$wqgO9}{o*%&t0w^ed`s}Xc6WpVx_8_Al70QV)=M|fTCL$n z_arGsHXGK~soTElU7)*kalGrid}p?AE?(r#HV0>)<-Jq5`t9A1Km9o$2FN?N@)*E2 z2jOmsQ-l>N3&WGFasvx3002M$NklJ2HO+;?>XYT!A92#U3a zm~$UM>`3^7ovYE+QZcx+F(L%tUz`MSfLS~Ur$slh_A+9DMM@I+8%&QPr17_{SYzVR z7L15l<(yC|(vFRXiSNKJuHaBAWKXIa?diZ0%nbJ@B9d{dn)!lN#Zhr1-|0!Z_xIXx z%Fmyb=5Cd$WSYPbnu>)`gHWw>DRaH1GPtDmH)LDbGDut=eT4(|Qt|oy^kwNCIcOok zO971zS<2seh{IS*YSCiOB4^ec%kmLKD&>qul$e646C0?EBYXcx2w&*pAo6gl4TpgN z1fi~&^?Imfgwh0%OEy6sD7q-IsVLETtgaOyDtC=o>sUreH>B7Cg;`!e*k8KwLo2jP z7)vEhq`vf#XT*(Mdee2ngJNsIWQ>54BqD{)AYLG1(OF=^aB~Z|BLo%UVL}X;1Ug~bpTGL4vuWjLV#DLq^w z#N4hn>HCB?*2?2+4S?GCROe7bR((d+Ct>4>MLougQehJ!1_XTmV#pG4gfj+$0O)d| zD0Q32Bt92U5YTrcswt5`tY=Wt^AJQ(Av_}YSYU#vSjy$xMbSbs;cyRqLF$=v;$)0YSD8c(8Zo!HwH??zO{NyNiJRh-SmsyX zC_n_DaD<7M4l1Z;t&rC$<3$9=!8dsNt~6$@u-Rh)F8fp;GW@&BopT1r;Su-xY6f<~ zuDBS068p7RB%c25WmRC7Mpzp8LV%m$(KQRVGK?w)E2He;Vt{OW00@g!@iA$eV3#b2Gy<=hkl{LW+g7|Sz8v{=#$31-kG!r7hV@!8J8Pnvz z;i!z#J9>;_*1z<_y%=3H5{PuJ{=??_b*>w8eFh6#yz$8MwU2*(^5E&^n^$k%d>0Xc z7Ky-Y$T;Z$yAp&1;-QHFWRYQ+p2S!xc^i4&`v3G#|JA>L^XB5s`Rk*+AL(trap?H! z;`ukf`af>+?K1eq?GJ7rbE7`1(YJa_aA&`dkpAX2ulU@t{m@`9&^T`poB2DEcZQo; zI#In}hGkDpbJ?A_Jbm)zy@ULk!|mb0{X92+aCma|^hXyj{wZIunlI()3n^;0-^KIkcK5&rp~cvFhgg)QS!$)+NQdo3kyK95Q-EE27DZu6u_6e zwy}U)Ph&D0C!i$MJv7|h(Uns z?Ky%WQZB)>BWA`dL@d*9!B*h{YVy`K)-#)g5?TlF=#YQmMnXl(8I>-Ep+_xua|fW; zni^^9;O|6GD9)6l2>aR*MutIwrH(>tLlQ3k~8a zV5_D^?K>b!5Yma4e?bHQjD#*2v;g)$Ui5euiV0*#v`De=0zKY?3?I$d zPv~Mb1{ILq0I(Es@a&SEAqgPC2EnHRBlaps9)v1&XjoveCaOLhk3eF&J7fUU3L(*H zKxjCQ8feF+&78riWE2IDy$C>Qow#;XM-r!SAtel3FKSrFi9L{Li$dm}<+2|@a#1TX z5Fol!l^!Rnb?%DwP+3ecE|z|D>tDHjZ0!&$%`i1{3GO1Q*CsM11U5BacryZL{2-u_ zU@Id7#)(E><1O;I>X;3CM7epu4&FE--P&5cSnm|{81A?(epDl12-> zl}mA`Ft?&dMXv+Q(nf`lTN{dPoy1~5b?k;fb#dJkX!I}?Cm@6*Mmn=Kh?~&MALMdl zpALUKwFCp@O()@Qa{-o5^FAc;`!}~Ye&^3R?_rKZDv|Ga&(;5@`JtzL>1SRe=&4|k zp+Tex-JpbTRdWuofbd}wWsPJ}gQTPn^3I=p*JrN&-`+cY@%)>^qvJ3oqJ{R~QfICx&LFlEMADrMd=z}8`%*{GJx4IzFt230@(q+wnmJI`#|K3yUB^Qv25@HjVapdzV`Cy{NlAQ%;KU^ZF zCcKhK418(G7*WlUj~MLF)Rp_HH5JV)CdeE035dkEP?wCGV09bcyHHgIQw5&%x z!3_;?8UV+@7!)CcM#2F*Vzk40F)4<2HcLGrvKq_IJ_zXxnd82j(g3qLeTk~12 zgqSPjLKPA)RuLx=z{gwb2#_+R4et;$VslooKm}sbHOmfhJaCe2*MS(>?6AE@tC*bH zOxA*zx>I8yJc;ad7!35b%E$s&qPs&GfuAy83lC!aalo$D;1O)HCeTVQtCfE!Ekex@ z@BoqZM^qjKj2zR6g(53pG&yO2ZC?7wtjnfz=r|OKKa?v z>Dl?Wf6p%i#5z;KN{xB8yt0#BX0(E?s7Z`TXd~re^jx2_P{s9YWxNJx8Wp9uLqd zR`oh%WBsvnI>5xT;XwhnH1SANtChu>v3{5uC2rp50XtBj=8s7&tVog+6#3LWB$h}- zKw_;d2*VMaG?xvXG$c5yK*PAFDs+r9VkM?W87hjLjJ@PC79-)Jm}