{"version":3,"sources":["deconstruct.mjs"],"sourcesContent":["// Kubicor — deconstruction (refining) math. SHARED, byte-for-byte, between the authoritative craft Cloud Function\n// (this file, functions/lib/deconstruct.mjs — the server truth) and the client's refine preview\n// (public/deconstruct.mjs, a byte-identical copy). generate-version-hash.js fails the build on any drift, exactly like\n// the world-gen copies: if the two disagree, the outputs a player previews differ from what the server actually credits.\n//\n// Model: refining N kubes of a COMPOSITE material — one whose kubes.json entry carries a `composition` map of\n// element → percent (e.g. Ignition Mineral → {Iron:70, Oxygen:30}) — yields N element kubes TOTAL, split by those\n// percentages. Total COUNT is conserved exactly (Σ outputs === N), so a refine can neither create nor destroy kubes;\n// that is what closes the refine→(future)recombine mint exploit. The integer split uses largest-remainder\n// apportionment (a.k.a. Hamilton's method):\n//   1. raw_i   = N * pct_i / 100                      (fractional ideal share)\n//   2. base_i  = floor(raw_i)                         (everyone gets their whole part)\n//   3. hand the leftover (N − Σ base_i) units, one apiece, to the largest fractional remainders\n// Ties break by descending percentage then element name, so the result is fully deterministic and identical on both\n// sides. The leftover is also corrected downward if the data's percentages sum slightly over 100, so Σ outputs === N\n// holds for ANY input — the invariant never depends on the catalogue's percentages being perfectly clean.\n\nexport function computeDeconstruct(composition, count) {\n  const n = Number(count);\n  if (!composition || typeof composition !== 'object' || !Number.isInteger(n) || n <= 0) return null;\n  const els = Object.keys(composition);\n  if (!els.length) return null;\n\n  // Data sanity: every percentage must be positive and the set must sum to ~100. A malformed entry is refused rather\n  // than silently minting or eating kubes; the small tolerance absorbs authored rounding (e.g. 33/33/34).\n  let sum = 0;\n  for (const el of els) { const p = composition[el]; if (!(p > 0)) return null; sum += p; }\n  if (Math.abs(sum - 100) > 0.5) return null;\n\n  const rows = els.map((el) => {\n    const raw = n * composition[el] / 100;\n    const base = Math.floor(raw);\n    return { el, base, rem: raw - base, pct: composition[el] };\n  });\n  // Deterministic order: largest fractional remainder first, then largest percentage, then element name.\n  rows.sort((a, b) => (b.rem - a.rem) || (b.pct - a.pct) || (a.el < b.el ? -1 : a.el > b.el ? 1 : 0));\n\n  let leftover = n - rows.reduce((s, r) => s + r.base, 0);\n  if (leftover > 0) {                                             // under-assigned → top up the best remainders\n    for (let i = 0; i < rows.length && leftover > 0; i++) { rows[i].base++; leftover--; }\n  } else if (leftover < 0) {                                      // over-assigned (pct summed >100) → shave the worst\n    for (let i = rows.length - 1; i >= 0 && leftover < 0; i--) { if (rows[i].base > 0) { rows[i].base--; leftover++; } }\n  }\n\n  const out = {};\n  for (const r of rows) if (r.base > 0) out[r.el] = r.base;      // omit zero-yield elements (small batches favour the dominant one)\n  return out;\n}\n"],"mappings":"AAiBO,gBAAS,mBAAmB,YAAa,MAAO,CACrD,MAAM,EAAI,OAAO,KAAK,EACtB,GAAI,CAAC,aAAe,OAAO,aAAgB,UAAY,CAAC,OAAO,UAAU,CAAC,GAAK,GAAK,EAAG,OAAO,KAC9F,MAAM,IAAM,OAAO,KAAK,WAAW,EACnC,GAAI,CAAC,IAAI,OAAQ,OAAO,KAIxB,IAAI,IAAM,EACV,UAAW,MAAM,IAAK,CAAE,MAAM,EAAI,YAAY,EAAE,EAAG,GAAI,EAAE,EAAI,GAAI,OAAO,KAAM,KAAO,CAAG,CACxF,GAAI,KAAK,IAAI,IAAM,GAAG,EAAI,GAAK,OAAO,KAEtC,MAAM,KAAO,IAAI,IAAK,IAAO,CAC3B,MAAM,IAAM,EAAI,YAAY,EAAE,EAAI,IAC5B,KAAO,KAAK,MAAM,GAAG,EAC3B,MAAO,CAAE,GAAI,KAAM,IAAK,IAAM,KAAM,IAAK,YAAY,EAAE,CAAE,CAC3D,CAAC,EAED,KAAK,KAAK,CAAC,EAAG,IAAO,EAAE,IAAM,EAAE,KAAS,EAAE,IAAM,EAAE,MAAS,EAAE,GAAK,EAAE,GAAK,GAAK,EAAE,GAAK,EAAE,GAAK,EAAI,EAAE,EAElG,IAAI,SAAW,EAAI,KAAK,OAAO,CAAC,EAAG,IAAM,EAAI,EAAE,KAAM,CAAC,EACtD,GAAI,SAAW,EACb,QAAS,EAAI,EAAG,EAAI,KAAK,QAAU,SAAW,EAAG,IAAO,KAAK,CAAC,EAAE,OAAQ,mBAC/D,SAAW,EACpB,QAAS,EAAI,KAAK,OAAS,EAAG,GAAK,GAAK,SAAW,EAAG,IAAW,KAAK,CAAC,EAAE,KAAO,IAAK,KAAK,CAAC,EAAE,OAAQ,YAGvG,MAAM,IAAM,CAAC,EACb,UAAW,KAAK,KAAU,EAAE,KAAO,IAAG,IAAI,EAAE,EAAE,EAAI,EAAE,MACpD,OAAO,GACT","names":[],"file":"deconstruct.mjs"}