// Factual Java 26.2 block-state metadata, generated by scripts/measure_lighting.py. // The catalog's minecraft_id is distinct from Shacraft's material registry id. let loading; const decoded = new WeakMap(); export function loadLightProperties() { loading ??= fetch(new URL("./light-properties.json", import.meta.url)) .then((response) => { if (!response.ok) throw new Error(`Light properties HTTP ${response.status}`); return response.json(); }) .then((properties) => { if (!Array.isArray(properties.codes) || !Array.isArray(properties.shapes) || !Array.isArray(properties.schemas) || !properties.blocks) { throw new Error("Invalid block light properties"); } return properties; }) .catch((error) => { loading = undefined; throw error; }); return loading; } function stateId(state, properties) { if (typeof state !== "string") return undefined; const match = /^([a-z0-9_]+:[a-z0-9_/.]+)(?:\[([^\]]*)\])?$/.exec(state); if (!match) return undefined; const definition = properties.blocks?.[match[1]]; if (!definition) return undefined; const [first, defaultId, schemaId] = definition; if (match[2] === undefined || match[2] === "") return defaultId; const schema = properties.schemas[schemaId]; const requested = new Map(); for (const pair of match[2].split(",")) { const parts = pair.split("="); if (parts.length !== 2 || requested.has(parts[0])) return undefined; requested.set(parts[0], parts[1]); } const defaults = []; let remaining = defaultId - first; for (let index = schema.length - 1; index >= 0; index--) { const values = schema[index][1]; defaults[index] = remaining % values.length; remaining = Math.floor(remaining / values.length); } let offset = 0; for (let index = 0; index < schema.length; index++) { const [name, values] = schema[index]; const selected = requested.has(name) ? values.indexOf(requested.get(name)) : defaults[index]; if (selected < 0) return undefined; requested.delete(name); offset = offset * values.length + selected; } return requested.size ? undefined : first + offset; } /** Enrich a copy; explicit Minecraft IDs take priority over the state fallback. */ export function applyLightProperties(material, properties) { if (!material || !properties?.codes) return { ...material }; let id = material.minecraft_id; if (!Number.isInteger(id) || id < 0 || id >= properties.codes.length) id = stateId(material.state, properties); if (id === undefined) return { ...material }; const code = properties.codes[id]; let cache = decoded.get(properties); if (!cache) { cache = new Map(); decoded.set(properties, cache); } let lighting = cache.get(code); if (!lighting) { const boxes = properties.shapes[Math.floor(code / 16)]; if (!boxes) return { ...material }; lighting = { light_dampening: code % 16, light_occlusion: Object.freeze(boxes.map((box) => Object.freeze({ min: Object.freeze(box.slice(0, 3)), max: Object.freeze(box.slice(3, 6)), }))), }; cache.set(code, lighting); } return { ...material, ...lighting }; }