babalae/better-genshin-impact · error · InvalidOperationException

{fieldName} OCR 在 {MaxOcrAttempts} 次内未达到连续 {RequiredStableOc

Error message

{fieldName} OCR 在 {MaxOcrAttempts} 次内未达到连续 {RequiredStableOcrCount} 次一致,最大连续次数={stableValues.MaxConsecutiveCount},末次结果={lastText}

What it means

`ReadLevelPairWithRetry` samples the level text up to `MaxOcrAttempts` (10) times, requiring `RequiredStableOcrCount` (3) consecutive identical parses. If it never achieves three-in-a-row it throws, reporting the best streak and the last raw text. This protects against returning a noisy/wrong level/limit pair.

Source

Thrown at BetterGenshinImpact/GameTask/CharacterDevelopment/CharacterDevelopmentTask.cs:942

                if (isStable)
                {
                    return (level, limit);
                }
            }
            else
            {
                stableValues.Reset();
                _logger.LogDebug("角色养成识别:{FieldName} OCR 第 {Attempt}/{MaxAttempts} 次解析失败,结果={Text}",
                    fieldName, attempt, MaxOcrAttempts, lastText);
            }

            if (attempt < MaxOcrAttempts)
            {
                await Delay(OcrRetryDelayMilliseconds, _ct);
            }
        }

        throw new InvalidOperationException(
            $"{fieldName} OCR 在 {MaxOcrAttempts} 次内未达到连续 {RequiredStableOcrCount} 次一致," +
            $"最大连续次数={stableValues.MaxConsecutiveCount},末次结果={lastText}");
    }

    /// <summary>
    /// 重复 OCR 武器名,并先通过标准武器表纠错;纠错后的名称连续三次一致才返回。
    /// </summary>
    private async Task<string> ReadWeaponNameWithRetry()
    {
        var stableNames = new StableValueAccumulator<string>(RequiredStableOcrCount);
        var lastOcrText = string.Empty;
        var lastMatchedName = string.Empty;
        for (var attempt = 1; attempt <= MaxOcrAttempts; attempt++)
        {
            using var capture = CaptureToRectArea();
            lastOcrText = OcrText(
                capture,
                Rect1080(1465, 132, 346, 42)).Trim();

View on GitHub (pinned to a7cb36712d)

Solutions

  1. Confirm the capture resolution and `_assetScale` produce an ROI that actually covers the level text.
  2. Raise `MaxOcrAttempts` / add a longer `OcrRetryDelayMilliseconds` if jitter is transient.
  3. Update the level ROI constants if the in-game layout changed.
  4. Preprocess the ROI (threshold/upscale) to stabilize PaddleOCR output.

Example fix

// before: ROI misaligned, never stable
var (lvl, lim) = await ReadLevelPairWithRetry(Rect1080(1467,207,172,35), "角色等级");

// after: correct ROI for current layout, or increase attempts
private const int MaxOcrAttempts = 20;
Defensive patterns

Strategy: retry

Validate before calling

// The loop already retries 10×; prevent by ensuring the ROI is correct:
// verify _assetScale and the level ROI cover 'Lv.X/Y' at the current resolution.

Try / catch

try { await ReadLevelPairWithRetry(roi, fieldName); }
catch (InvalidOperationException ex) when (ex.Message.Contains("OCR"))
{ /* log lastText, check ROI/scale, optionally raise MaxOcrAttempts */ }

Prevention

When it happens

Trigger: Persistent OCR jitter on the level ROI (e.g. 'Lv.90/90' flickering between parses); the ROI covering the wrong region so it never parses two numbers; animations/particles overlapping the level text.

Common situations: Wrong capture resolution breaking the 1080p-scaled ROI; a game UI change moving the level text; slow render making early frames unreadable; anti-aliasing/overlay interference.

Related errors


AI-assisted analysis of babalae/better-genshin-impact@a7cb36712d (2026-08-13). Data as JSON: /api/errors/6b4c49a839eb2b54. Report an issue: GitHub.