{"record":{"id":"c8921079bece37c3","repo":"3b1b/manim","slug":"the-stroke-shader-no-longer-declares-declaration","errorCode":null,"errorMessage":"The stroke shader no longer declares {declaration!r}, so a fill's border would be compiled as an ordinary stroke and vanish","messagePattern":"The stroke shader no longer declares (.+?), so a fill's border would be compiled as an ordinary stroke and vanish","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"manimlib/renderer/drawing.py","lineNumber":370,"sourceCode":"    @classmethod\n    def draws(cls, mobject: Mobject) -> bool:\n        \"\"\"Always, the two shaders being named here rather than by the mobject\"\"\"\n        return True\n\n    @classmethod\n    def key(cls, mobject: Mobject) -> tuple:\n        return (*super().key(mobject), mobject.shader_code_target)\n\n    @classmethod\n    def module_specs(cls, mobject: Mobject) -> list[ModuleSpec]:\n        \"\"\"\n        Three modules from two sources: the border around a fill is the stroke shader with one\n        constant compiled the other way.\n        \"\"\"\n        source = read_shader_file(cls.stroke_file)\n        declaration = cls.border_declaration\n        if declaration not in source:\n            raise ValueError(\n                f\"The stroke shader no longer declares {declaration!r}, so a fill's border \"\n                f\"would be compiled as an ordinary stroke and vanish\"\n            )\n        border = {declaration: declaration.replace(\"false\", \"true\")}\n\n        # A snippet is meant for one of the two sources where it names one of their fields,\n        # since a snippet reading stroke_rgba would not compile against the fill\n        target = mobject.shader_code_target\n        replacements = mobject.shader_code_replacements\n        nothing: dict[str, str] = dict()\n        for_fill = replacements if target in (None, \"fill\") else nothing\n        for_stroke = replacements if target in (None, \"stroke\") else nothing\n        return [\n            (\"fill\", cls.fill_file, for_fill),\n            (\"stroke\", cls.stroke_file, for_stroke),\n            (\"border\", cls.stroke_file, {**for_stroke, **border}),\n        ]\n","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/3b1b/manim/blob/dee01804d47b9f94402d71472674710dcac125b8/manimlib/renderer/drawing.py#L352-L388","documentation":"This ValueError is an internal invariant check in the fill-border rendering pipeline: a fill's outline is drawn by recompiling the stroke shader with IS_FILL_BORDER flipped from false to true (drawing.py:350 declares border_declaration = 'const IS_FILL_BORDER: bool = false;'). module_specs verifies that declaration still exists in the stroke shader source before doing the string replacement; if a fork or edit removed/renamed it, borders would silently render as ordinary strokes and vanish, so the code fails loudly instead.","triggerScenarios":"Editing manimlib/shaders/quadratic_bezier_stroke/ (or whichever stroke_file the class uses) and deleting or altering the line 'const IS_FILL_BORDER: bool = false;'; renaming the constant; changing 'false' to another literal. It fires the first time any fill mobject with a stroke is rendered.","commonSituations":"Custom shader work in a fork of manim; upstream shader refactors in a modified local install; version mismatches where drawing.py and the shader directory come from different commits.","solutions":["Restore or keep the exact line 'const IS_FILL_BORDER: bool = false;' in the stroke shader file","If the constant was renamed intentionally, update border_declaration in drawing.py to match the new declaration string","Ensure drawing.py and the shaders directory are from the same manim version (reinstall or git checkout the matching commit)"],"exampleFix":"// before (stroke shader)\n// (declaration deleted during refactor)\n// after\nconst IS_FILL_BORDER: bool = false;","handlingStrategy":"validation","validationCode":"from manimlib.renderer.drawing import StrokedFillPath  # or the class owning stroke_file\nsrc = open(cls.stroke_file).read()\nassert cls.border_declaration in src, \"stroke shader lost the IS_FILL_BORDER declaration\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["In shader forks, grep for IS_FILL_BORDER after every edit: it must stay exactly 'const IS_FILL_BORDER: bool = false;'","Keep drawing.py and shaders/ in the same git commit; never mix versions"],"tags":["opengl","shader","rendering","internal-invariant","fork"],"backgroundTag":null,"analyzedSha":"dee01804d47b9f94402d71472674710dcac125b8","analyzedAt":"2026-08-14T19:53:44.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}