{"record":{"id":"39d6959e6bad9b26","repo":"babalae/better-genshin-impact","slug":"error-39d695","errorCode":null,"errorMessage":"连音缺少 .$ 结束标记","messagePattern":"连音缺少 \\.\\$ 结束标记","errorType":"exception","errorClass":"FormatException","httpStatus":null,"severity":"warning","filePath":"BetterGenshinImpact/GameTask/Music/Service/MusicScoreParser.cs","lineNumber":222,"sourceCode":"            }\n\n            if (TupletSpecialRegex().IsMatch(token.Special))\n            {\n                var group = new List<YuanQinToken>();\n                while (i < tokens.Count)\n                {\n                    group.Add(tokens[i]);\n                    if (tokens[i].Special.EndsWith('$'))\n                    {\n                        break;\n                    }\n\n                    i++;\n                }\n\n                if (!group[^1].Special.EndsWith('$'))\n                {\n                    throw new FormatException(\"连音缺少 .$ 结束标记\");\n                }\n\n                var totalDuration = GetNoteDuration(bpm, beatDenominator, token.Denominator);\n                var weights = group.Select(x =>\n                {\n                    var match = TupletSpecialRegex().Match(x.Special);\n                    return 1d / double.Parse(match.Groups[\"display\"].Value, CultureInfo.InvariantCulture);\n                }).ToList();\n                var weightSum = weights.Sum();\n\n                for (var groupIndex = 0; groupIndex < group.Count; groupIndex++)\n                {\n                    var duration = TimeSpan.FromTicks(\n                        (long)Math.Round(totalDuration.Ticks * weights[groupIndex] / weightSum));\n                    AddNote(events, cursor, duration, group[groupIndex].Keys);\n                    cursor += duration;\n                }\n","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/GameTask/Music/Service/MusicScoreParser.cs#L204-L240","documentation":"Thrown by MusicScoreParser.ParseYuanQinTimeline when a tuplet (连音) group is started but never terminated with a special token ending in '$'. The parser collects consecutive tokens matching the TupletSpecialRegex pattern; if the last collected token does not end with '$', the group is incomplete.","triggerScenarios":"A yuanqin sheet string or note array containing a tuplet start marker (e.g. '3.6') without a matching end marker (e.g. '3.$'). The while-loop exhausts available tokens or the group ends without a '$'-suffixed special.","commonSituations":"A score author forgot to close a tuplet group; the sheet was truncated or copy-pasted incorrectly; an OCR or conversion tool dropped the end marker.","solutions":["Inspect the yuanqin sheet for tuplet markers and ensure each group has a matching end token ending in '$' (e.g. '3.6 ... 3.$').","Validate the sheet syntax before playback — pair-check tuplet start/end markers.","Manually edit the score file to add the missing closing token."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-validate tuplet balance before parsing\nvar tupletStarts = Regex.Matches(sheet, @\"^\\d+\\.[36]\");\nvar tupletEnds = Regex.Matches(sheet, @\"\\.\");\n// Ensure each tuplet group has an end marker ending with '$'","typeGuard":null,"tryCatchPattern":"var score = await parser.ParseAsync(path, rootFolder, ct);\nif (!string.IsNullOrEmpty(score.Error)) { logger.LogWarning(score.Error); }","preventionTips":["Balance-check tuplet start/end markers in the sheet before parsing.","Follow yuanqin syntax: each tuplet group must end with a '.$' suffix.","Use example scores as reference for tuplet syntax."],"tags":["music","yuanqin","format-exception","score-syntax","user-content"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}