diff --git a/.gitignore b/.gitignore index c83dc61d..70371877 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ /node_modules /reference /test-server -/tools/lightgen/out +/tools/stategen/out diff --git a/README.md b/README.md index edd2c7f1..976529ac 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Run `just` with no arguments to list every recipe. The common ones: - `just client-dev` / `just client-build` / `just client-release`: run, build, or benchmark the client; flags forward after `--`, e.g. `just client-dev -- --username Steve` - `just launcher-dev` / `just launcher-build`: run or bundle the launcher - `just client-pre-pr` / `just launcher-pre-pr`: the fmt, clippy, and test checks CI enforces -- `just protogen` / `just registrygen` / `just blockgen` / `just lightgen`: regenerate a version's packet-id, registry, block-state, and light tables from `reference//` +- `just protogen` / `just registrygen` / `just blockgen` / `just stategen`: regenerate a version's packet-id, registry, block-state, and per-state property tables from `reference//` ## Contributing diff --git a/justfile b/justfile index 1a710a29..a8ac8db9 100644 --- a/justfile +++ b/justfile @@ -43,14 +43,14 @@ registrygen version="26.2": blockgen version="26.2": @cargo run -p blockgen -- blocks reference/{{ version }}/generated/reports/blocks.json {{ version }} pomme-client/src/world/block/data/blocks-{{ version }}.json -# JDK 25 bin dir for lightgen; override with `just jdk= lightgen`. +# JDK 25 bin dir for stategen; override with `just jdk= stategen`. jdk := "C:/Program Files/Amazon Corretto/jdk25.0.2_10/bin" -# Regenerate a version's light-property table by running vanilla's own code -# (tools/lightgen/LightDump.java) against the reference server jar, then -# compacting the dump with `blockgen light`. Uses the deobf server jar when +# Regenerate a version's per-state property table by running vanilla's own code +# (tools/stategen/StateDump.java) against the reference server jar, then +# compacting the dump with `blockgen state`. Uses the deobf server jar when # one exists (pre-26.x); needs the Corretto JDK for 26.x class files. -lightgen version="26.2": +stategen version="26.2": #!/usr/bin/env bash set -euo pipefail v="{{ version }}" @@ -64,7 +64,7 @@ lightgen version="26.2": | xargs unzip -qn "$ref/server.jar" -d "$ref/bundler" fi libs=$(find "$ref/bundler" -name '*.jar' | tr '\n' ';') - mkdir -p tools/lightgen/out - "$jdk/javac.exe" --release 21 -d tools/lightgen/out tools/lightgen/LightDump.java - "$jdk/java.exe" -cp "$classes;${libs}tools/lightgen/out" LightDump "$v" "$ref/generated/light.json" - cargo run -p blockgen -- light "$ref/generated/light.json" pomme-client/src/world/block/data/blocks-"$v".json pomme-client/src/world/block/data/light-"$v".json + mkdir -p tools/stategen/out + "$jdk/javac.exe" --release 21 -d tools/stategen/out tools/stategen/StateDump.java + "$jdk/java.exe" -cp "$classes;${libs}tools/stategen/out" StateDump "$v" "$ref/generated/state.json" + cargo run -p blockgen -- state "$ref/generated/state.json" pomme-client/src/world/block/data/blocks-"$v".json pomme-client/src/world/block/data/state-"$v".json diff --git a/pomme-client/src/world/block/data/light-1.21.10.json b/pomme-client/src/world/block/data/state-1.21.10.json similarity index 100% rename from pomme-client/src/world/block/data/light-1.21.10.json rename to pomme-client/src/world/block/data/state-1.21.10.json diff --git a/pomme-client/src/world/block/data/light-1.21.11.json b/pomme-client/src/world/block/data/state-1.21.11.json similarity index 100% rename from pomme-client/src/world/block/data/light-1.21.11.json rename to pomme-client/src/world/block/data/state-1.21.11.json diff --git a/pomme-client/src/world/block/data/light-26.1.json b/pomme-client/src/world/block/data/state-26.1.json similarity index 100% rename from pomme-client/src/world/block/data/light-26.1.json rename to pomme-client/src/world/block/data/state-26.1.json diff --git a/pomme-client/src/world/block/data/light-26.2.json b/pomme-client/src/world/block/data/state-26.2.json similarity index 100% rename from pomme-client/src/world/block/data/light-26.2.json rename to pomme-client/src/world/block/data/state-26.2.json diff --git a/pomme-client/src/world/block/mod.rs b/pomme-client/src/world/block/mod.rs index 9ccb4390..b8ff9913 100644 --- a/pomme-client/src/world/block/mod.rs +++ b/pomme-client/src/world/block/mod.rs @@ -45,12 +45,12 @@ const DEFAULT_BEHAVIOR: BlockBehavior = BlockBehavior { }; /// A face-occlusion shape projected onto the face plane as a 16x16 bit grid; -/// `mask[v]` holds row `v`'s bits. Generated by `just lightgen`. +/// `mask[v]` holds row `v`'s bits. Generated by `just stategen`. pub type FaceMask = [u16; 16]; /// Per-state light properties (vanilla `BlockBehaviour.BlockStateBase`'s /// baked light cache), loaded from the per-version table generated by -/// `tools/lightgen` running vanilla's own code. +/// `tools/stategen` running vanilla's own code. #[derive(Clone, Copy)] pub struct LightProps { pub emission: u8, @@ -158,19 +158,19 @@ struct BehaviorEntry { } #[derive(serde::Deserialize)] -struct LightFile { +struct StateFile { version: String, state_count: u32, /// Deduped face masks as 64-hex-char strings (16 rows of 4 chars). masks: Vec, - blocks: Vec, + blocks: Vec, } /// Per-block light fields; each is a scalar when uniform across the block's /// states, else one value per state. `f` is 6 mask-dictionary indices — /// a single tuple when uniform, else a per-state list of tuple-or-null. #[derive(serde::Deserialize)] -struct LightEntry { +struct StateEntry { name: String, e: ScalarOrPerState, d: ScalarOrPerState, @@ -209,12 +209,15 @@ const BLOCK_DATA: [(i32, &str); 4] = [ (773, include_str!("data/blocks-1.21.10.json")), ]; -/// Light-property tables (`just lightgen`), index-aligned with [`BLOCK_DATA`]. -const LIGHT_DATA: [&str; BLOCK_DATA.len()] = [ - include_str!("data/light-26.2.json"), - include_str!("data/light-26.1.json"), - include_str!("data/light-1.21.11.json"), - include_str!("data/light-1.21.10.json"), +/// Per-state property tables (`just stategen`), index-aligned with +/// [`BLOCK_DATA`]. `blocks-.json` is the state layout (ids and properties +/// from the data-generator report); `state-.json` holds the per-state +/// values vanilla bakes at runtime, dumped by running its own code. +const STATE_DATA: [&str; BLOCK_DATA.len()] = [ + include_str!("data/state-26.2.json"), + include_str!("data/state-26.1.json"), + include_str!("data/state-1.21.11.json"), + include_str!("data/state-1.21.10.json"), ]; /// One lazily-built table per embedded data file; `ACTIVE_TABLE` indexes the @@ -499,32 +502,31 @@ pub fn prewarm_protocol(protocol: i32) -> usize { .iter() .position(|&(p, _)| p == protocol) .unwrap_or(0); - BLOCK_TABLES[slot].get_or_init(|| build_table(BLOCK_DATA[slot].1, LIGHT_DATA[slot])); + BLOCK_TABLES[slot].get_or_init(|| build_table(BLOCK_DATA[slot].1, STATE_DATA[slot])); slot } -fn build_table(data: &str, light_data: &str) -> Vec { +fn build_table(data: &str, state_data: &str) -> Vec { let file: BlockFile = serde_json::from_str(data).expect("invalid block-state data"); let behaviors: HashMap = serde_json::from_str(include_str!("data/block_behavior.json")) .expect("invalid block-behavior data"); - let light_file: LightFile = - serde_json::from_str(light_data).expect("invalid light-property data"); + let state_file: StateFile = serde_json::from_str(state_data).expect("invalid state data"); assert_eq!( - light_file.version, file.version, - "light-property data is for a different version" + state_file.version, file.version, + "state data is for a different version" ); assert_eq!( - light_file.state_count, file.state_count, - "light-property data state count mismatch" + state_file.state_count, file.state_count, + "state data state count mismatch" ); assert_eq!( - light_file.blocks.len(), + state_file.blocks.len(), file.blocks.len(), - "light-property data block count mismatch" + "state data block count mismatch" ); let masks: &'static [FaceMask] = Vec::leak( - light_file + state_file .masks .iter() .map(|hex| decode_face_mask(hex)) @@ -552,7 +554,7 @@ fn build_table(data: &str, light_data: &str) -> Vec { }; let mut table: Vec = Vec::with_capacity(file.state_count as usize); - for (block, light_entry) in file.blocks.iter().zip(&light_file.blocks) { + for (block, state_entry) in file.blocks.iter().zip(&state_file.blocks) { assert_eq!( block.first_id, table.len() as u32, @@ -560,8 +562,8 @@ fn build_table(data: &str, light_data: &str) -> Vec { block.name ); assert_eq!( - light_entry.name, block.name, - "light-property data out of order at '{}'", + state_entry.name, block.name, + "state data out of order at '{}'", block.name ); let name = intern(&block.name); @@ -584,7 +586,7 @@ fn build_table(data: &str, light_data: &str) -> Vec { let collides = !NO_COLLISION.contains(&block.name.as_str()); let count: u32 = props.iter().map(|(_, vs)| vs.len() as u32).product(); - let face_indices = light_face_indices(light_entry, count as usize); + let face_indices = light_face_indices(state_entry, count as usize); for offset in 0..count { let mut pairs = Vec::with_capacity(props.len()); let mut stride = count; @@ -596,11 +598,11 @@ fn build_table(data: &str, light_data: &str) -> Vec { let fluid = state_fluid(name, &properties); let shape = compute_shape(name, &properties).map(Vec::into_boxed_slice); let light = LightProps { - emission: light_entry.e.get(offset as usize), - dampening: light_entry.d.get(offset as usize), - propagates_skylight_down: light_entry.p.get(offset as usize) != 0, - can_occlude: light_entry.o.get(offset as usize) != 0, - use_shape_for_light_occlusion: light_entry.u.get(offset as usize) != 0, + emission: state_entry.e.get(offset as usize), + dampening: state_entry.d.get(offset as usize), + propagates_skylight_down: state_entry.p.get(offset as usize) != 0, + can_occlude: state_entry.o.get(offset as usize) != 0, + use_shape_for_light_occlusion: state_entry.u.get(offset as usize) != 0, face_occlusion: face_indices[offset as usize].map(&mut tuple), }; table.push(BlockData { @@ -633,7 +635,7 @@ fn decode_face_mask(hex: &str) -> FaceMask { } /// Expands a light entry's `f` field into per-state mask-dictionary tuples. -fn light_face_indices(entry: &LightEntry, count: usize) -> Vec> { +fn light_face_indices(entry: &StateEntry, count: usize) -> Vec> { let Some(f) = &entry.f else { return vec![None; count]; }; @@ -976,7 +978,7 @@ mod tests { #[test] fn all_tables_build() { for (slot, (_, data)) in BLOCK_DATA.iter().enumerate() { - build_table(data, LIGHT_DATA[slot]); + build_table(data, STATE_DATA[slot]); } } } diff --git a/tools/blockgen/src/main.rs b/tools/blockgen/src/main.rs index 1ac95103..9afed8b6 100644 --- a/tools/blockgen/src/main.rs +++ b/tools/blockgen/src/main.rs @@ -5,7 +5,7 @@ //! Usage: //! blockgen blocks //! blockgen behavior -//! blockgen light .json> +//! blockgen state .json> //! //! `blocks` flattens the report into a compact per-block table (name, first //! state id, default state id, ordered property lists). Every explicit state @@ -18,12 +18,12 @@ //! src/generated.rs`); new blocks the seed doesn't know must be appended by //! hand from the decompiled `Blocks.java`. //! -//! `light` compacts the raw per-state light-property dump produced by -//! `tools/lightgen/LightDump.java` (see `just lightgen`) into the per-block -//! table the client embeds: each field is a scalar when uniform across the -//! block's states, else a per-state array, and face-occlusion masks are -//! deduped into a dictionary. State counts and value ranges are cross-checked -//! against the version's blocks table. +//! `state` compacts the raw per-state property dump produced by running +//! vanilla (`tools/stategen/StateDump.java`, see `just stategen`) into the +//! per-block table the client embeds: each field is a scalar when uniform +//! across the block's states, else a per-state array, and face-occlusion +//! masks are deduped into a dictionary. State counts and value ranges are +//! cross-checked against the version's blocks table. use std::collections::BTreeMap; use std::fmt::Write as _; @@ -34,8 +34,8 @@ fn main() -> ExitCode { let result = match args.as_slice() { [cmd, report, version, out] if cmd == "blocks" => gen_blocks(report, version, out), [cmd, generated, out] if cmd == "behavior" => gen_behavior(generated, out), - [cmd, dump, blocks, out] if cmd == "light" => gen_light(dump, blocks, out), - _ => Err("usage: blockgen blocks \n blockgen behavior \n blockgen light .json> ".into()), + [cmd, dump, blocks, out] if cmd == "state" => gen_state(dump, blocks, out), + _ => Err("usage: blockgen blocks \n blockgen behavior \n blockgen state .json> ".into()), }; match result { Ok(()) => ExitCode::SUCCESS, @@ -322,9 +322,9 @@ fn extract_float_arg(text: &str, method: &str) -> Option { rest[..end].trim().parse().ok() } -/// Raw per-state dump written by `tools/lightgen/LightDump.java`. +/// Raw per-state dump written by `tools/stategen/StateDump.java`. #[derive(serde::Deserialize)] -struct LightDumpFile { +struct StateDumpFile { version: String, state_count: u32, emission: Vec, @@ -352,20 +352,20 @@ struct BlocksEntry { props: Vec<(String, Vec)>, } -fn gen_light(dump_path: &str, blocks_path: &str, out_path: &str) -> Result<(), Error> { - let dump: LightDumpFile = serde_json::from_str(&std::fs::read_to_string(dump_path)?)?; +fn gen_state(dump_path: &str, blocks_path: &str, out_path: &str) -> Result<(), Error> { + let dump: StateDumpFile = serde_json::from_str(&std::fs::read_to_string(dump_path)?)?; let blocks: BlocksFile = serde_json::from_str(&std::fs::read_to_string(blocks_path)?)?; if dump.version != blocks.version { return Err(format!( - "version mismatch: light dump is '{}', blocks table is '{}'", + "version mismatch: state dump is '{}', blocks table is '{}'", dump.version, blocks.version ) .into()); } if dump.state_count != blocks.state_count { return Err(format!( - "state count mismatch: light dump has {}, blocks table has {}", + "state count mismatch: state dump has {}, blocks table has {}", dump.state_count, blocks.state_count ) .into()); @@ -505,7 +505,7 @@ fn gen_light(dump_path: &str, blocks_path: &str, out_path: &str) -> Result<(), E std::fs::write(out_path, &out)?; println!( - "wrote light data for {} states ({} shaped, {} distinct masks) to {out_path}", + "wrote state data for {} states ({} shaped, {} distinct masks) to {out_path}", n, masks_by_state.len(), dict.len() diff --git a/tools/lightgen/LightDump.java b/tools/stategen/StateDump.java similarity index 96% rename from tools/lightgen/LightDump.java rename to tools/stategen/StateDump.java index b90c25d1..a2699e15 100644 --- a/tools/lightgen/LightDump.java +++ b/tools/stategen/StateDump.java @@ -10,7 +10,7 @@ import java.util.Map; /** - * Dumps per-block-state light properties by running vanilla's own code: + * Dumps per-block-state properties (the baked light set) by running vanilla's own code: * bootstraps the block registry from the server jar on the classpath, then * iterates Block.BLOCK_STATE_REGISTRY in state-id order. * @@ -24,16 +24,16 @@ * that reduces to 2D coverage — exact as long as every shape is 1/16-aligned, * which this tool hard-fails on if violated. * - * Usage: java -cp ;;. LightDump + * Usage: java -cp ;;. StateDump */ -public final class LightDump { +public final class StateDump { // Direction.values() order (DOWN, UP, NORTH, SOUTH, WEST, EAST) -> slice axis: // Y for down/up, Z for north/south, X for west/east. private static final char[] AXIS_BY_ORDINAL = {'Y', 'Y', 'Z', 'Z', 'X', 'X'}; public static void main(String[] args) throws Exception { if (args.length != 2) { - System.err.println("usage: LightDump "); + System.err.println("usage: StateDump "); System.exit(2); } String version = args[0]; @@ -230,5 +230,5 @@ private static Method firstMethod(Class cls, String... names) throws NoSuchMe } } - private LightDump() {} + private StateDump() {} }