Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/node_modules
/reference
/test-server
/tools/lightgen/out
/tools/stategen/out
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<version>/`
- `just protogen` / `just registrygen` / `just blockgen` / `just stategen`: regenerate a version's packet-id, registry, block-state, and per-state property tables from `reference/<version>/`

## Contributing

Expand Down
18 changes: 9 additions & 9 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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=<path> lightgen`.
# JDK 25 bin dir for stategen; override with `just jdk=<path> 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 }}"
Expand All @@ -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
68 changes: 35 additions & 33 deletions pomme-client/src/world/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<String>,
blocks: Vec<LightEntry>,
blocks: Vec<StateEntry>,
}

/// 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,
Expand Down Expand Up @@ -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-<v>.json` is the state layout (ids and properties
/// from the data-generator report); `state-<v>.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
Expand Down Expand Up @@ -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<BlockData> {
fn build_table(data: &str, state_data: &str) -> Vec<BlockData> {
let file: BlockFile = serde_json::from_str(data).expect("invalid block-state data");
let behaviors: HashMap<String, BehaviorEntry> =
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))
Expand Down Expand Up @@ -552,16 +554,16 @@ fn build_table(data: &str, light_data: &str) -> Vec<BlockData> {
};

let mut table: Vec<BlockData> = 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,
"block-state data not dense at '{}'",
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);
Expand All @@ -584,7 +586,7 @@ fn build_table(data: &str, light_data: &str) -> Vec<BlockData> {
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;
Expand All @@ -596,11 +598,11 @@ fn build_table(data: &str, light_data: &str) -> Vec<BlockData> {
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 {
Expand Down Expand Up @@ -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<Option<[u32; 6]>> {
fn light_face_indices(entry: &StateEntry, count: usize) -> Vec<Option<[u32; 6]>> {
let Some(f) = &entry.f else {
return vec![None; count];
};
Expand Down Expand Up @@ -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]);
}
}
}
32 changes: 16 additions & 16 deletions tools/blockgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! Usage:
//! blockgen blocks <reports/blocks.json> <version> <out.json>
//! blockgen behavior <azalea generated.rs> <out.json>
//! blockgen light <generated/light.json> <blocks-<v>.json> <out.json>
//! blockgen state <generated/state.json> <blocks-<v>.json> <out.json>
//!
//! `blocks` flattens the report into a compact per-block table (name, first
//! state id, default state id, ordered property lists). Every explicit state
Expand All @@ -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 _;
Expand All @@ -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 <blocks.json> <version> <out.json>\n blockgen behavior <generated.rs> <out.json>\n blockgen light <light.json> <blocks-<v>.json> <out.json>".into()),
[cmd, dump, blocks, out] if cmd == "state" => gen_state(dump, blocks, out),
_ => Err("usage: blockgen blocks <blocks.json> <version> <out.json>\n blockgen behavior <generated.rs> <out.json>\n blockgen state <state.json> <blocks-<v>.json> <out.json>".into()),
};
match result {
Ok(()) => ExitCode::SUCCESS,
Expand Down Expand Up @@ -322,9 +322,9 @@ fn extract_float_arg(text: &str, method: &str) -> Option<f32> {
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<u8>,
Expand Down Expand Up @@ -352,20 +352,20 @@ struct BlocksEntry {
props: Vec<(String, Vec<String>)>,
}

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());
Expand Down Expand Up @@ -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()
Expand Down
10 changes: 5 additions & 5 deletions tools/lightgen/LightDump.java → tools/stategen/StateDump.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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 <server-classes-jar>;<bundled-libs...>;. LightDump <version> <out.json>
* Usage: java -cp <server-classes-jar>;<bundled-libs...>;. StateDump <version> <out.json>
*/
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 <version> <out.json>");
System.err.println("usage: StateDump <version> <out.json>");
System.exit(2);
}
String version = args[0];
Expand Down Expand Up @@ -230,5 +230,5 @@ private static Method firstMethod(Class<?> cls, String... names) throws NoSuchMe
}
}

private LightDump() {}
private StateDump() {}
}