{
  "schemaVersion": 1,
  "site": "https://dfttk.com",
  "purpose": "Delta Force weapon TTK and BTK calculation in the browser",
  "calculatorUrl": "https://dfttk.com/ai/calculator",
  "manifestUrl": "https://dfttk.com/data/v3/manifest.json",
  "modes": {
    "sol": "烽火地带，支持弹药、护甲、头盔、耐久、配件与精校",
    "mp": "全面战场，无护甲和弹药选择"
  },
  "weaponData": {
    "allowedManifestField": "weaponPacks[weaponId].combat",
    "requiredKind": "weaponCombatRuntimePack",
    "forbiddenManifestFields": [
      "weaponPacks[weaponId].build",
      "weaponPacks[weaponId].sharedBuild"
    ],
    "note": "战斗分析只对外返回 combat 战斗包；selection 包仅内部校验。getOptions 返回经校验的选项摘要，不暴露 selection、build 或 sharedBuild 包。"
  },
  "customWeaponData": {
    "mode": "sol",
    "sourceType": "userCustom",
    "storage": "当前浏览器 localStorage: dfttk.customWeapons.v3",
    "note": "自定义枪械使用已保存的最终武器与绑定弹药数值，不属于 v3 manifest，不读取 selection、combat、build 或 sharedBuild 单枪包。"
  },
  "browserApi": {
    "global": "window.DFTTK_AI",
    "readyOn": "https://dfttk.com/ai/calculator",
    "methods": {
      "searchWeapons": "searchWeapons({ mode, query, limit })",
      "listCustomWeapons": "listCustomWeapons({ query, limit }) -> userCustom metadata[]",
      "getCombatData": "getCombatData({ mode, weaponId }) -> weaponCombatRuntimePack",
      "getCustomData": "getCustomData({ customPairId }) -> userCustomCombatData",
      "calculate": "calculate(input) -> Promise<calculation>",
      "compare": "compare({ configs, distancesMeters }, { signal?, onProgress? }?) -> Promise<comparison>",
      "getOptions": "getOptions({ mode, sourceType, weaponId, customPairId, ammoItemId, loadoutConfig, tunes, fireMode }) -> Promise<options>"
    },
    "version": 1,
    "requires": "支持 JavaScript 和 Web Worker 的浏览器；打开 calculatorUrl，等待 window.DFTTK_AI 出现。不是 HTTP RPC 或 MCP 服务。",
    "limits": {
      "minCompareConfigs": 2,
      "maxCompareConfigs": 4,
      "maxCompareDistances": 201,
      "searchLimit": {
        "default": 20,
        "min": 1,
        "max": 100
      },
      "customSearchLimit": {
        "default": 100,
        "min": 1,
        "max": 100
      }
    },
    "input": {
      "mode": {
        "type": "string",
        "enum": [
          "sol",
          "mp"
        ],
        "default": "sol"
      },
      "sourceType": {
        "type": "string",
        "enum": [
          "v3Official",
          "userCustom"
        ],
        "default": "v3Official"
      },
      "weaponId": {
        "type": "string",
        "requiredWhen": "正式枪；从 searchWeapons 返回值取得"
      },
      "customPairId": {
        "type": "string",
        "requiredWhen": "自定义枪；从 listCustomWeapons 返回值取得；存在时优先使用自定义枪，仅 sol"
      },
      "ammoItemId": {
        "type": "string|null",
        "default": "getOptions.defaultAmmoItemId",
        "note": "sol 从 getOptions.ammo[].id 选择；自定义枪固定绑定弹药；mp 忽略"
      },
      "distanceMeters": {
        "type": "number",
        "minimum": 0,
        "default": 0,
        "unit": "m"
      },
      "initialHealth": {
        "type": "number",
        "minimum": 1,
        "default": 100
      },
      "armorId": {
        "type": "string|null",
        "default": null,
        "note": "sol 从 getOptions.armors[].id 选择；mp 忽略"
      },
      "helmetId": {
        "type": "string|null",
        "default": null,
        "note": "sol 从 getOptions.helmets[].id 选择；mp 忽略"
      },
      "armorDurability": {
        "type": "number",
        "minimum": 0,
        "default": "所选护甲 maxDurability；无护甲为 0"
      },
      "helmetDurability": {
        "type": "number",
        "minimum": 0,
        "default": "所选头盔 maxDurability；无头盔为 0"
      },
      "loadoutConfig": {
        "type": "array",
        "default": [],
        "items": {
          "slotPath": "string",
          "itemId": "string"
        },
        "note": "从 getOptions.slots 选择。修改后再次查询以发现子槽位、冲突和被移除的选择；自定义枪不叠加配件"
      },
      "tunes": {
        "type": "object",
        "default": {},
        "shape": "{ [slotPath]: { [tuneId]: number } }",
        "note": "从 getOptions.tuneControls 查询当前安装配件的范围；超范围值会截断；自定义枪不叠加精校"
      },
      "fireMode": {
        "type": "string|null",
        "default": null,
        "note": "从 getOptions.fireControl.supportedFireModes 选择；实际模式见 provenance 与 getOptions.fireControl.selectedFireMode；支持选择不等于射击节奏已验证"
      },
      "hitProbabilities": {
        "type": "object",
        "default": "getOptions.defaultHitProbabilities（按模式）",
        "properties": {
          "head": "非负有限数",
          "upperChest": "非负有限数",
          "lowerChest": "非负有限数",
          "upperArm": "非负有限数",
          "lowerArm": "非负有限数",
          "thigh": "非负有限数",
          "lowerLeg": "非负有限数",
          "miss": "非负有限数"
        },
        "note": "总和小于 1 时差额补为 miss；大于 1 时归一化；全部为 0 时回退上胸必中。省略的部位为 0。"
      },
      "includeTriggerDelay": {
        "type": "boolean",
        "default": true
      },
      "includeProjectileFlightTime": {
        "type": "boolean",
        "default": false
      },
      "maxShots": {
        "type": "integer",
        "minimum": 1,
        "default": 120
      },
      "maxStates": {
        "type": "integer",
        "minimum": 100,
        "default": 20000
      },
      "pruneProbability": {
        "type": "number",
        "minimum": 0,
        "default": 1e-12,
        "note": "现有核心将 0 也视为默认 1e-12；不表示禁用剪枝"
      },
      "statePrecision": {
        "type": "number",
        "minimum": 0,
        "default": 0,
        "note": "0 表示不分桶；正数表示状态分桶精度。改变此值可能改变结果。"
      }
    },
    "inputRules": "传有限数值和 JSON 可序列化输入。calculate({}) 不自动选择武器；calculate() 不传参数沿用当前页面条件。默认值说明适用于显式传入 input 的调用。计算会规范化输入，请检查返回的 input 和 status。",
    "optionsResult": {
      "ammo": "[{ id, name, penetration, specialRuleId }]；有特殊规则的弹药不一定可计算",
      "armors": "[{ id, name, level, maxDurability }]；mp 为空",
      "helmets": "[{ id, name, level, maxDurability }]；mp 为空",
      "selection": "规范化后的配装与精校；rejected/displaced 列出无效或被挤占的配件",
      "slots": "[{ slotPath, name, equipped, defaultItemId, options: [{itemId,name}] }]；随当前配装变化",
      "unavailableParts": "[{ itemId, reason }]；网站暂不支持的配件，不能选择或用于装配联动",
      "tuneControls": "[{ slotPath, itemId, tuneId, range: {minValue,maxValue,defaultValue,step}, sliderType, value }]；对应当前安装配件",
      "fireControl": "武器当前射击控制信息，包括 supportedFireModes、selectedFireMode 和 scheduleStatus；null 表示不可用",
      "dataVersion": "查询时数据版本；与后续计算 provenance.dataVersion 核对",
      "defaultHitProbabilities": "当前模式默认命中权重；自定义枪返回固定弹药和空 slots/tuneControls"
    },
    "execution": {
      "compare": "主线程读取并校验资源、固定配置快照，随后 Worker 计算；保持原配置顺序，距离去重后升序。无效距离被过滤，过滤后为空或超过限制时拒绝。",
      "progress": "onProgress({completed,total})；按完成的配置×距离计数，最多约每 100ms 通知一次，非剩余时间预测；准备资源时尚无计数。回调抛错会终止该调用。",
      "cancel": "第二参数传 AbortController.signal，controller.abort() 只取消对应调用；Promise 拒绝，error.name 为 AbortError。取消不返回半份比较表。",
      "concurrency": "每个调用独立返回，页面仅显示最新有效请求。离开计算页会取消未完成批量请求；修改页面条件不会改写已提交的快照。",
      "errors": "输入/资源错误、Worker 不可用或失败均拒绝 Promise，捕获 error.name/message。Worker 不可用时不回退到阻塞主线程。单行状态不可计算时仍返回该行。",
      "calculate": "单场景仍在主线程执行；不支持第二参数取消。",
      "ui": "超过 500ms 的批量任务才显示进度和取消按钮；完整结果无新增说明。"
    },
    "examples": {
      "calculate": "const api = window.DFTTK_AI; const [weapon] = await api.searchWeapons({mode:'sol',query:'M4A1'}); const input = {mode:'sol',weaponId:weapon.weaponId}; const options = await api.getOptions(input); const result = await api.calculate({...input,ammoItemId:options.defaultAmmoItemId,distanceMeters:30,hitProbabilities:{upperChest:1}}); if (result.status.code !== 'ok') { console.log(result.status, result.result.diagnostics); }",
      "compare": "const controller = new AbortController(); const task = window.DFTTK_AI.compare({configs:[{...input,label:'无甲'},{...input,label:'有甲',armorId:options.armors[0].id}],distancesMeters:[0,25,50,100]}, {signal:controller.signal,onProgress:p=>console.log(p)}); // 如需取消：controller.abort();\nconst comparison = await task;"
    }
  },
  "result": {
    "primaryFields": [
      "result.expectedTtkMs",
      "result.expectedBtk",
      "result.killProbability",
      "status"
    ],
    "provenanceFields": [
      "provenance.sourceType",
      "provenance.customPairId",
      "provenance.dataVersion",
      "provenance.runtimeVersion",
      "provenance.calculatorContractVersion",
      "provenance.combatPackKind",
      "provenance.combatPackPath",
      "provenance.buildPackUsed"
    ],
    "semantics": {
      "ok": "ok===true 当且仅当 status.code===ok；完整指剩余概率+丢弃概率≤1e-9，不代表游戏全部规则已验证。",
      "partial": "ok===false 且 status.code===partial。expectedTtkMs/expectedBtk 是已完成击杀概率条件下的平均值，不是全体场景无条件均值。不得混入完整结果给出确定排名。",
      "unavailable": "其他 status.code 表示不能计算；读取 status.message 解释。null 不是 0，真实 0ms 可表示一枪击杀且不计额外延迟。",
      "diagnostics": "result.diagnostics 包含 complete、shotsEvaluated、maxAliveStates、remainingProbability、discardedProbability；分布概率之和为 killProbability，不自动归一化为 1。",
      "reproducibility": "保留完整 input、weapon、ammo、provenance 和批量 configSnapshot；input 回显实际计算限制。相同配置在新数据版本下可能产生不同结果；自定义枪另存 getCustomData 返回值以保留浏览器本地数值。"
    },
    "comparison": "{ configs: number, distancesMeters: number[], rows: [{ configIndex, configId, label, configSnapshot, distanceMeters, calculation }] }；calculation 与 calculate 返回值同形。",
    "calculationShape": {
      "ok": "boolean",
      "status": {
        "code": "string: ok | partial | 其他不可计算状态",
        "message": "string"
      },
      "mode": "sol | mp",
      "weapon": {
        "weaponId": "string",
        "customPairId": "string|null",
        "sourceType": "v3Official | userCustom",
        "name": "string",
        "categoryId": "string"
      },
      "ammo": "弹药对象或 null",
      "input": "规范化的选择与场景，含实际计算限制",
      "result": {
        "expectedTtkMs": "number|null，毫秒",
        "expectedBtk": "number|null，发数",
        "killProbability": "number，已完成击杀概率",
        "btkDistribution": [
          {
            "shots": "number",
            "probability": "number"
          }
        ],
        "ttkDistribution": [
          {
            "ttkMs": "number",
            "probability": "number"
          }
        ],
        "diagnostics": {
          "complete": "boolean",
          "shotsEvaluated": "number",
          "maxAliveStates": "number",
          "remainingProbability": "number",
          "discardedProbability": "number"
        },
        "status": "与外层 status 相同"
      },
      "provenance": "见 provenanceFields；buildPackUsed 恒为 false"
    }
  }
}
