diff --git a/SVSim.EmulatedEntrypoint/Services/StoryService.cs b/SVSim.EmulatedEntrypoint/Services/StoryService.cs index 4a0c16c..65e4ae6 100644 --- a/SVSim.EmulatedEntrypoint/Services/StoryService.cs +++ b/SVSim.EmulatedEntrypoint/Services/StoryService.cs @@ -177,21 +177,47 @@ public class StoryService : IStoryService bool worldComplete = sectionsInWorld.Count > 0; foreach (var s in sectionsInWorld) { - var charas = s.IsLeaderSelect ? charaIds : new[] { 0 }; - int released = 0, finished = 0, charasWithChapters = 0; - foreach (var c in charas) + int released = 0, finished = 0; + bool sectionFinished; + + if (s.IsLeaderSelect) { - if (!chaptersBySectionChara.TryGetValue((s.Id, c), out var chapters) || chapters.Count == 0) - continue; - charasWithChapters++; - int doneCount = chapters.Count(x => - allProgress.TryGetValue(x.StoryId, out var p) && (p.IsFinish || p.IsSkipped)); - if (doneCount > 0) released++; - if (doneCount == chapters.Count) finished++; + // released_chara_count = charas with playable chapters in the catalog + // (chapter 1 is always unlocked, so a chara is "released" the moment it has + // any chapter row). Drives the "X/Y complete" label, which the client only + // renders when released > 0. Counter is per-catalog, NOT per-viewer-progress. + // finished_chara_count = charas where the viewer has cleared every chapter. + int charasWithChapters = 0; + foreach (var c in charaIds) + { + if (!chaptersBySectionChara.TryGetValue((s.Id, c), out var chapters) || chapters.Count == 0) + continue; + charasWithChapters++; + int doneCount = chapters.Count(x => + allProgress.TryGetValue(x.StoryId, out var p) && (p.IsFinish || p.IsSkipped)); + if (doneCount == chapters.Count) finished++; + } + released = charasWithChapters; + sectionFinished = released > 0 && finished == released; } - // Compare against charas that actually have chapters, not the canonical 1-8 list — - // otherwise a section missing a class would never be `IsFinished`. - bool sectionFinished = charasWithChapters > 0 && finished == charasWithChapters; + else + { + // Non-leader-select sections (Limited / Event story) use chara_id=0 and don't + // expose chara counters — prod emits released=finished=0 regardless of progress. + // is_finished is derived from completion of the single chara=0 chapter set. + chaptersBySectionChara.TryGetValue((s.Id, 0), out var chapters); + if (chapters is { Count: > 0 }) + { + int doneCount = chapters.Count(x => + allProgress.TryGetValue(x.StoryId, out var p) && (p.IsFinish || p.IsSkipped)); + sectionFinished = doneCount == chapters.Count; + } + else + { + sectionFinished = false; + } + } + if (!sectionFinished) worldComplete = false; worldDto.SectionList.Add(new SectionEntry {