Files
shacraft-core/client/light-properties.js
Emil c7e86663d8
MVP checks / mvp (push) Waiting to run
Expand voxel gameplay, lighting, full-height streaming and world imports
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.
2026-09-17 02:10:53 +03:00

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 };
}