MVP checks / mvp (push) Waiting to run
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
79 lines
3.1 KiB
JavaScript
79 lines
3.1 KiB
JavaScript
// 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 };
|
|
}
|