{
  "schemaVersion": 1,
  "slug": "running-pace-calculator",
  "category": "sports",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "696e734f2135de84b767b01f5fbf10c93c0955dbd2e98f4919a856fca860cc18",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/running-pace-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "p = t / D; t = D × p; D = t / p; 1 mi = 1.609344 km",
    "vars": [
      [
        "D",
        "Distance (km)"
      ],
      [
        "t",
        "Total time (min)"
      ],
      [
        "p",
        "Pace per kilometre (min/km)"
      ]
    ],
    "note": "Choose pace, finish time or distance. Enter the other two values. Splits assume a constant pace and include the final partial segment. Share a plan as text, an image or a link."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-28",
    "sources": [
      {
        "name": "Riegel (1981) — Athletic records and time-distance extrapolation",
        "url": "https://pubmed.ncbi.nlm.nih.gov/7235349/"
      },
      {
        "name": "NIST — Guide for the Use of the International System of Units (SI)",
        "url": "https://www.nist.gov/pml/special-publication-811"
      },
      {
        "name": "World Athletics — Competition and Technical Rules (road race distances)",
        "url": "https://worldathletics.org/about-iaaf/documents/book-of-rules"
      }
    ],
    "referenceRole": "context",
    "implementationHash": "696e734f2135de84b767b01f5fbf10c93c0955dbd2e98f4919a856fca860cc18",
    "basis": "p = t / D; t = D × p; D = t / p. Statute mile conversion uses 1 mi = 1.609344 km (exact by international agreement; NIST SI usage). Split tables assume constant pace and keep a final partial segment when distance is not an integer number of split units. P1 consolidation retains the core model and adds input modes or derived outputs. The migration report records the input/output union and known-answer checks.",
    "limitations": "Constant-pace planning only — not a fatigue, hill, or wind model. Displayed pace rounds to whole seconds, so km↔mi conversion can nudge projected finish by a second. Riegel-style race prediction lives on the marathon pace predictor, not this page. Added display totals and migration adapters are implementation behavior, not independently validated claims."
  },
  "contract": {
    "compute": "const mergedResult=(()=>{if((true&&![\"pace\", \"time\", \"distance\"].includes(String(mode)))||([\"pace\", \"time\"].includes(String(mode))&&![\"km\", \"mi\", \"m\"].includes(String(unit)))||([\"time\", \"distance\"].includes(String(mode))&&![\"km\", \"mi\"].includes(String(paceUnit)))||(true&&![\"km\", \"mi\"].includes(String(splitUnit))))return{__error:'inputError'};\nif(([\"pace\",\"time\"].includes(String(mode))&&(!Number.isFinite(distance)||distance<0||distance>500000))||([\"pace\",\"distance\"].includes(String(mode))&&(!Number.isFinite(hours)||hours<0||hours>1000))||([\"pace\",\"distance\"].includes(String(mode))&&(!Number.isFinite(minutes)||minutes<0||minutes>60000))||([\"pace\",\"distance\"].includes(String(mode))&&(!Number.isFinite(seconds)||seconds<0||seconds>60000))||([\"time\",\"distance\"].includes(String(mode))&&(!Number.isFinite(paceMinutes)||paceMinutes<0||paceMinutes>194))||([\"time\",\"distance\"].includes(String(mode))&&(!Number.isFinite(paceSeconds)||paceSeconds<0||paceSeconds>59)))return{__error:'inputError'};\nconst fmtTime = s => { const n=Math.round(s); return Math.floor(n/3600)+':'+String(Math.floor(n/60)%60).padStart(2,'0')+':'+String(n%60).padStart(2,'0'); };\nconst fmtPace = s => { const n=Math.round(s); return Math.floor(n/60)+':'+String(n%60).padStart(2,'0'); };\nlet km=unit==='km'?distance:unit==='mi'?distance*1.609344:distance/1000;\nlet t=hours*3600+minutes*60+seconds;\nlet p=(paceMinutes*60+paceSeconds)/(paceUnit==='mi'?1.609344:1);\nif(mode==='pace') p=t/km; else if(mode==='time') t=km*p; else if(mode==='distance') km=t/p;\nif(!['pace','time','distance'].includes(mode)||!['km','mi','m'].includes(unit)||!['km','mi'].includes(paceUnit)||!['km','mi'].includes(splitUnit)||!Number.isFinite(km)||!Number.isFinite(t)||!Number.isFinite(p)||km<=0||km>500||t<=0||p<=0||p>7200||(mode!=='time'&&[hours,minutes,seconds].some(v=>!Number.isInteger(v)))||(mode!=='pace'&&[paceMinutes,paceSeconds].some(v=>!Number.isInteger(v)))) return {__error:'inputError',pacePerKm:null,pacePerMile:null,finishTime:null,distanceKm:null,distanceMi:null,totalMinutes:null,speedKph:null,speedMph:null};\nconst step=splitUnit==='mi'?1.609344:1;\nconst splits=[];\nfor(let n=1;n*step<km-1e-9;n++) splits.push({distance:n,time:fmtTime(n*step*p)});\nsplits.push({distance:km/step,time:fmtTime(t)});\nreturn {pacePerKm:fmtPace(p),pacePerMile:fmtPace(p*1.609344),finishTime:fmtTime(t),distanceKm:km,distanceMi:km/1.609344,totalMinutes:t/60,speedKph:3600/p,speedMph:3600/p/1.609344,__splits:splits};\n})();return mergedResult.__error?{\"pacePerKm\":null,\"pacePerMile\":null,\"finishTime\":null,\"distanceKm\":null,\"distanceMi\":null,\"totalMinutes\":null,\"speedKph\":null,\"speedMph\":null,...mergedResult}:mergedResult;",
    "inputs": [
      {
        "name": "mode",
        "type": "select",
        "default": "pace",
        "options": [
          {
            "value": "pace"
          },
          {
            "value": "time"
          },
          {
            "value": "distance"
          }
        ]
      },
      {
        "name": "distance",
        "type": "number",
        "default": 10,
        "min": 0,
        "max": 500000,
        "step": 0.001,
        "showWhen": {
          "mode": [
            "pace",
            "time"
          ]
        },
        "unitField": "unit",
        "unitScales": {
          "km": 1,
          "mi": 1.609344,
          "m": 0.001
        },
        "maxByUnit": {
          "km": 500,
          "mi": 310.685596118667,
          "m": 500000
        },
        "unitPrecision": 6,
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "unit",
        "type": "select",
        "default": "km",
        "options": [
          {
            "value": "km"
          },
          {
            "value": "mi"
          },
          {
            "value": "m"
          }
        ],
        "showWhen": {
          "mode": [
            "pace",
            "time"
          ]
        }
      },
      {
        "name": "hours",
        "type": "number",
        "default": 0,
        "min": 0,
        "max": 1000,
        "step": 1,
        "showWhen": {
          "mode": [
            "pace",
            "distance"
          ]
        },
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "minutes",
        "type": "number",
        "default": 50,
        "min": 0,
        "max": 60000,
        "step": 1,
        "showWhen": {
          "mode": [
            "pace",
            "distance"
          ]
        },
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "seconds",
        "type": "number",
        "default": 0,
        "min": 0,
        "max": 60000,
        "step": 1,
        "showWhen": {
          "mode": [
            "pace",
            "distance"
          ]
        },
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "paceMinutes",
        "type": "number",
        "default": 5,
        "min": 0,
        "max": 194,
        "step": 1,
        "showWhen": {
          "mode": [
            "time",
            "distance"
          ]
        },
        "requiredValue": true,
        "unitField": "paceUnit",
        "maxByUnit": {
          "km": 120,
          "mi": 194
        },
        "strictRange": true
      },
      {
        "name": "paceSeconds",
        "type": "number",
        "default": 0,
        "min": 0,
        "max": 59,
        "step": 1,
        "showWhen": {
          "mode": [
            "time",
            "distance"
          ]
        },
        "requiredValue": true,
        "strictRange": true
      },
      {
        "name": "paceUnit",
        "type": "select",
        "default": "km",
        "options": [
          {
            "value": "km"
          },
          {
            "value": "mi"
          }
        ],
        "showWhen": {
          "mode": [
            "time",
            "distance"
          ]
        }
      },
      {
        "name": "splitUnit",
        "type": "select",
        "default": "km",
        "options": [
          {
            "value": "km"
          },
          {
            "value": "mi"
          }
        ],
        "advanced": true
      }
    ],
    "outputs": [
      {
        "name": "pacePerKm",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "pacePerMile",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "finishTime",
        "type": "text",
        "precision": 0
      },
      {
        "name": "distanceKm",
        "precision": 3,
        "unit": "km"
      },
      {
        "name": "distanceMi",
        "precision": 3,
        "unit": "mi"
      },
      {
        "name": "totalMinutes",
        "precision": 2,
        "unit": "min"
      },
      {
        "name": "speedKph",
        "precision": 2,
        "unit": "km/h"
      },
      {
        "name": "speedMph",
        "precision": 2,
        "unit": "mph"
      }
    ],
    "interpret": null
  }
}
