{"version":3,"sources":["item-ids.mjs"],"sourcesContent":["// ITEM IDS — the codec between what is SAVED or SENT and what the code works with. Byte-identical twins:\n// public/item-ids.mjs, functions/lib/item-ids.mjs, zone-server/lib/item-ids.mjs (generate-version-hash.js checks).\n//\n// Every catalogue entry (kubes.json, products.json) carries a permanent numeric `id` (tools/item-ids.py). The owner,\n// Oct 1: refer to items by an id, not a string, so a rename never breaks what players own or built. So:\n//   - everything STORED (inventory slots, the inventory mirror, placed blocks, fluid blends, stashes, the edit log, a\n//     world's recorded sea) and everything SENT between the game and its servers holds the id;\n//   - code in memory still holds the item's current NAME, which it gets from here on the way in and gives back on\n//     the way out. A rename therefore changes the catalogue and nothing that was saved.\n//\n// READING IS TOLERANT: a stored value may be a number (the id), a numeric string (an id used as a map key — Firestore\n// keys are strings), or a legacy NAME written before the switch. All three decode to the current name. Writing is\n// strict: a known name always encodes to its id. A value that is neither (junk from before validation existed) is\n// carried through unchanged in both directions, as readInv always did, so nothing is lost by passing through here.\n//\n// Pure apart from the registry: call registerItems(kubes, products) once at load, as setStackLimits is called.\n\nconst _id = new Map();     // current name → id\nconst _name = new Map();   // id → current name\nconst _variant = new Map();   // variant's id → { base: id, material: id }\nconst _hex = new Map();       // id → catalogue colour, for drawing a variant in its material's colour\nconst _alias = new Map();     // a FORMER name → id (a catalogue entry's `aka`), so a record written under it still decodes\nconst _idAlias = new Map();   // a RETIRED id → the id of the item it now decodes as (a catalogue entry's `akaIds`)\n\n// MATERIAL VARIANTS (the owner, Oct 1: \"items with varying recipes adapt their textures based on what materials they're\n// crafted with\"). A Toolbench made of Verge Wood is its own item, \"Toolbench (Verge Wood)\", so it stacks, saves and\n// places like anything else; its catalogue entry says what it is a variant OF (`variantOf`, the base item's id) and\n// what it is MADE of (`material`, that material's id). Every rule about benches and machines asks baseItemOf(name),\n// and every drawing of one asks materialOf(name). tools/material-variants.py writes the entries.\nexport function registerItems(...lists) {\n  for (const list of lists) for (const e of list || []) {\n    if (!e || typeof e.name !== 'string' || !Number.isInteger(e.id)) continue;\n    _id.set(e.name, e.id);\n    _name.set(e.id, e.name);\n    if (typeof e.hex === 'string') _hex.set(e.id, e.hex);\n    if (Number.isInteger(e.variantOf)) _variant.set(e.id, { base: e.variantOf, material: Number.isInteger(e.material) ? e.material : null });\n    // A RENAME KEEPS ITS OLD NAMES (the owner, Oct 7: the Bucket became the Thermos). Every stored record holds the id,\n    // so the rename alone moves every bucket a player owns; `aka` covers the one place a NAME can still turn up — a\n    // record written before ids existed, or a legacy client sending one — by decoding it to the item it now is.\n    for (const a of Array.isArray(e.aka) ? e.aka : []) if (typeof a === 'string' && a) _alias.set(a, e.id);\n    // …AND A REMOVED ITEM'S ID MAY LIVE ON AS ANOTHER'S (the owner, Oct 21: the Packing Crate removed, \"existing ones become\n    // Cabinets of the same wood\"). Its id is retired (tools/item-ids.py RETIRED, never issued again) and listed in the\n    // Cabinet's `akaIds`, so a crate held, placed, stored or logged under the old id decodes as that Cabinet, and is saved\n    // again under the Cabinet's own id the next time it is written. No record has to be rewritten first.\n    for (const a of Array.isArray(e.akaIds) ? e.akaIds : []) if (Number.isInteger(a) && a > 0) _idAlias.set(a, e.id);\n  }\n}\n// What an item is, setting aside what it is made of: \"Toolbench (Iron)\" → \"Toolbench\". Anything else → itself.\nexport function baseItemOf(name) {\n  const v = _variant.get(_id.get(name));\n  return v ? (itemNameOf(v.base) ?? name) : name;\n}\n// What a variant is made of (\"Toolbench (Iron)\" → \"Iron\"), or null for an item that is not a variant.\nexport function materialOf(name) {\n  const v = _variant.get(_id.get(name));\n  return v && v.material != null ? itemNameOf(v.material) : null;\n}\n// An item's catalogue colour, or null.\nexport const itemHexOf = (name) => (_hex.has(_id.get(name)) ? _hex.get(_id.get(name)) : null);\n// Every variant of a base item, by name.\nexport function variantsOf(baseName) {\n  const b = _id.get(baseName), out = [];\n  for (const [id, v] of _variant) if (v.base === b) out.push(itemNameOf(id));\n  return out;\n}\n// RETIRED ITEMS: removed from the catalogue, their ids never issued again (tools/item-ids.py RETIRED holds the same numbers).\n// Their names are kept here, and only here, for a migration to recognise what is still saved under them. decodeItem knows\n// nothing of them (an id it has no entry for decodes to null), so the game itself never meets one.\n//   785, 788, 789  Oct 13: the Air Recycler and the Airlock Door (with its upper half) left with vessels' insides\n//                  (tools/vessels-exterior-migrate.mjs refunds what was placed or held).\nexport const RETIRED_ITEMS = Object.freeze({ 785: 'Air Recycler', 788: 'Airlock Door', 789: 'Airlock Door Upper' });\nexport const itemsRegistered = () => _name.size > 0;\nexport const itemIdOf = (name) => (_id.has(name) ? _id.get(name) : null);\nexport const itemNameOf = (id) => (_name.has(id) ? _name.get(id) : _idAlias.has(id) ? _name.get(_idAlias.get(id)) ?? null : null);\n\nconst NUMERIC = /^[1-9][0-9]{0,8}$/;\n\n// A former name (`aka`) → the item's current name, or null when it is not one.\nexport const aliasOf = (name) => (_alias.has(name) && !_id.has(name) ? itemNameOf(_alias.get(name)) : null);\n// A stored or received item → the current name. Unknown ids decode to null (an item that no longer exists); a former\n// name decodes to the item's current one; other unknown strings are returned as they are.\nexport function decodeItem(v) {\n  if (typeof v === 'number') return Number.isInteger(v) ? itemNameOf(v) : null;\n  if (typeof v !== 'string') return null;\n  if (NUMERIC.test(v)) { const n = itemNameOf(Number(v)); if (n != null) return n; }\n  return aliasOf(v) ?? v;\n}\n// A name → what to store or send: its id (a former name's too), or the value unchanged when it is not a catalogue name.\nexport function encodeItem(name) {\n  if (typeof name !== 'string') return name;\n  const id = itemIdOf(name);\n  return id == null ? (_alias.has(name) ? _alias.get(name) : name) : id;\n}\n\n// { key: item | null } — a chunk's placed blocks. null (mined out) stays null.\nexport function encodeValues(obj) {\n  const out = {};\n  for (const k in obj || {}) out[k] = obj[k] == null ? obj[k] : encodeItem(obj[k]);\n  return out;\n}\nexport function decodeValues(obj) {\n  const out = {};\n  for (const k in obj || {}) out[k] = obj[k] == null ? obj[k] : decodeItem(obj[k]);\n  return out;\n}\n\n// { item: number } — the inventory mirror, a fluid blend, the edit log's mined/placed. Keys are ids as strings.\nexport function encodeKeys(obj) {\n  if (!obj || typeof obj !== 'object') return obj;\n  const out = {};\n  for (const k in obj) { const e = encodeItem(k); out[typeof e === 'number' ? String(e) : k] = obj[k]; }\n  return out;\n}\nexport function decodeKeys(obj) {\n  if (!obj || typeof obj !== 'object') return obj;\n  const out = {};\n  // A document written across the switch can hold one item under both keys (a legacy name and its id, merged into\n  // the same map). Counts are added, so neither half is lost.\n  for (const k in obj) {\n    const d = decodeItem(k); if (d == null) continue;\n    out[d] = (typeof out[d] === 'number' && typeof obj[k] === 'number') ? out[d] + obj[k] : obj[k];\n  }\n  return out;\n}\n\n// One stack { n, c, q?, mix?, ...rest } — an inventory slot, an overflow entry, a stash's contents.\nexport function encodeStack(s) {\n  if (!s || typeof s !== 'object') return s;\n  const out = { ...s, n: encodeItem(s.n) };\n  if (s.mix) out.mix = encodeKeys(s.mix);\n  return out;\n}\nexport function decodeStack(s) {\n  if (!s || typeof s !== 'object') return s;\n  const n = decodeItem(s.n);\n  if (n == null) return null;\n  const out = { ...s, n };\n  if (s.mix) out.mix = decodeKeys(s.mix);\n  return out;\n}\nexport const encodeStacks = (list) => (Array.isArray(list) ? list.map(encodeStack) : list);\nexport const decodeStacks = (list) => (Array.isArray(list) ? list.map(decodeStack) : list);\n\n// A CALLABLE'S REPLY. The item fields the game's functions return — `credited` {item: n}, `outputs` [{name, count}],\n// `input` {kube, count}, `destroyed` {name, count}, `kube`, a thermos's `fluid.bucket.mix`, and a gas canister's\n// `canister.mix` and the world's `gas.delta` (gas names as keys) — travel as ids like\n// everything else. The functions encode their reply with this; the client's one call wrapper (app.js callFn) decodes\n// every reply with its twin, so no caller sees an id. Fields a reply does not have are left alone.\nfunction mapReply(r, item, keys) {\n  if (!r || typeof r !== 'object' || Array.isArray(r)) return r;\n  const out = { ...r };\n  if (r.credited && typeof r.credited === 'object') out.credited = keys(r.credited);\n  if (Array.isArray(r.outputs)) out.outputs = r.outputs.map((o) => (o && o.name != null ? { ...o, name: item(o.name) } : o));\n  if (r.input && r.input.kube != null) out.input = { ...r.input, kube: item(r.input.kube) };\n  if (r.destroyed && r.destroyed.name != null) out.destroyed = { ...r.destroyed, name: item(r.destroyed.name) };\n  if (r.kube != null && typeof r.kube !== 'object') out.kube = item(r.kube);\n  if (r.fluid && r.fluid.bucket && r.fluid.bucket.mix) out.fluid = { ...r.fluid, bucket: { ...r.fluid.bucket, mix: keys(r.fluid.bucket.mix) } };\n  if (r.canister && r.canister.mix) out.canister = { ...r.canister, mix: keys(r.canister.mix) };\n  if (r.gas && r.gas.delta && typeof r.gas.delta === 'object') out.gas = { ...r.gas, delta: keys(r.gas.delta) };\n  return out;\n}\nexport const encodeReply = (r) => mapReply(r, encodeItem, encodeKeys);\nexport const decodeReply = (r) => mapReply(r, decodeItem, decodeKeys);\n"],"mappings":"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","names":[],"file":"item-ids.mjs"}