{"record":{"id":"673f9d59979597dc","repo":"affaan-m/ECC","slug":"native-snapshot-fps-is-missing-invalid-or-contradictory","errorCode":null,"errorMessage":"native snapshot fps is missing, invalid or contradictory","messagePattern":"native snapshot fps is missing, invalid or contradictory","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/integration.py","lineNumber":210,"sourceCode":"    _artifact(baseline[\"project_file\"])\n    snapshot = _artifact(baseline[\"snapshot_file\"], parse_json=True)\n    if not isinstance(snapshot, dict):\n        raise ValueError(\"native snapshot must be an object\")\n    settings = snapshot.get(\"settings\")\n    native_fps = settings.get(\"timelineFrameRate\") if isinstance(settings, dict) else None\n    # Resolve's conventional decimal NTSC labels represent these exact rates.\n    ntsc = {\"23.976\": \"24000/1001\", \"29.97\": \"30000/1001\", \"59.94\": \"60000/1001\"}\n    try:\n        if type(native_fps) not in (str, int, float):\n            raise ValueError(\"native fps missing\")\n        label = str(native_fps)\n        if not re.fullmatch(r\"[0-9]{1,9}(?:\\.[0-9]{1,12}|/[1-9][0-9]{0,8})?\", label):\n            raise ValueError(\"native fps must use a bounded decimal or rational label\")\n        native_rate = Fraction(ntsc.get(label, label))\n        if native_rate != Fraction(*_rate(baseline[\"fps\"])):\n            raise ValueError(\"native fps differs\")\n    except (ValueError, ZeroDivisionError) as exc:\n        raise ValueError(\"native snapshot fps is missing, invalid or contradictory\") from exc\n    for cfg, key in [(\"project_name\", \"project\"), (\"timeline_name\", \"timeline\")]:\n        if _text(baseline[cfg]) != snapshot.get(key):\n            raise ValueError(\"native snapshot identity mismatch\")\n    tracks = snapshot.get(\"timeline_readback\")\n    if not isinstance(tracks, dict) or not tracks:\n        raise ValueError(\"native clip snapshot required\")\n    for track, clips in tracks.items():\n        if not re.fullmatch(r\"(?:video|audio)[1-9][0-9]*\", track) or not isinstance(clips, list):\n            raise ValueError(\"invalid native snapshot track\")\n        for clip in clips:\n            _check_clip(clip, bounds)\n    return tracks\n\n\ndef _check_clip(clip: Any, bounds: list[int]) -> None:\n    required = {\"path\", \"name\", \"start\", \"end\", \"left_offset\", \"right_offset\", \"enabled\", \"properties\"}\n    if not isinstance(clip, dict) or not required <= clip.keys():\n        raise ValueError(\"native snapshot clip is incomplete\")","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/integration.py#L192-L228","documentation":"All fps problems found while reconciling the native snapshot (missing, badly formatted, or contradicting baseline) are consolidated into this single ValueError raised by _snapshot. It is the user-facing error for the whole try block that validates settings.timelineFrameRate against baseline fps.","triggerScenarios":"build_application_bundle -> _snapshot when timelineFrameRate is absent, not str/int/float, fails the label regex, or its resolved Fraction differs from baseline.fps (also ZeroDivisionError from a zero denominator).","commonSituations":"Snapshot exported from a mismatched project; hand-edited fps values; NTSC decimal vs exact rational confusion; a snapshot missing the settings block entirely.","solutions":["Ensure settings.timelineFrameRate exists and is a clean label (\"23.976\", \"24\", \"24000/1001\").","Make the exact resolved rate equal baseline.fps (remember 23.976 means 24000/1001).","Re-export the snapshot from the intended Resolve timeline.","Validate fps parity with a quick script before running build_application_bundle."],"exampleFix":"// before\n\"settings\": {}  (no fps) while baseline fps = 24000/1001\n\n// after\n\"settings\": {\"timelineFrameRate\": \"23.976\"}  # resolves to 24000/1001","handlingStrategy":"validation","validationCode":"from fractions import Fraction\ndef fps_consistent(baseline_fps, snapshot):\n    settings = snapshot.get(\"settings\")\n    if not isinstance(settings, dict):\n        return False\n    v = settings.get(\"timelineFrameRate\")\n    if type(v) not in (str, int, float):\n        return False\n    ntsc = {\"23.976\": \"24000/1001\", \"29.97\": \"30000/1001\", \"59.94\": \"60000/1001\"}\n    label = str(v)\n    import re\n    if not re.fullmatch(r\"[0-9]{1,9}(?:\\.[0-9]{1,12}|/[1-9][0-9]{0,8})?\", label):\n        return False\n    return Fraction(ntsc.get(label, label)) == Fraction(baseline_fps[\"numerator\"], baseline_fps[\"denominator\"])","typeGuard":null,"tryCatchPattern":"try:\n    bundle = build_application_bundle(config, None, local_only=True)\nexcept ValueError as exc:\n    if \"fps is missing, invalid or contradictory\" in str(exc):\n        print(\"reconcile settings.timelineFrameRate with baseline.fps\")\n    raise","preventionTips":["Run a preflight fps check before invoking the library.","Keep one canonical fps representation per project.","Re-export snapshots after any project frame-rate change.","Test the full _snapshot path with real exports, not synthetic JSON."],"tags":["fps","validation","snapshot"],"backgroundTag":"invalid-config-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}