Files
Emil c7e86663d8
MVP checks / mvp (push) Canceled after 0s
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

487 lines
16 KiB
Rust

use shacraft_compat::{
Mode, export_anvil, export_schem, import_anvil, import_anvil_baseline, import_schem,
nbt::{self, Tag},
};
use shacraft_core::{BlockChange, WorldStore};
use std::{
fs,
io::Read,
path::{Path, PathBuf},
};
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("fixtures")
.join(name)
}
fn read_gzip(p: &Path) -> Tag {
let mut b = Vec::new();
flate2::read::GzDecoder::new(fs::File::open(p).unwrap())
.read_to_end(&mut b)
.unwrap();
nbt::decode(&b).unwrap().1
}
fn assert_tree_equal(source: &Path, out: &Path) {
for e in fs::read_dir(source).unwrap() {
let e = e.unwrap();
let to = out.join(e.file_name());
if e.file_type().unwrap().is_dir() {
assert_tree_equal(&e.path(), &to)
} else {
assert_eq!(
fs::read(e.path()).unwrap(),
fs::read(to).unwrap(),
"{}",
e.path().display()
)
}
}
}
#[test]
fn sponge_import_edits_unknown_states_and_preserves_nbt() {
let t = tempfile::tempdir().unwrap();
let store_path = t.path().join("store");
let source = fixture("sponge-v3.schem");
let imported = import_schem(&source, &store_path, "main", Mode::Exact).unwrap();
assert_eq!(imported.nonair_blocks, 4);
let exact = t.path().join("exact");
export_schem(&store_path, "main", &exact, None, None, Mode::Exact).unwrap();
assert_eq!(
fs::read(&source).unwrap(),
fs::read(exact.join("world.schem")).unwrap()
);
let mut store = WorldStore::open(&store_path, 2).unwrap();
let unknown = store.get_block("main", [-15, -1, 31]).unwrap();
assert_eq!(
store.registry()[unknown as usize],
"example:unknown[facing=west]"
);
let revision = store.revision("main").unwrap();
store
.edit(
"main",
revision,
"break-container",
vec![BlockChange {
pos: [-17, -1, 31],
block: 0,
}],
)
.unwrap();
drop(store);
let rejected = t.path().join("rejected");
assert!(
export_schem(&store_path, "main", &rejected, None, None, Mode::Exact)
.unwrap_err()
.to_string()
.contains("exact_provenance_invalidated")
);
assert!(!rejected.exists());
let edited = t.path().join("edited");
let r = export_schem(&store_path, "main", &edited, None, None, Mode::BestEffort).unwrap();
assert!(r.issues.iter().any(|i| i.code == "changed_block_entity"));
let a = read_gzip(&source);
let b = read_gzip(&edited.join("world.schem"));
let a = a.compound().unwrap()["Schematic"].compound().unwrap();
let b = b.compound().unwrap()["Schematic"].compound().unwrap();
for k in ["Metadata", "Entities", "Biomes", "UnknownRoot", "Offset"] {
assert_eq!(a[k], b[k], "{k}")
}
let reimport = t.path().join("reimport");
import_schem(&edited.join("world.schem"), &reimport, "main", Mode::Exact).unwrap();
let mut s = WorldStore::open(&reimport, 2).unwrap();
assert_eq!(s.get_block("main", [-17, -1, 31]).unwrap(), 0);
assert_ne!(s.get_block("main", [-16, -1, 31]).unwrap(), 0);
}
#[test]
fn genuine_java_world_import_exact_roundtrip_edit_and_new_chunk() {
let t = tempfile::tempdir().unwrap();
let store_path = t.path().join("store");
let source = fixture("java26_2-world");
let imported = import_anvil(&source, &store_path, Mode::Exact).unwrap();
assert_eq!(imported.nonair_blocks, 3);
assert_eq!(imported.chunks, 1);
let exact = t.path().join("exact");
export_anvil(&store_path, None, &exact, Mode::Exact).unwrap();
assert_tree_equal(&source, &exact);
let mut store = WorldStore::open(&store_path, 2).unwrap();
let stone = store.register_block("minecraft:stone").unwrap();
assert_ne!(store.get_block("main", [-1, -1, 31]).unwrap(), 0);
let revision = store.revision("main").unwrap();
store
.edit(
"main",
revision,
"edit",
vec![
BlockChange {
pos: [-1, -1, 31],
block: 0,
},
BlockChange {
pos: [512, 64, -513],
block: stone,
},
],
)
.unwrap();
drop(store);
let rejected = t.path().join("rejected");
assert!(export_anvil(&store_path, None, &rejected, Mode::Exact).is_err());
assert!(!rejected.exists());
let edited = t.path().join("edited");
let report = export_anvil(&store_path, None, &edited, Mode::BestEffort).unwrap();
assert_eq!(report.chunks, 2);
assert!(
report
.issues
.iter()
.any(|i| i.code == "dependent_records_invalidated")
);
assert_eq!(
fs::read(source.join("entities/r.-1.0.mca")).unwrap(),
fs::read(edited.join("entities/r.-1.0.mca")).unwrap()
);
let reimport = t.path().join("reimport");
import_anvil(&edited, &reimport, Mode::Exact).unwrap();
let mut store = WorldStore::open(&reimport, 2).unwrap();
assert_eq!(store.get_block("main", [-1, -1, 31]).unwrap(), 0);
let id = store.get_block("main", [512, 64, -513]).unwrap();
assert_eq!(store.registry()[id as usize], "minecraft:stone");
}
#[test]
fn anvil_baseline_preserves_roundtrip_edits_snapshots_and_reset() {
let t = tempfile::tempdir().unwrap();
let path = t.path().join("baseline");
let source = fixture("java26_2-world");
let report = import_anvil_baseline(&source, &path, Mode::Exact).unwrap();
assert_eq!(report.nonair_blocks, 3);
assert_eq!(report.worlds["main"], 0);
let exact = t.path().join("exact");
export_anvil(&path, None, &exact, Mode::Exact).unwrap();
assert_tree_equal(&source, &exact);
let db = rusqlite::Connection::open(path.join("worlds.sqlite3")).unwrap();
let history: i64 = db
.query_row("SELECT count(*) FROM operation_changes", [], |r| r.get(0))
.unwrap();
assert_eq!(history, 0);
drop(db);
let mut store = WorldStore::open(&path, 2).unwrap();
let pos = [-1, -1, 31];
let original = store.get_block("main", pos).unwrap();
assert_ne!(original, 0);
store
.edit("main", 0, "remove", vec![BlockChange { pos, block: 0 }])
.unwrap();
drop(store);
assert!(export_anvil(&path, None, &t.path().join("rejected"), Mode::Exact).is_err());
let edited = t.path().join("edited");
export_anvil(&path, None, &edited, Mode::BestEffort).unwrap();
let reimport = t.path().join("reimport");
import_anvil_baseline(&edited, &reimport, Mode::Exact).unwrap();
assert_eq!(
WorldStore::open(&reimport, 2)
.unwrap()
.get_block("main", pos)
.unwrap(),
0
);
let mut store = WorldStore::open(&path, 2).unwrap();
store.reset_world("main", 1, "reset").unwrap();
assert_eq!(store.get_block("main", pos).unwrap(), original);
store.create_world("playable", Some("main")).unwrap();
assert_eq!(store.get_block("playable", pos).unwrap(), original);
assert!(
store
.read_section("playable", [-1, -1, 1])
.unwrap()
.is_some()
);
}
#[test]
fn native_new_world_export_and_schematic() {
let t = tempfile::tempdir().unwrap();
let store_path = t.path().join("store");
let mut store = WorldStore::open(&store_path, 2).unwrap();
store.create_world("native", None).unwrap();
let stone = store.register_block("minecraft:stone").unwrap();
let stairs = store
.register_block(
"minecraft:oak_stairs[facing=east,half=top,shape=straight,waterlogged=false]",
)
.unwrap();
store
.edit(
"native",
0,
"build",
vec![
BlockChange {
pos: [-1, 63, 0],
block: stone,
},
BlockChange {
pos: [0, 64, 0],
block: stairs,
},
],
)
.unwrap();
drop(store);
let world = t.path().join("world");
export_anvil(&store_path, Some("native"), &world, Mode::BestEffort).unwrap();
assert!(
world
.join("data/minecraft/world_gen_settings.dat")
.is_file()
);
let reimport = t.path().join("reimport");
assert_eq!(
import_anvil(&world, &reimport, Mode::Exact)
.unwrap()
.nonair_blocks,
2
);
let schem = t.path().join("schem");
export_schem(
&store_path,
"native",
&schem,
Some([-1, 63, 0]),
Some([0, 64, 0]),
Mode::Exact,
)
.unwrap();
let s = t.path().join("schem-reimport");
assert_eq!(
import_schem(&schem.join("world.schem"), &s, "main", Mode::Exact)
.unwrap()
.nonair_blocks,
2
);
}
#[test]
fn no_overwrite_and_provenance_hash_guard() {
let t = tempfile::tempdir().unwrap();
let path = t.path().join("store");
let source = fixture("sponge-v3.schem");
import_schem(&source, &path, "main", Mode::Exact).unwrap();
let before = fs::read(path.join("worlds.sqlite3")).unwrap();
assert!(import_schem(&source, &path, "other", Mode::Exact).is_err());
assert_eq!(before, fs::read(path.join("worlds.sqlite3")).unwrap());
fs::write(path.join("compat/original/source.schem"), b"tampered").unwrap();
let out = t.path().join("out");
assert!(export_schem(&path, "main", &out, None, None, Mode::Exact).is_err());
assert!(!out.exists());
}
#[test]
fn malformed_input_is_not_published() {
let t = tempfile::tempdir().unwrap();
let bad = t.path().join("bad.schem");
fs::write(&bad, b"not gzip").unwrap();
let out = t.path().join("out");
assert!(import_schem(&bad, &out, "main", Mode::Exact).is_err());
assert!(!out.exists());
}
fn metadata(path: &Path) -> serde_json::Value {
let db = rusqlite::Connection::open(path.join("server.sqlite3")).unwrap();
let text: String = db
.query_row("SELECT json FROM metadata WHERE id=1", [], |r| r.get(0))
.unwrap();
serde_json::from_str(&text).unwrap()
}
fn save_metadata(path: &Path, v: &serde_json::Value) {
let db = rusqlite::Connection::open(path.join("server.sqlite3")).unwrap();
db.execute(
"UPDATE metadata SET json=?1 WHERE id=1",
[serde_json::to_string(v).unwrap()],
)
.unwrap();
}
#[test]
fn native_entity_edit_move_create_remove_roundtrip() {
let t = tempfile::tempdir().unwrap();
let path = t.path().join("store");
import_anvil(&fixture("java26_2-world"), &path, Mode::Exact).unwrap();
let mut meta = metadata(&path);
let id = meta["entities"]
.as_object()
.unwrap()
.keys()
.next()
.unwrap()
.clone();
meta["entities"][&id]["position"] = serde_json::json!([513.5, 65., -513.5]);
meta["entities"][&id]["yaw"] = serde_json::json!(1.25);
meta["entities"][&id]["properties"]["Invisible"] = serde_json::json!(true);
let new_id = "12345678-1234-4234-8234-123456789abc";
meta["entities"][new_id] = serde_json::json!({"id":new_id,"world":"main","kind":"minecraft:cow","type":"minecraft:cow","position":[0.5,70.,0.5],"yaw":0.,"properties":{"Health":17.0,"CustomName":"moo","custom_unmapped":{"a":1}}});
meta["revision"] = serde_json::json!(1);
save_metadata(&path, &meta);
let rejected = t.path().join("exact");
assert!(
export_anvil(&path, None, &rejected, Mode::Exact)
.unwrap_err()
.to_string()
.contains("exact_entity_revision")
);
assert!(!rejected.exists());
let out = t.path().join("out");
let r = export_anvil(&path, None, &out, Mode::BestEffort).unwrap();
assert!(
r.issues
.iter()
.any(|i| i.code == "entity_property_unmapped")
);
let again = t.path().join("again");
import_anvil(&out, &again, Mode::Exact).unwrap();
let got = metadata(&again);
assert_eq!(got["entities"].as_object().unwrap().len(), 2);
assert_eq!(
got["entities"][&id]["position"],
meta["entities"][&id]["position"]
);
assert!(
got["entities"][&id]["properties"]["Invisible"]
.as_bool()
.unwrap()
);
assert_eq!(
got["entities"][new_id]["properties"]["Health"],
serde_json::json!(17.0)
);
meta["entities"] = serde_json::json!({});
save_metadata(&path, &meta);
let removed = t.path().join("removed");
let r = export_anvil(&path, None, &removed, Mode::BestEffort).unwrap();
assert!(r.issues.iter().any(|i| i.code == "entity_removed"));
let final_store = t.path().join("final");
import_anvil(&removed, &final_store, Mode::Exact).unwrap();
assert!(
metadata(&final_store)["entities"]
.as_object()
.unwrap()
.is_empty()
);
}
#[test]
fn sponge_entity_metadata_guard_and_position_roundtrip() {
let t = tempfile::tempdir().unwrap();
let path = t.path().join("store");
import_schem(&fixture("sponge-v3.schem"), &path, "main", Mode::Exact).unwrap();
let mut meta = metadata(&path);
let id = meta["entities"]
.as_object()
.unwrap()
.keys()
.next()
.unwrap()
.clone();
meta["entities"][&id]["position"] = serde_json::json!([-15., 1., 31.]);
save_metadata(&path, &meta);
assert!(
export_schem(
&path,
"main",
&t.path().join("reject"),
None,
None,
Mode::Exact
)
.is_err()
);
let out = t.path().join("out");
export_schem(&path, "main", &out, None, None, Mode::BestEffort).unwrap();
let again = t.path().join("again");
import_schem(&out.join("world.schem"), &again, "main", Mode::Exact).unwrap();
assert_eq!(
metadata(&again)["entities"][&id]["position"],
meta["entities"][&id]["position"]
);
}
fn write_gzip(path: &Path, tag: &Tag) {
use std::io::Write;
let mut out = flate2::write::GzEncoder::new(
fs::File::create(path).unwrap(),
flate2::Compression::default(),
);
out.write_all(&nbt::encode("", tag).unwrap()).unwrap();
out.finish().unwrap();
}
#[test]
fn duplicate_entity_uuid_stays_outside_editable_subset() {
let t = tempfile::tempdir().unwrap();
let mut tag = read_gzip(&fixture("sponge-v3.schem"));
let c = tag
.compound_mut()
.unwrap()
.get_mut("Schematic")
.unwrap()
.compound_mut()
.unwrap();
if let Tag::List(_, entities) = c.get_mut("Entities").unwrap() {
entities.push(entities[0].clone());
} else {
panic!("fixture entities")
}
let source = t.path().join("duplicate.schem");
write_gzip(&source, &tag);
let store = t.path().join("store");
let report = import_schem(&source, &store, "main", Mode::Exact).unwrap();
assert!(
report
.issues
.iter()
.any(|i| i.code == "entity_duplicate_uuid")
);
assert!(metadata(&store)["entities"].as_object().unwrap().is_empty());
let out = t.path().join("out");
export_schem(&store, "main", &out, None, None, Mode::Exact).unwrap();
assert_eq!(
fs::read(source).unwrap(),
fs::read(out.join("world.schem")).unwrap()
);
}
#[test]
fn unsupported_data_version_rejects_exact_before_publication() {
let t = tempfile::tempdir().unwrap();
let mut tag = read_gzip(&fixture("sponge-v3.schem"));
let c = tag
.compound_mut()
.unwrap()
.get_mut("Schematic")
.unwrap()
.compound_mut()
.unwrap();
c.insert("DataVersion".into(), Tag::Int(100));
let source = t.path().join("old.schem");
write_gzip(&source, &tag);
let dest = t.path().join("store");
assert!(import_schem(&source, &dest, "main", Mode::Exact).is_err());
assert!(!dest.exists());
let report = import_schem(&source, &dest, "main", Mode::BestEffort).unwrap();
assert!(
report
.issues
.iter()
.any(|i| i.code == "unverified_data_version")
);
assert!(
export_schem(
&dest,
"main",
&t.path().join("reject"),
None,
None,
Mode::Exact
)
.is_err()
);
}