mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 02:29:50 -10:00
Fix mannequin_save key order, environment_timeline NaN sign, and inline_feature_parse codec errors
Co-Authored-By: Gemini 3 Pro <[email protected]>
This commit is contained in:
1 parent
11102a5651
commit
16d1d18ef1
3 files changed
+208
-14
No files matched your search
@@ -313,6 +313,14 @@ pub fn java_hash_order(
|
||||
"NoBasePlate",
|
||||
"Marker",
|
||||
"Pose",
|
||||
// Mannequin.addAdditionalSaveData.
|
||||
"profile",
|
||||
"hidden_layers",
|
||||
"main_hand",
|
||||
"pose",
|
||||
"immovable",
|
||||
"description",
|
||||
"hide_description",
|
||||
];
|
||||
// BlockEntity.saveMetadata writes the position after the entity's data.
|
||||
const AFTER: &[&str] = &["x", "y", "z"];
|
||||
@@ -397,6 +405,21 @@ mod tests {
|
||||
assert_eq!(pretty(&tag).plain(), ordinary);
|
||||
}
|
||||
#[test]
|
||||
fn mannequin_compound_orders_pose_before_immovable_in_shared_bucket() {
|
||||
let tag = parse_snbt(
|
||||
r#"{AbsorptionAmount:0.0f,Air:300s,Brain:{memories:{}},DeathTime:0s,FallFlying:0b,Fire:0s,Health:20.0f,HurtTime:0s,Invulnerable:0b,Motion:[0.0d,0.0d,0.0d],NoGravity:1b,OnGround:0b,PortalCooldown:0,Pos:[2.5d,100.0d,2.5d],Rotation:[0.0f,0.0f],Tags:["m1"],UUID:[I;369,0,0,1],attributes:[],current_impulse_context_reset_grace_time:0,description:{text:"Guard"},hide_description:1b,immovable:1b,main_hand:"left",pose:"crouching",profile:{name:"alex"}}"#,
|
||||
)
|
||||
.unwrap()
|
||||
.0;
|
||||
let rendered = pretty(&tag).plain();
|
||||
let pose_idx = rendered.find("pose: \"crouching\"").unwrap();
|
||||
let immovable_idx = rendered.find("immovable: 1b").unwrap();
|
||||
assert!(
|
||||
pose_idx < immovable_idx,
|
||||
"expected `pose` before `immovable` in bucket 5, got: {rendered}"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn compact_form_matches_vanilla() {
|
||||
assert_eq!(
|
||||
rt("{b:1b,a:\"x\",c:[1,2],d:1.5f,e:2d,f:3L,g:4s}"),
|
||||
|
||||
@@ -150,6 +150,137 @@ fn predicate_fields(id: &str, map: &Compound, t: &Tag, reg: &Registries) -> Resu
|
||||
}
|
||||
}
|
||||
|
||||
/// ConfiguredFeature.DIRECT_CODEC: validates the feature `type` against
|
||||
/// `minecraft:worldgen/feature_type` and checks required configuration keys
|
||||
/// with DataFixerUpper error formatting.
|
||||
fn check_feature(tag: &Tag, reg: &Registries) -> Result<(), String> {
|
||||
const KNOWN_FEATURE_TYPES: &[&str] = &[
|
||||
"minecraft:no_op",
|
||||
"minecraft:tree",
|
||||
"minecraft:fallen_tree",
|
||||
"minecraft:flower",
|
||||
"minecraft:no_bonemeal_flower",
|
||||
"minecraft:random_patch",
|
||||
"minecraft:block_pile",
|
||||
"minecraft:spring_feature",
|
||||
"minecraft:chorus_plant",
|
||||
"minecraft:replace_single_block",
|
||||
"minecraft:void_start_platform",
|
||||
"minecraft:desert_well",
|
||||
"minecraft:fossil",
|
||||
"minecraft:huge_red_mushroom",
|
||||
"minecraft:huge_brown_mushroom",
|
||||
"minecraft:huge_fungus",
|
||||
"minecraft:ice_spike",
|
||||
"minecraft:glowstone_blob",
|
||||
"minecraft:freeze_top_layer",
|
||||
"minecraft:vines",
|
||||
"minecraft:block_column",
|
||||
"minecraft:vegetation_patch",
|
||||
"minecraft:waterlogged_vegetation_patch",
|
||||
"minecraft:root_system",
|
||||
"minecraft:multiface_growth",
|
||||
"minecraft:underwater_magma",
|
||||
"minecraft:monster_room",
|
||||
"minecraft:blue_ice",
|
||||
"minecraft:iceberg",
|
||||
"minecraft:forest_rock",
|
||||
"minecraft:disk",
|
||||
"minecraft:lake",
|
||||
"minecraft:ore",
|
||||
"minecraft:scattered_ore",
|
||||
"minecraft:end_platform",
|
||||
"minecraft:end_spike",
|
||||
"minecraft:end_island",
|
||||
"minecraft:end_gateway",
|
||||
"minecraft:seagrass",
|
||||
"minecraft:kelp",
|
||||
"minecraft:coral_tree",
|
||||
"minecraft:coral_mushroom",
|
||||
"minecraft:coral_claw",
|
||||
"minecraft:sea_pickle",
|
||||
"minecraft:simple_block",
|
||||
"minecraft:bamboo",
|
||||
"minecraft:geode",
|
||||
"minecraft:dripstone_cluster",
|
||||
"minecraft:large_dripstone",
|
||||
"minecraft:pointed_dripstone",
|
||||
"minecraft:sculk_patch",
|
||||
"minecraft:twisting_vines",
|
||||
"minecraft:weeping_vines",
|
||||
"minecraft:nether_forest_vegetation",
|
||||
"minecraft:basalt_columns",
|
||||
"minecraft:delta_feature",
|
||||
"minecraft:netherrack_replace_blobs",
|
||||
"minecraft:fill_layer",
|
||||
"minecraft:bonus_chest",
|
||||
"minecraft:basalt_pillar",
|
||||
"minecraft:random_selector",
|
||||
"minecraft:simple_random_selector",
|
||||
"minecraft:random_boolean_selector",
|
||||
];
|
||||
let Tag::Compound(c) = tag else {
|
||||
return Err(not_map(tag));
|
||||
};
|
||||
let Some(ty) = get(c, "type") else {
|
||||
return Err(no_key("type", t_or(tag)));
|
||||
};
|
||||
let Some(name) = as_string(ty) else {
|
||||
return Err("Not a string".into());
|
||||
};
|
||||
let Some(full) = parse_identifier(&name) else {
|
||||
return Err(format!("Not a valid resource location: {name}"));
|
||||
};
|
||||
let known_ids = reg.ids("minecraft:worldgen/feature_type");
|
||||
let known = if !known_ids.is_empty() {
|
||||
known_ids.contains(&full)
|
||||
} else {
|
||||
KNOWN_FEATURE_TYPES.contains(&full.as_str())
|
||||
};
|
||||
if !known {
|
||||
return Err(format!(
|
||||
"Unknown registry key in ResourceKey[minecraft:root / minecraft:worldgen/feature_type]: {full}"
|
||||
));
|
||||
}
|
||||
if full == "minecraft:simple_block" {
|
||||
let cfg_tag = get(c, "config").unwrap_or(tag);
|
||||
let Tag::Compound(cfg_map) = cfg_tag else {
|
||||
return Err(not_map(cfg_tag));
|
||||
};
|
||||
if get(cfg_map, "to_place").is_none() {
|
||||
return Err(no_key("to_place", cfg_tag));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn t_or(t: &Tag) -> &Tag {
|
||||
t
|
||||
}
|
||||
|
||||
fn dfu_feature_error(tag: &Tag, msg: &str) -> String {
|
||||
if let Some(field) = msg.strip_prefix("invalid world generation field: ") {
|
||||
if field == "basic feature type"
|
||||
&& let Some(Tag::String(s)) = tag.get("type")
|
||||
{
|
||||
let raw = s.to_string_lossy();
|
||||
let full = if raw.contains(':') {
|
||||
raw
|
||||
} else {
|
||||
format!("minecraft:{raw}")
|
||||
};
|
||||
return format!(
|
||||
"Unknown registry key in ResourceKey[minecraft:root / minecraft:worldgen/feature_type]: {full}"
|
||||
);
|
||||
}
|
||||
let cfg_tag = tag.get("config").unwrap_or(tag);
|
||||
if matches!(cfg_tag, Tag::Compound(c) if get(c, field).is_none()) {
|
||||
return no_key(field, cfg_tag);
|
||||
}
|
||||
}
|
||||
msg.to_owned()
|
||||
}
|
||||
|
||||
/// Decode an inline value of a ResourceOrIdArgument registry.
|
||||
pub fn decode(registry: &str, t: &Tag, reg: &Registries) -> Result<(), String> {
|
||||
match registry {
|
||||
@@ -218,13 +349,13 @@ pub fn decode(registry: &str, t: &Tag, reg: &Registries) -> Result<(), String> {
|
||||
require(map, t, &keys)
|
||||
}),
|
||||
// The ConfiguredFeature codec, where the source has the live features.
|
||||
"minecraft:worldgen/feature" => match t {
|
||||
Tag::Compound(_) => match ®.inline_feature {
|
||||
Some(codec) => codec.check(t),
|
||||
None => Ok(()),
|
||||
},
|
||||
_ => Err(not_map(t)),
|
||||
},
|
||||
"minecraft:worldgen/feature" => {
|
||||
check_feature(t, reg)?;
|
||||
if let Some(codec) = ®.inline_feature {
|
||||
return codec.check(t).map_err(|msg| dfu_feature_error(t, &msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
"minecraft:loot_table" => match t {
|
||||
Tag::Compound(_) => Ok(()),
|
||||
_ => Err(not_map(t)),
|
||||
|
||||
@@ -36,11 +36,30 @@ const PI: f32 = std::f64::consts::PI as f32;
|
||||
/// negative default NaN), which `-` makes positive and `+ 1.0F` keeps. LLVM may fold `-r + 1.0` into `1.0 - r`,
|
||||
/// whose NaN keeps the root's sign, so the NaN is returned before the addition.
|
||||
fn in_circ(x: f32) -> f32 {
|
||||
let negated = (-f64::from(1.0 - x * x).sqrt()) as f32;
|
||||
if negated.is_nan() {
|
||||
return f32::from_bits(0x7fc0_0000);
|
||||
let radicand = f64::from(1.0 - x * x);
|
||||
if radicand < 0.0 {
|
||||
const IN_CIRC_NAN: u32 = if cfg!(target_arch = "aarch64") {
|
||||
0xffc0_0000
|
||||
} else {
|
||||
0x7fc0_0000
|
||||
};
|
||||
return f32::from_bits(IN_CIRC_NAN);
|
||||
}
|
||||
negated + 1.0
|
||||
((-radicand.sqrt()) as f32) + 1.0
|
||||
}
|
||||
|
||||
fn out_circ(x: f32) -> f32 {
|
||||
let d = x - 1.0;
|
||||
let radicand = f64::from(1.0 - d * d);
|
||||
if radicand < 0.0 {
|
||||
const OUT_CIRC_NAN: u32 = if cfg!(target_arch = "aarch64") {
|
||||
0x7fc0_0000
|
||||
} else {
|
||||
0xffc0_0000
|
||||
};
|
||||
return f32::from_bits(OUT_CIRC_NAN);
|
||||
}
|
||||
radicand.sqrt() as f32
|
||||
}
|
||||
|
||||
const EASINGS: [(&str, fn(f32) -> f32); 32] = [
|
||||
@@ -159,9 +178,7 @@ const EASINGS: [(&str, fn(f32) -> f32); 32] = [
|
||||
1.0 + 2.701_58 * cube(x - 1.0) + 1.701_58 * square(x - 1.0)
|
||||
}),
|
||||
("out_bounce", out_bounce),
|
||||
("out_circ", |x| {
|
||||
f64::from(1.0 - square(x - 1.0)).sqrt() as f32
|
||||
}),
|
||||
("out_circ", out_circ),
|
||||
("out_cubic", |x| 1.0 - cube(1.0 - x)),
|
||||
("out_elastic", |x| {
|
||||
if x == 0.0 {
|
||||
@@ -557,3 +574,26 @@ impl Timeline {
|
||||
self.period.map_or(0, |p| (total / i64::from(p)) as i32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn out_of_domain_circ_easings_preserve_vanilla_nan_bits() {
|
||||
let expected_in = if cfg!(target_arch = "aarch64") {
|
||||
0xffc0_0000
|
||||
} else {
|
||||
0x7fc0_0000
|
||||
};
|
||||
let expected_out = if cfg!(target_arch = "aarch64") {
|
||||
0x7fc0_0000
|
||||
} else {
|
||||
0xffc0_0000
|
||||
};
|
||||
let in_circ = Easing::parse(&serde_json::json!("in_circ")).unwrap();
|
||||
let out_circ = Easing::parse(&serde_json::json!("out_circ")).unwrap();
|
||||
assert_eq!(in_circ.apply(1.2).to_bits(), expected_in);
|
||||
assert_eq!(out_circ.apply(-0.2).to_bits(), expected_out);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user