From 213b8e5967cc446a15bb70680cbbc2dd24e0afbe Mon Sep 17 00:00:00 2001 From: SilverSupplier <192233040+SilverSupplier@users.noreply.github.com> Date: Sat, 13 Jun 2026 01:14:19 +0900 Subject: [PATCH] test: EditMode coverage for hint escalator and mentor profiles HintAssistanceTests verifies the assist escalator (ReasonHint -> Checklist -> GhostTrace with cap), clean-success behavior, assisted flag retention, hintShown/level invariant, per-family checklists, and mentor profile mapping. EditMode total 113/113 locally. Inspection report corrections: the 8-second first-floor silence trigger does exist (ghost gesture at 8s, missed by the initial grep); dialogue pool/cooldown remain unimplemented features rather than missing tests. --- docs/PHASE5_6_CODE_INSPECTION.md | 4 +- .../Tests/EditMode/HintAssistanceTests.cs | 112 ++++++++++++++++++ .../EditMode/HintAssistanceTests.cs.meta | 2 + 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 unity/MagicExamHall/Assets/Tests/EditMode/HintAssistanceTests.cs create mode 100644 unity/MagicExamHall/Assets/Tests/EditMode/HintAssistanceTests.cs.meta diff --git a/docs/PHASE5_6_CODE_INSPECTION.md b/docs/PHASE5_6_CODE_INSPECTION.md index 3983289..588e0c1 100644 --- a/docs/PHASE5_6_CODE_INSPECTION.md +++ b/docs/PHASE5_6_CODE_INSPECTION.md @@ -53,11 +53,11 @@ | # | 항목 | 판정 | 증거 | | --- | --- | --- | --- | -| 1 | 트리거 6종 | **부분** | 5분 침묵(300s) ✓ (`firstFloorLongSilenceShown`, 5343행). 실패 escalator ✓ (PlayMode `FailedBaseCastsEscalateMagicNoteHints`). **1층 8초 침묵 트리거는 코드에서 발견 못 함** | +| 1 | 트리거 6종 | **부분** | 5분 침묵(300s) ✓ (`firstFloorLongSilenceShown`, 5343행). 실패 escalator ✓ (PlayMode `FailedBaseCastsEscalateMagicNoteHints`). 1층 8초 침묵 ✓ — **정정(2026-06-12): `firstFloorGhostShown` 8초 트리거가 구현돼 있음** (ExamGameController 5331행, ghost gesture + 노트). 초기 검수의 grep 누락이었음 | | 2 | 의도적 미등장 | 빌드 검증 필요 | 코드 구조상 모순 없음, 전수 확인은 플레이 필요 | | 3 | 대사 풀 트리거당 3~5변주 ≈ 25줄 | **부분** | `MentorPresentationController`에 층별 멘토 프로필 5종(벽화 연구원·균열 감시자 등) 구현. 변주 수 전수 카운트는 미실시 | | 4 | 등장 연출 (페이드인+머리 위 룬+`npc_appear`, 4층 가장자리 윈도우) | **부분** | `npc_appear` SFX ✓, 멘토 프레젠테이션 ✓. 4층 가장자리 윈도우는 4층 디자인 자체가 변경돼 스펙 재정의 필요 | -| 5 | EditMode 테스트 (쿨다운·1회성·풀 무작위) | **미구현** | EditMode 테스트 5클래스 중 멘토 트리거 테스트 없음 (CustomShape/Gesture/PlayerMovement/StageDefinition/StrokeInput뿐) | +| 5 | EditMode 테스트 (쿨다운·1회성·풀 무작위) | **부분 (2026-06-12 갱신)** | `HintAssistanceTests` 추가 — escalator 단계 상승·캡·assisted 플래그·체크리스트·멘토 프로필을 EditMode로 검증. 단 "쿨다운·풀 무작위"는 **대사 풀/쿨다운 시스템 자체가 미구현**이라 테스트 대상이 없음 (스펙 §13 잔여 기능) | ## P6-2. 결정적 모먼트 검수 diff --git a/unity/MagicExamHall/Assets/Tests/EditMode/HintAssistanceTests.cs b/unity/MagicExamHall/Assets/Tests/EditMode/HintAssistanceTests.cs new file mode 100644 index 0000000..7c530fe --- /dev/null +++ b/unity/MagicExamHall/Assets/Tests/EditMode/HintAssistanceTests.cs @@ -0,0 +1,112 @@ +using System; +using MagicExamHall; +using NUnit.Framework; + +namespace MagicExamHall.Tests +{ + public sealed class HintAssistanceTests + { + [Test] + public void FirstFailureStartsAtReasonHintAndEscalatesToGhostTraceCap() + { + Assert.That(HintAssistance.ResolveLevel(priorFailures: 0, success: false), Is.EqualTo(AssistLevel.ReasonHint)); + Assert.That(HintAssistance.ResolveLevel(priorFailures: 1, success: false), Is.EqualTo(AssistLevel.Checklist)); + Assert.That(HintAssistance.ResolveLevel(priorFailures: 2, success: false), Is.EqualTo(AssistLevel.GhostTrace)); + Assert.That(HintAssistance.ResolveLevel(priorFailures: 7, success: false), Is.EqualTo(AssistLevel.GhostTrace), "escalator must cap at GhostTrace"); + } + + [Test] + public void CleanSuccessNeverShowsAssistance() + { + Assert.That(HintAssistance.ResolveLevel(priorFailures: 0, success: true), Is.EqualTo(AssistLevel.None)); + + var state = HintAssistance.ForAttempt(SpellFamily.Fire, priorFailures: 0, success: true, result: null); + Assert.That(state.hintShown, Is.False); + Assert.That(state.assisted, Is.False); + Assert.That(state.currentLevel, Is.EqualTo(AssistLevel.None)); + } + + [Test] + public void SuccessAfterFailuresKeepsAssistedFlagAndReachedLevel() + { + var state = HintAssistance.ForAttempt(SpellFamily.Water, priorFailures: 2, success: true, result: null); + + Assert.That(state.assisted, Is.True, "a success that needed prior failures counts as assisted"); + Assert.That(state.currentLevel, Is.EqualTo(AssistLevel.Checklist)); + Assert.That(state.hintShown, Is.True); + } + + [Test] + public void HintShownAlwaysMatchesLevel() + { + foreach (SpellFamily family in Enum.GetValues(typeof(SpellFamily))) + { + for (var failures = 0; failures <= 4; failures++) + { + var state = HintAssistance.PreviewFor(family, failures); + Assert.That(state.hintShown, Is.EqualTo(state.currentLevel != AssistLevel.None), + $"hintShown must mirror level for {family} with {failures} failures"); + Assert.That(state.body, Is.Not.Empty, $"every hint state needs body text ({family}, {failures})"); + } + } + } + + [Test] + public void EveryFamilyHasAThreeItemChecklistWithActionVerbs() + { + foreach (SpellFamily family in Enum.GetValues(typeof(SpellFamily))) + { + var checklist = HintAssistance.ChecklistFor(family); + Assert.That(checklist.Count, Is.EqualTo(3), $"{family} checklist must have 3 steps"); + foreach (var item in checklist) + { + Assert.That(item.Trim(), Is.Not.Empty); + Assert.That(item.Trim().Length, Is.LessThanOrEqualTo(30), $"checklist lines stay short: '{item}'"); + } + } + } + + [Test] + public void ChecklistLevelBodyJoinsTheFamilyChecklist() + { + var state = HintAssistance.PreviewFor(SpellFamily.Earth, priorFailures: 2); + + Assert.That(state.currentLevel, Is.EqualTo(AssistLevel.Checklist)); + foreach (var item in HintAssistance.ChecklistFor(SpellFamily.Earth)) + { + Assert.That(state.body, Does.Contain(item)); + } + } + + [Test] + public void MentorProfilesAreDistinctPerFloorAndFallBackToFloorOne() + { + var names = new System.Collections.Generic.HashSet(); + for (var floor = 1; floor <= 5; floor++) + { + var profile = MentorProfile.ForFloor(floor); + Assert.That(profile.name, Is.Not.Empty); + Assert.That(names.Add(profile.name), Is.True, $"floor {floor} mentor name must be unique"); + } + + Assert.That(MentorProfile.ForFloor(99).name, Is.EqualTo(MentorProfile.ForFloor(1).name)); + Assert.That(MentorProfile.ForFloor(-1).name, Is.EqualTo(MentorProfile.ForFloor(1).name)); + } + + [Test] + public void MentorProfileMapsEveryMoodToASprite() + { + for (var floor = 1; floor <= 5; floor++) + { + var profile = MentorProfile.ForFloor(floor); + foreach (MentorMood mood in Enum.GetValues(typeof(MentorMood))) + { + Assert.That((int)profile.KindFor(mood), Is.GreaterThanOrEqualTo(0)); + } + + Assert.That(profile.KindFor(MentorMood.Happy), Is.Not.EqualTo(profile.KindFor(MentorMood.Frown)), + $"floor {floor} mentor needs distinct happy/frown sprites"); + } + } + } +} diff --git a/unity/MagicExamHall/Assets/Tests/EditMode/HintAssistanceTests.cs.meta b/unity/MagicExamHall/Assets/Tests/EditMode/HintAssistanceTests.cs.meta new file mode 100644 index 0000000..7883da7 --- /dev/null +++ b/unity/MagicExamHall/Assets/Tests/EditMode/HintAssistanceTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 160216e438c438849abbd03dda4b399e \ No newline at end of file