From d10db7ce4295200419e085389ba1c20744b30108 Mon Sep 17 00:00:00 2001 From: SilverSupplier <192233040+SilverSupplier@users.noreply.github.com> Date: Thu, 21 May 2026 00:38:48 +0900 Subject: [PATCH 1/3] feat: add Unity Windows build method --- .../Editor/MagicExamHallBuildPipeline.cs | 110 ++++++++++++++++++ .../Editor/MagicExamHallBuildPipeline.cs.meta | 2 + unity/MagicExamHall/README.md | 9 ++ 3 files changed, 121 insertions(+) create mode 100644 unity/MagicExamHall/Assets/MagicExamHall/Editor/MagicExamHallBuildPipeline.cs create mode 100644 unity/MagicExamHall/Assets/MagicExamHall/Editor/MagicExamHallBuildPipeline.cs.meta diff --git a/unity/MagicExamHall/Assets/MagicExamHall/Editor/MagicExamHallBuildPipeline.cs b/unity/MagicExamHall/Assets/MagicExamHall/Editor/MagicExamHallBuildPipeline.cs new file mode 100644 index 0000000..26061c8 --- /dev/null +++ b/unity/MagicExamHall/Assets/MagicExamHall/Editor/MagicExamHallBuildPipeline.cs @@ -0,0 +1,110 @@ +using System; +using System.IO; +using UnityEditor; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace MagicExamHall.Editor +{ + public static class MagicExamHallBuildPipeline + { + private const string ScenePath = "Assets/Scenes/MagicExamHall.unity"; + private const string DefaultBuildPath = "Builds/MagicExamHall.exe"; + private const string BuildPathArgument = "-magicExamHallBuildPath"; + + [MenuItem("Magic Exam Hall/Build Windows Player")] + public static void BuildWindowsPlayer() + { + BuildWindowsPlayer(ResolveBuildPath(Environment.GetCommandLineArgs())); + } + + public static BuildReport BuildWindowsPlayer(string outputPath) + { + if (string.IsNullOrWhiteSpace(outputPath)) + { + throw new ArgumentException("A Windows player output path is required.", nameof(outputPath)); + } + + EnsureSceneRegistered(); + + var absoluteOutputPath = Path.GetFullPath(outputPath); + var outputDirectory = Path.GetDirectoryName(absoluteOutputPath); + if (!string.IsNullOrEmpty(outputDirectory)) + { + Directory.CreateDirectory(outputDirectory); + } + + Debug.Log($"Building Magic Exam Hall Windows player at {absoluteOutputPath}"); + + var report = BuildPipeline.BuildPlayer(new BuildPlayerOptions + { + scenes = new[] { ScenePath }, + locationPathName = absoluteOutputPath, + target = BuildTarget.StandaloneWindows64, + options = BuildOptions.None + }); + + var summary = report.summary; + Debug.Log( + $"Magic Exam Hall Windows player build finished with {summary.result}: {summary.totalSize} bytes, " + + $"{summary.totalErrors} errors, {summary.totalWarnings} warnings."); + + if (summary.result != BuildResult.Succeeded) + { + throw new InvalidOperationException( + $"Magic Exam Hall Windows player build failed with {summary.result}: " + + $"{summary.totalErrors} errors, {summary.totalWarnings} warnings."); + } + + return report; + } + + internal static string ResolveBuildPath(string[] args) + { + if (args == null) + { + return DefaultBuildPath; + } + + for (var i = 0; i < args.Length; i++) + { + var arg = args[i]; + if (string.Equals(arg, BuildPathArgument, StringComparison.OrdinalIgnoreCase)) + { + if (i + 1 >= args.Length || string.IsNullOrWhiteSpace(args[i + 1])) + { + throw new ArgumentException($"{BuildPathArgument} requires a non-empty output path."); + } + + return args[i + 1]; + } + + var inlinePrefix = BuildPathArgument + "="; + if (arg.StartsWith(inlinePrefix, StringComparison.OrdinalIgnoreCase)) + { + var inlinePath = arg.Substring(inlinePrefix.Length); + if (string.IsNullOrWhiteSpace(inlinePath)) + { + throw new ArgumentException($"{BuildPathArgument} requires a non-empty output path."); + } + + return inlinePath; + } + } + + return DefaultBuildPath; + } + + private static void EnsureSceneRegistered() + { + var scene = AssetDatabase.LoadAssetAtPath(ScenePath); + if (scene == null) + { + throw new FileNotFoundException($"Could not find Magic Exam Hall scene at {ScenePath}.", ScenePath); + } + + EditorBuildSettings.scenes = new[] { new EditorBuildSettingsScene(ScenePath, true) }; + AssetDatabase.SaveAssets(); + } + } +} diff --git a/unity/MagicExamHall/Assets/MagicExamHall/Editor/MagicExamHallBuildPipeline.cs.meta b/unity/MagicExamHall/Assets/MagicExamHall/Editor/MagicExamHallBuildPipeline.cs.meta new file mode 100644 index 0000000..135907a --- /dev/null +++ b/unity/MagicExamHall/Assets/MagicExamHall/Editor/MagicExamHallBuildPipeline.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7db9f4e535f7416a8ddf44d9217a6dd6 diff --git a/unity/MagicExamHall/README.md b/unity/MagicExamHall/README.md index 02265dd..b726b70 100644 --- a/unity/MagicExamHall/README.md +++ b/unity/MagicExamHall/README.md @@ -14,6 +14,14 @@ The scene contains a top-down exam tower room, player, world-space casting input Magic Exam Hall/Rebuild Demo Scene ``` +To create a Windows player from the editor, use: + +```text +Magic Exam Hall/Build Windows Player +``` + +The default output is `unity/MagicExamHall/Builds/MagicExamHall.exe`. + ## Controls - Move: WASD or arrow keys @@ -63,6 +71,7 @@ EditMode tests cover base recognition, overlay recognition, `martial_axis` depen ```powershell & 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -quit -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -executeMethod MagicExamHall.Editor.MagicExamHallSceneBuilder.BuildAll +& 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -quit -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -executeMethod MagicExamHall.Editor.MagicExamHallBuildPipeline.BuildWindowsPlayer -magicExamHallBuildPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\Builds\MagicExamHall.exe' -logFile 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\unity-build.log' & 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -runTests -testPlatform editmode -testResults 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\TestResults.xml' & 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -runTests -testPlatform playmode -testResults 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\PlayModeTestResults.xml' ``` From 8fcdae68fcf76558f78a0ce84d17370136d9daa5 Mon Sep 17 00:00:00 2001 From: SilverSupplier <192233040+SilverSupplier@users.noreply.github.com> Date: Thu, 21 May 2026 00:56:33 +0900 Subject: [PATCH 2/3] test: add Windows player smoke script --- scripts/smoke-magic-exam-hall-player.ps1 | 65 ++++++++++++++++++++++++ unity/MagicExamHall/README.md | 3 ++ 2 files changed, 68 insertions(+) create mode 100644 scripts/smoke-magic-exam-hall-player.ps1 diff --git a/scripts/smoke-magic-exam-hall-player.ps1 b/scripts/smoke-magic-exam-hall-player.ps1 new file mode 100644 index 0000000..eeb3823 --- /dev/null +++ b/scripts/smoke-magic-exam-hall-player.ps1 @@ -0,0 +1,65 @@ +param( + [string]$BuildPath = "unity/MagicExamHall/Builds/MagicExamHall.exe", + [string]$LogPath = "unity/MagicExamHall/player-smoke.log", + [int]$TimeoutSeconds = 8 +) + +$ErrorActionPreference = "Stop" + +$resolvedBuildPath = Resolve-Path -LiteralPath $BuildPath +$absoluteLogPath = [System.IO.Path]::GetFullPath($LogPath) +$absoluteLogDirectory = Split-Path -Parent $absoluteLogPath + +if ($TimeoutSeconds -lt 3) { + throw "TimeoutSeconds must be at least 3 so the player has time to load the scene." +} + +if (-not (Test-Path -LiteralPath $absoluteLogDirectory)) { + New-Item -ItemType Directory -Path $absoluteLogDirectory | Out-Null +} + +if (Test-Path -LiteralPath $absoluteLogPath) { + Remove-Item -LiteralPath $absoluteLogPath -Force +} + +$arguments = @("-batchmode", "-nographics", "-logFile", $absoluteLogPath) +$process = Start-Process -FilePath $resolvedBuildPath.Path -ArgumentList $arguments -PassThru -WindowStyle Hidden + +try { + $deadline = (Get-Date).AddSeconds($TimeoutSeconds) + while ((Get-Date) -lt $deadline) { + Start-Sleep -Milliseconds 250 + if ($process.HasExited) { + throw "Magic Exam Hall player exited during smoke test with exit code $($process.ExitCode)." + } + } +} +finally { + if (-not $process.HasExited) { + Stop-Process -Id $process.Id -Force + $process.WaitForExit() + } +} + +if (-not (Test-Path -LiteralPath $absoluteLogPath)) { + throw "Magic Exam Hall player did not create a smoke log at $absoluteLogPath." +} + +$logText = Get-Content -LiteralPath $absoluteLogPath -Raw +$requiredPatterns = @( + "Initialize engine version", + "UnloadTime" +) + +foreach ($pattern in $requiredPatterns) { + if ($logText -notmatch [regex]::Escape($pattern)) { + throw "Magic Exam Hall player smoke log is missing expected startup marker: $pattern" + } +} + +$fatalPattern = "(?i)(NullReferenceException|MissingMethodException|DllNotFoundException|Fatal|Crash|Could not load scene|Failed to load)" +if ($logText -match $fatalPattern) { + throw "Magic Exam Hall player smoke log contains a fatal startup pattern: $($Matches[0])" +} + +Write-Output "Magic Exam Hall player smoke passed: process stayed alive for $TimeoutSeconds seconds and startup log markers were present." diff --git a/unity/MagicExamHall/README.md b/unity/MagicExamHall/README.md index b726b70..9dacd05 100644 --- a/unity/MagicExamHall/README.md +++ b/unity/MagicExamHall/README.md @@ -72,10 +72,13 @@ EditMode tests cover base recognition, overlay recognition, `martial_axis` depen ```powershell & 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -quit -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -executeMethod MagicExamHall.Editor.MagicExamHallSceneBuilder.BuildAll & 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -quit -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -executeMethod MagicExamHall.Editor.MagicExamHallBuildPipeline.BuildWindowsPlayer -magicExamHallBuildPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\Builds\MagicExamHall.exe' -logFile 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\unity-build.log' +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\smoke-magic-exam-hall-player.ps1 -BuildPath 'unity/MagicExamHall/Builds/MagicExamHall.exe' -LogPath 'unity/MagicExamHall/player-smoke.log' & 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -runTests -testPlatform editmode -testResults 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\TestResults.xml' & 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -runTests -testPlatform playmode -testResults 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\PlayModeTestResults.xml' ``` +The player smoke script starts the generated Windows build headlessly, waits for the scene startup log markers, and fails if the player exits early or logs a fatal startup pattern. Manual release QA should still confirm WASD movement and right-mouse world drawing in the visible player. + ## Troubleshooting If batchmode exits before compile/test output and the log contains `No valid Unity Editor license found`, activate the Unity Editor license first and rerun the command. From b1d7cec5bf5252ff0bd33e7b7418dcbf7adecc8b Mon Sep 17 00:00:00 2001 From: SilverSupplier <192233040+SilverSupplier@users.noreply.github.com> Date: Thu, 21 May 2026 01:00:21 +0900 Subject: [PATCH 3/3] docs: add release candidate checklist --- .gitignore | 3 +- README.md | 2 + docs/RELEASE_CHECKLIST.md | 78 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 docs/RELEASE_CHECKLIST.md diff --git a/.gitignore b/.gitignore index 7488701..83932a5 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,6 @@ unity/**/*.pidb unity/**/*.booproj unity/**/*.svd unity/**/TestResults.xml -unity/**/PlayModeTestResults.xml +unity/**/EditModeTestResults*.xml +unity/**/PlayModeTestResults*.xml unity/**/*.log diff --git a/README.md b/README.md index 6599b97..1304c01 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ unity/MagicExamHall 조작은 WASD/방향키 이동, 우클릭 hold/release 월드 드로잉입니다. 기본 플레이에서는 별도 입력 패널이나 `마법 시전` 버튼을 사용하지 않습니다. +제출 후보를 만들 때는 [Release Checklist](docs/RELEASE_CHECKLIST.md)를 따라 Web 검증, Unity 테스트, Windows 빌드, player smoke, 수동 5층 완주, 로그/개인정보 확인을 함께 점검합니다. + ## 초기 검증 튜토리얼 처음 받았을 때는 아래 순서로 확인하면 됩니다. diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md new file mode 100644 index 0000000..ffc281f --- /dev/null +++ b/docs/RELEASE_CHECKLIST.md @@ -0,0 +1,78 @@ +# Release Checklist + +Magic Recognizer V1.5 / Magic Exam Hall 제출 후보를 만들기 전에 이 문서를 위에서 아래로 실행한다. + +관련 이슈: #13, #16, #18, #52 + +## 1. Web 검증 + +- [ ] `npm ci` +- [ ] `npm run validate:docs` +- [ ] `npm test` +- [ ] `npm run build` +- [ ] 브라우저에서 Vite dev server를 열고 base family, overlay, final seal compile 흐름을 짧게 확인한다. +- [ ] survey API나 export를 켠 경우, 테스트 데이터와 실제 제출용 데이터가 섞이지 않았는지 확인한다. + +## 2. Unity 자동 검증 + +PowerShell에서 저장소 루트 기준으로 실행한다. + +```powershell +& 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -quit -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -executeMethod MagicExamHall.Editor.MagicExamHallSceneBuilder.BuildAll +& 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -runTests -testPlatform editmode -testResults 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\EditModeTestResults.xml' +& 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -runTests -testPlatform playmode -testResults 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\PlayModeTestResults.xml' +& 'C:\Program Files\Unity\Hub\Editor\6000.3.14f1\Editor\Unity.com' -batchmode -quit -projectPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall' -executeMethod MagicExamHall.Editor.MagicExamHallBuildPipeline.BuildWindowsPlayer -magicExamHallBuildPath 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\Builds\MagicExamHall.exe' -logFile 'C:\Users\silve\source\repos\magic\unity\MagicExamHall\unity-build.log' +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\smoke-magic-exam-hall-player.ps1 -BuildPath 'unity/MagicExamHall/Builds/MagicExamHall.exe' -LogPath 'unity/MagicExamHall/player-smoke.log' +``` + +- [ ] EditMode 결과가 `Passed`, failed `0`이다. +- [ ] PlayMode 결과가 `Passed`, failed `0`이다. +- [ ] Windows player build log에 `Build Finished, Result: Success.`가 있다. +- [ ] Windows player build log에 `0 errors`가 있다. +- [ ] player smoke log에 `Initialize engine version`과 `UnloadTime`이 있다. +- [ ] player smoke script가 early exit나 fatal startup pattern 없이 통과한다. + +## 3. Unity 수동 5층 완주 + +Unity Editor Play 또는 생성된 `unity/MagicExamHall/Builds/MagicExamHall.exe`로 확인한다. + +- [ ] WASD 또는 방향키로 이동할 수 있다. +- [ ] 우클릭 hold로 바닥에 stroke가 보이고, release 후 주문이 판정된다. +- [ ] 1층에서 base family 실험과 목표 완료가 가능하다. +- [ ] 2층에서 overlay operator 실험과 목표 완료가 가능하다. +- [ ] 3층에서 bridge/flow 계열 반응이 읽힌다. +- [ ] 4층에서 hazard reset과 stabilizer 반응이 읽힌다. +- [ ] 5층에서 final seal 목표를 완료할 수 있다. +- [ ] 엔딩 리포트가 표시된다. +- [ ] 플레이 중 콘솔이나 player log에 fatal exception이 없다. + +## 4. 로그와 개인정보 + +Unity 로그 경로: + +```text +Application.persistentDataPath/MagicExamHallLogs// +``` + +- [ ] `attempts.jsonl`과 `attempts.csv`가 생성된다. +- [ ] `survey.jsonl`과 `survey.csv`가 생성된다. +- [ ] 로그에 참가자 실명, 연락처, 학번 같은 직접 식별 정보가 들어가지 않는다. +- [ ] 연구용으로 공유할 로그는 session id를 익명화한다. +- [ ] 테스트 실행으로 생긴 임시 로그와 실제 플레이테스트 로그를 구분한다. +- [ ] 개인정보와 연구 데이터 처리 기준은 #16 범위 문서와 일치한다. + +## 5. 제출 패키지 + +- [ ] 루트 `README.md`의 빠른 시작과 Unity 실행 안내가 최신이다. +- [ ] `unity/MagicExamHall/README.md`의 Unity 버전, 조작법, 검증 명령이 최신이다. +- [ ] `docs/PROJECT_ROADMAP.md`가 현재 구현 상태와 크게 어긋나지 않는다. +- [ ] Windows build 산출물을 새로 생성했다. +- [ ] 제출물에 포함할 build, README, 발표 자료, playtest notes의 버전을 기록했다. +- [ ] known issue가 있으면 PR, issue, 또는 발표 자료에 명시했다. + +## Known Benign Messages + +- Unity batchmode 종료 중 `abort_threads: Failed aborting id ... mono_thread_manage will ignore it`가 보일 수 있다. +- Unity licensing에서 access token 갱신 경고가 보일 수 있다. + +이 메시지는 같은 로그에 compile error, failed test, build failure, fatal exception이 없을 때만 benign으로 취급한다.