{"record":{"id":"8ace738f92a9889e","repo":"babalae/better-genshin-impact","slug":"expression-rect","errorCode":null,"errorMessage":"表达式 {expression} 未返回 Rect","messagePattern":"表达式 (.+?) 未返回 Rect","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"BetterGenshinImpact/Core/Recognition/RecognitionObjectJsonLoader.cs","lineNumber":327,"sourceCode":"            {\n                var alias = resolved[1..];\n                if (!_config.Templates.TryGetValue(alias, out resolved))\n                {\n                    throw new KeyNotFoundException($\"未找到模板别名 {alias}\");\n                }\n            }\n\n            return resolved;\n        }\n\n        private Rect EvaluateRect(string expression)\n        {\n            var resolved = ResolveRegionExpression(expression);\n            var value = EvaluateValue(resolved);\n            return value switch\n            {\n                Rect rect => rect,\n                _ => throw new InvalidOperationException($\"表达式 {expression} 未返回 Rect\")\n            };\n        }\n\n        private string ResolveRegionExpression(string expression)\n        {\n            var resolved = expression.Trim();\n            while (resolved.StartsWith('@'))\n            {\n                var alias = resolved[1..];\n                if (!_config.Regions.TryGetValue(alias, out resolved))\n                {\n                    throw new KeyNotFoundException($\"未找到区域别名 {alias}\");\n                }\n            }\n\n            return resolved;\n        }\n","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/Core/Recognition/RecognitionObjectJsonLoader.cs#L309-L345","documentation":"EvaluateRect runs the ROI expression through NCalc and expects a Rect result (produced by the rect()/cutXxx() helper functions). If the expression evaluates to a scalar number or any non-Rect value, it throws InvalidOperationException, because a region of interest must be a rectangle.","triggerScenarios":"A RecognitionObject has \"roi\": \"cw * 0.5\" (a bare number) instead of \"roi\": \"rect(0, 0, cw, ch)\" or a cutXxx(...) call.","commonSituations":"ROI confused with a numeric threshold; forgot to wrap dimensions in rect(); referenced a region alias that resolves to a number.","solutions":["Wrap the expression in rect(x, y, width, height), e.g. \"rect(0, 0, cw * 0.5, ch)\".","Use a cutXxx(...) helper or a region alias that resolves to a Rect-producing expression.","Confirm the expression returns a Rect by testing it before deployment."],"exampleFix":"// before (Recognition.json)\n// \"roi\": \"cw * 0.5\"  -> evaluates to a number -> throws\n\n// after\n// \"roi\": \"rect(0, 0, cw * 0.5, ch)\"","handlingStrategy":"validation","validationCode":"// Evaluate the ROI expression in a dry-run and assert the result type\nusing NCalc.Expression e = new(expr);\n// supply cw/ch/cx/cy/s parameters as in the loader\nvar result = e.Evaluate();\nif (result is not OpenCvSharp.Rect)\n    throw new InvalidOperationException($\"ROI expression '{expr}' must return a Rect (use rect(...)/cutXxx(...)).\");","typeGuard":"static bool IsRectExpression(string expression, IDictionary<string, object> parameters)\n{\n    try\n    {\n        var e = new NCalc.Expression(expression);\n        foreach (var (k, v) in parameters) e.Parameters[k] = v;\n        return e.Evaluate() is OpenCvSharp.Rect;\n    }\n    catch { return false; }\n}","tryCatchPattern":"try { return RecognitionObjectJsonLoader.Load(config, objectName, context); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"未返回 Rect\"))\n{ Logger.LogError(ex, \"ROI expression did not evaluate to a Rect.\"); throw; }","preventionTips":["Always wrap ROI dimensions in rect(...) or a cutXxx(...) helper.","Distinguish ROI (needs Rect) from numeric thresholds in authoring tooling.","Unit-test ROI expressions against sample capture sizes."],"tags":["config","ncalc","roi","validation"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}