{"version":3,"sources":["craft-room.mjs"],"sourcesContent":["// CRAFT ROOM: how many runs of a recipe the backpack has room for (the owner, Oct 21). Timber made eight to one, and a\n// shift-click on a full bag ran every run the slots covered: craft() credits what fits and drops the rest\n// (functions/lib/inv-slots.mjs credit), and the page never read the reply's `credited`, so a full bag silently lost what\n// it made. Now nothing is lost: the page asks this first and runs no more than fit, and craft() asks the same question of\n// the stored inventory and runs no more than fit there (functions/index.js craft).\n//\n// A mirror of the server's own arithmetic over the 40 grid slots (inv-slots.mjs): a run's inputs are spent first, plain\n// stacks before stateful ones, from the highest slot down (spend), which may empty a slot; then each output tops up the\n// stacks of it already there, lowest first, to its stack limit, and opens free slots for the rest (credit). Pure: the\n// page passes its slots; tests/storage-oct21.test.mjs holds it to the server's spend and credit on the same bags.\n//\n//   slots    the grid, [{ n, c, q?, mix? } | null] (hotbar then backpack; the suit slots are never credited)\n//   inputs   { name: count } for ONE run;  outputs [{ name, count }] for ONE run\n//   limitOf  name → its stack limit (products.json stackLimit, else 100)\nexport function craftFits(slots, inputs, outputs, runs, limitOf) {\n  const s = (slots || []).map((x) => (x && x.n ? { n: x.n, c: x.c | 0, st: x.q != null || !!x.mix } : null));\n  // ONE NET CHANGE A NAME, as craft() builds its delta: an output that is also an input is spent or gained only by the difference.\n  const net = {};\n  for (const k in inputs || {}) net[k] = (net[k] || 0) - inputs[k] * runs;\n  for (const o of outputs || []) net[o.name] = (net[o.name] || 0) + o.count * runs;\n  for (const k in net) {\n    let need = -net[k]; if (need <= 0) continue;\n    for (const plainFirst of [true, false]) for (let i = s.length - 1; i >= 0 && need > 0; i--) {\n      const x = s[i]; if (!x || x.n !== k || (plainFirst && x.st)) continue;\n      const take = Math.min(x.c, need); x.c -= take; need -= take; if (x.c <= 0) s[i] = null;\n    }\n    if (need > 0) return false;                                  // not enough of an input: no room question to ask\n  }\n  for (const name in net) {\n    let left = net[name]; if (left <= 0) continue;\n    const cap = Math.max(1, limitOf ? limitOf(name) : 100);\n    for (let i = 0; i < s.length && left > 0; i++) { const x = s[i]; if (x && x.n === name && !x.st && x.c < cap) { const add = Math.min(left, cap - x.c); x.c += add; left -= add; } }\n    for (let i = 0; i < s.length && left > 0; i++) if (!s[i]) { const add = Math.min(left, cap); s[i] = { n: name, c: add, st: false }; left -= add; }\n    if (left > 0) return false;\n  }\n  return true;\n}\n// The most runs, up to `runs`, for which `fits(r)` holds (0 when not even one does): a search, as craft() makes it.\nexport function largestRuns(runs, fits) {\n  const n = Math.max(0, Math.floor(runs) || 0);\n  if (n === 0 || fits(n)) return n;\n  let lo = 0, hi = n - 1;\n  while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (fits(mid)) lo = mid; else hi = mid - 1; }\n  return lo;\n}\n// The most runs, up to `runs`, whose outputs all fit (0 when not even one does).\nexport const craftRunsThatFit = (slots, inputs, outputs, runs, limitOf) => largestRuns(runs, (r) => craftFits(slots, inputs, outputs, r, limitOf));\n// What the toast says when not even one run fits: \"Not enough room in your bag: make room for 8 Verge Timber first.\" (craft()\n// words its refusal the same, so a page from before Oct 21, which shows only a refusal saying \"not enough\", shows it too.)\nexport const BAG_FULL = (outputs) => `Not enough room in your bag: make room for ${(outputs || []).map((o) => `${o.count} ${o.name}`).join(' and ')} first.`;\n"],"mappings":"AAcO,gBAAS,UAAU,MAAO,OAAQ,QAAS,KAAM,QAAS,CAC/D,MAAM,GAAK,OAAS,CAAC,GAAG,IAAK,GAAO,GAAK,EAAE,EAAI,CAAE,EAAG,EAAE,EAAG,EAAG,EAAE,EAAI,EAAG,GAAI,EAAE,GAAK,MAAQ,CAAC,CAAC,EAAE,GAAI,EAAI,IAAK,EAEnG,IAAM,CAAC,EACb,UAAW,KAAK,QAAU,CAAC,EAAG,IAAI,CAAC,GAAK,IAAI,CAAC,GAAK,GAAK,OAAO,CAAC,EAAI,KACnE,UAAW,KAAK,SAAW,CAAC,EAAG,IAAI,EAAE,IAAI,GAAK,IAAI,EAAE,IAAI,GAAK,GAAK,EAAE,MAAQ,KAC5E,UAAW,KAAK,IAAK,CACnB,IAAI,KAAO,CAAC,IAAI,CAAC,EAAG,GAAI,QAAQ,GAChC,WAAW,aAAc,CAAC,GAAM,EAAK,EAAG,QAAS,EAAI,EAAE,OAAS,EAAG,GAAK,GAAK,KAAO,EAAG,IAAK,CAC1F,MAAM,EAAI,EAAE,CAAC,EAAG,GAAI,CAAC,GAAK,EAAE,IAAM,GAAM,YAAc,EAAE,GAAK,SAC7D,MAAM,KAAO,KAAK,IAAI,EAAE,EAAG,IAAI,EAAG,EAAE,GAAK,KAAM,MAAQ,KAAU,EAAE,GAAK,IAAG,EAAE,CAAC,EAAI,KACpF,CACA,GAAI,KAAO,EAAG,MAAO,GACvB,CACA,UAAW,QAAQ,IAAK,CACtB,IAAI,KAAO,IAAI,IAAI,EAAG,GAAI,MAAQ,EAAG,SACrC,MAAM,IAAM,KAAK,IAAI,EAAG,QAAU,QAAQ,IAAI,EAAI,GAAG,EACrD,QAAS,EAAI,EAAG,EAAI,EAAE,QAAU,KAAO,EAAG,IAAK,CAAE,MAAM,EAAI,EAAE,CAAC,EAAG,GAAI,GAAK,EAAE,IAAM,MAAQ,CAAC,EAAE,IAAM,EAAE,EAAI,IAAK,CAAE,MAAM,IAAM,KAAK,IAAI,KAAM,IAAM,EAAE,CAAC,EAAG,EAAE,GAAK,IAAK,MAAQ,GAAK,CAAE,CAClL,QAAS,EAAI,EAAG,EAAI,EAAE,QAAU,KAAO,EAAG,IAAK,GAAI,CAAC,EAAE,CAAC,EAAG,CAAE,MAAM,IAAM,KAAK,IAAI,KAAM,GAAG,EAAG,EAAE,CAAC,EAAI,CAAE,EAAG,KAAM,EAAG,IAAK,GAAI,EAAM,EAAG,MAAQ,GAAK,CACjJ,GAAI,KAAO,EAAG,MAAO,EACvB,CACA,MAAO,EACT,CAEO,gBAAS,YAAY,KAAM,KAAM,CACtC,MAAM,EAAI,KAAK,IAAI,EAAG,KAAK,MAAM,IAAI,GAAK,CAAC,EAC3C,GAAI,IAAM,GAAK,KAAK,CAAC,EAAG,OAAO,EAC/B,IAAI,GAAK,EAAG,GAAK,EAAI,EACrB,KAAO,GAAK,IAAI,CAAE,MAAM,IAAO,GAAK,GAAK,GAAM,EAAO,KAAK,GAAG,EAAG,GAAK,IAAU,GAAK,IAAM,CAAG,CAC9F,OAAO,EACT,CAEO,aAAM,iBAAmB,CAAC,MAAO,OAAQ,QAAS,KAAM,UAAY,YAAY,KAAO,GAAM,UAAU,MAAO,OAAQ,QAAS,EAAG,OAAO,CAAC,EAGpI,SAAY,SAAY,+CAA+C,SAAW,CAAC,GAAG,IAAK,GAAM,GAAG,EAAE,KAAK,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,OAAO,CAAC","names":[],"file":"craft-room.mjs"}