Merge main into #7316

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Ludovic MarieandClaude Opus 5.5 committed 2026-11-02 16:33:49 +01:00
commit a3740ffa49
118 files changed
+2337 -663

No files matched your search

+10 -2
View File
@@ -7011,7 +7011,11 @@ impl Renderer {
let additive_quads = (frame.additive.len() / 4) as u32 - self.entity_batch.moved_additive;
let see_through_quads = (frame.see_through.len() / 4) as u32;
let shadow_only_quads = (frame.shadow_only.len() / 4) as u32;
let entity_vertices = u64::from(posed) + all_entities.len() as u64;
// Glowing entities' silhouettes posed on the GPU go last, past every
// material (the outline pass draws them from there).
let outline_base = posed + all_entities.len() as u32;
let outlined = skin.map_or(0, |s| s.outline_vertices);
let entity_vertices = u64::from(outline_base) + u64::from(outlined);
if entity_vertices > self.entity_capacity {
self.entity_capacity = entity_vertices.next_power_of_two();
self.entity_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
@@ -7037,6 +7041,7 @@ impl Renderer {
skin,
&self.entity_buffer,
0,
outline_base,
);
}
let static_sections = frame.static_block_entities;
@@ -7163,6 +7168,7 @@ impl Renderer {
&mut self.uploads,
&mut encoder,
frame.glowing,
(outline_base, outlined / 4),
[self.config.width, self.config.height],
);
if let Some((rotation, brightness)) = frame.stars {
@@ -7194,7 +7200,8 @@ impl Renderer {
lightning::QUADS
})
.max(weather_quads)
.max(frame.glowing.len() as u32 / 4);
.max(frame.glowing.len() as u32 / 4)
.max(outlined / 4);
if max_quads > self.index_quads {
self.index_quads = max_quads.next_power_of_two();
self.indices = Self::quad_indices(&self.device, self.index_quads);
@@ -9083,6 +9090,7 @@ impl Renderer {
view_target,
&self.entity_group,
&self.indices,
&self.entity_buffer,
timing.as_mut(),
);
// The world icon grabs here: after the level and the hand, before
@@ -29,6 +29,8 @@ pub(super) struct EntityOutline {
vertices: wgpu::Buffer,
capacity: u64,
quads: u32,
/// The silhouettes posed on the GPU: first vertex in the entity buffer, quads.
posed: (u32, u32),
targets: Option<Targets>,
}
@@ -159,6 +161,7 @@ impl EntityOutline {
vertices: Self::vertex_buffer(device, 256),
capacity: 256,
quads: 0,
posed: (0, 0),
targets: None,
}
}
@@ -175,16 +178,20 @@ impl EntityOutline {
/// Uploads this frame's silhouettes (`LevelRenderer.currentFrameRendersEntityOutline`
/// when there are any) and sizes the targets like the window. A frame with
/// none gives the targets' video memory back until an entity glows again.
/// `posed`: where the silhouettes posed on the GPU start in the entity
/// buffer (`entity_skin`'s outline jobs), and how many quads they are.
pub(super) fn prepare(
&mut self,
device: &wgpu::Device,
uploads: &mut super::upload::Uploads,
encoder: &mut wgpu::CommandEncoder,
silhouettes: &[Vertex],
posed: (u32, u32),
size: [u32; 2],
) {
self.quads = (silhouettes.len() / 4) as u32;
if self.quads == 0 {
self.posed = posed;
if self.quads == 0 && posed.1 == 0 {
self.targets = None;
return;
}
@@ -193,12 +200,14 @@ impl EntityOutline {
self.capacity = vertices.next_power_of_two();
self.vertices = Self::vertex_buffer(device, self.capacity);
}
uploads.buffer(
encoder,
&self.vertices,
0,
bytemuck::cast_slice(silhouettes),
);
if !silhouettes.is_empty() {
uploads.buffer(
encoder,
&self.vertices,
0,
bytemuck::cast_slice(silhouettes),
);
}
if self.targets.as_ref().is_some_and(|t| t.size == size) {
return;
}
@@ -260,9 +269,14 @@ impl EntityOutline {
frame: &wgpu::TextureView,
entity_group: &wgpu::BindGroup,
indices: &wgpu::Buffer,
entities: &wgpu::Buffer,
mut timing: Option<&mut super::pack_timing::PassTiming>,
) {
let Some(t) = self.targets.as_ref().filter(|_| self.quads > 0) else {
let Some(t) = self
.targets
.as_ref()
.filter(|_| self.quads > 0 || self.posed.1 > 0)
else {
return;
};
let clear = wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT);
@@ -277,8 +291,15 @@ impl EntityOutline {
pass.set_pipeline(&self.silhouette);
pass.set_bind_group(0, entity_group, &[]);
pass.set_index_buffer(indices.slice(..), wgpu::IndexFormat::Uint32);
pass.set_vertex_buffer(0, self.vertices.slice(..));
pass.draw_indexed(0..self.quads * 6, 0, 0..1);
if self.quads > 0 {
pass.set_vertex_buffer(0, self.vertices.slice(..));
pass.draw_indexed(0..self.quads * 6, 0, 0..1);
}
let (first, quads) = self.posed;
if quads > 0 {
pass.set_vertex_buffer(0, entities.slice(..));
pass.draw_indexed(0..quads * 6, first as i32, 0..1);
}
}
// post_effect/entity_outline.json; its last pass only copies swap back to the
// outline target, so the blit reads swap.
@@ -107,6 +107,11 @@ pub struct SkinFrame {
pub jobs: Vec<SkinJob>,
/// Vertices the jobs write: a whole number of quads.
pub vertices: u32,
/// Glowing entities' silhouettes posed here (`OutlineBufferSource`'s copy of
/// their draws, [`SkinFrame::outline_since`]): `dst` counts from the first.
pub outline_jobs: Vec<SkinJob>,
/// Vertices the outline jobs write.
pub outline_vertices: u32,
}
impl SkinFrame {
@@ -115,6 +120,24 @@ impl SkinFrame {
self.parts.clear();
self.jobs.clear();
self.vertices = 0;
self.outline_jobs.clear();
self.outline_vertices = 0;
}
/// Copies the draws since job `first` into the glowing entities' outline
/// (`Frame::glowing`'s, on the GPU), as `glowing.rs`'s `mark_outline` copies
/// vertices: the same ones, in `color` (`Vertex::color`) with no normal.
pub fn outline_since(&mut self, first: usize, color: u32) {
for i in first..self.jobs.len() {
let job = self.jobs[i];
self.outline_jobs.push(SkinJob {
dst: self.outline_vertices,
color,
mode: SkinJob::FLAT,
..job
});
self.outline_vertices += job.count;
}
}
/// The renderer took the new models.
@@ -138,7 +161,10 @@ impl SkinFrame {
/// Whether the renderer has anything to take.
pub fn pending(&self) -> bool {
self.reset || !self.new_model.is_empty() || !self.jobs.is_empty()
self.reset
|| !self.new_model.is_empty()
|| !self.jobs.is_empty()
|| !self.outline_jobs.is_empty()
}
}
@@ -293,7 +319,8 @@ impl EntitySkin {
}
/// Records the frame's jobs: they write `frame.vertices` vertices into
/// `out` from vertex `base` on. Call after the CPU vertices' upload.
/// `out` from vertex `base` on, and the outline jobs `frame.outline_vertices`
/// from vertex `outline_base` on. Call after the CPU vertices' upload.
#[allow(clippy::too_many_arguments)]
pub(super) fn dispatch(
&mut self,
@@ -303,26 +330,32 @@ impl EntitySkin {
frame: &SkinFrame,
out: &wgpu::Buffer,
base: u32,
outline_base: u32,
) {
if frame.jobs.is_empty() {
if frame.jobs.is_empty() && frame.outline_jobs.is_empty() {
return;
}
let Some(model) = &self.model_buffer else {
return;
};
// Whole rows of workgroups past `ROW` jobs: the padding jobs write nothing.
let count = frame.jobs.len() as u32;
let count = (frame.jobs.len() + frame.outline_jobs.len()) as u32;
let rows = count.div_ceil(ROW);
let padded = if rows > 1 {
(rows * ROW) as usize
} else {
frame.jobs.len()
count as usize
};
self.staged.clear();
self.staged.extend(frame.jobs.iter().map(|job| SkinJob {
dst: job.dst + base,
..*job
}));
let moved = |at: u32| {
move |job: &SkinJob| SkinJob {
dst: job.dst + at,
..*job
}
};
self.staged.extend(frame.jobs.iter().map(moved(base)));
self.staged
.extend(frame.outline_jobs.iter().map(moved(outline_base)));
self.staged.resize(padded, SkinJob::default());
self.parts.ensure(
device,
@@ -369,8 +402,22 @@ impl EntitySkin {
/// the same way: the reference its tests hold the shader and the game's walk
/// (`mobs::skin`) to. `model` is the whole store.
pub fn expand(model: &[SkinVertex], frame: &SkinFrame) -> Vec<Vertex> {
let mut out = vec![bytemuck::Zeroable::zeroed(); frame.vertices as usize];
for job in &frame.jobs {
expand_jobs(model, frame, &frame.jobs, frame.vertices)
}
/// [`expand`] of the frame's outline jobs: the silhouettes they write.
pub fn expand_outline(model: &[SkinVertex], frame: &SkinFrame) -> Vec<Vertex> {
expand_jobs(model, frame, &frame.outline_jobs, frame.outline_vertices)
}
fn expand_jobs(
model: &[SkinVertex],
frame: &SkinFrame,
jobs: &[SkinJob],
vertices: u32,
) -> Vec<Vertex> {
let mut out = vec![bytemuck::Zeroable::zeroed(); vertices as usize];
for job in jobs {
for k in 0..job.count {
let v = model[(job.first + k) as usize];
let part = &frame.parts[(job.part + v.part) as usize];
@@ -557,13 +604,30 @@ mod tests {
},
],
vertices: 120,
outline_jobs: Vec::new(),
outline_vertices: 0,
};
// A glowing entity's silhouette: the item job and the first job again,
// in its team colour with no normal.
let mut frame = frame;
frame.jobs.push(frame.jobs[2]);
frame.jobs.last_mut().expect("pushed").dst = 120;
frame.jobs.push(SkinJob {
dst: 144,
..frame.jobs[0]
});
frame.vertices = 192;
frame.outline_since(4, 0xff20_c0e0);
assert_eq!(frame.outline_vertices, 72);
let expected = expand(&model, &frame);
// The posed vertices land after 5 others, which they leave alone.
let outline = expand_outline(&model, &frame);
// The posed vertices land after 5 others, which they leave alone, and the
// silhouettes 3 past them.
let before = 5u32;
let outline_base = before + frame.vertices + 3;
let out = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: u64::from(before + frame.vertices) * 44,
size: u64::from(outline_base + frame.outline_vertices) * 44,
usage: EntitySkin::usage(true) | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
@@ -572,7 +636,15 @@ mod tests {
let mut uploads = Uploads::new(&device);
let mut encoder = device.create_command_encoder(&Default::default());
assert!(skin.register(&device, &mut uploads, &mut encoder, &frame));
skin.dispatch(&device, &mut uploads, &mut encoder, &frame, &out, before);
skin.dispatch(
&device,
&mut uploads,
&mut encoder,
&frame,
&out,
before,
outline_base,
);
let read = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: out.size(),
@@ -595,5 +667,24 @@ mod tests {
"vertex {i}: {got:?} != {want:?}"
);
}
let silhouettes = &got[(outline_base - before) as usize..];
assert_eq!(silhouettes.len(), outline.len());
for (i, (got, want)) in silhouettes.iter().zip(&outline).enumerate() {
assert_eq!(
bytemuck::bytes_of(got),
bytemuck::bytes_of(want),
"silhouette {i}: {got:?} != {want:?}"
);
}
// A silhouette is its draw's vertex with the colour and no normal
// (`mark_outline`).
for (copy, of) in outline.iter().zip(&expected[120..]) {
let marked = Vertex {
color: 0xff20_c0e0,
normal: [0; 4],
..*of
};
assert_eq!(bytemuck::bytes_of(copy), bytemuck::bytes_of(&marked));
}
}
}
+11 -1
View File
@@ -61,6 +61,7 @@ fn cpu_if_enabled() -> Option<Duration> {
/// Run `f`, recording its duration under `name` when spans are enabled.
#[inline]
pub fn time<T>(name: &'static str, f: impl FnOnce() -> T) -> T {
let _heap = horizon_threads::heap_tag(name);
if !recording_here() {
return f();
}
@@ -291,9 +292,15 @@ pub mod startup {
pub struct Step {
name: &'static str,
started: Instant,
traced: bool,
/// A heap-census build's tag for the step's allocations.
_heap: Option<horizon_threads::heap_census::Tag>,
}
impl Drop for Step {
fn drop(&mut self) {
if !self.traced {
return;
}
let end = Instant::now();
write(format_args!(
r#""ph":"X","name":"{}","ts":{},"dur":{}"#,
@@ -307,9 +314,12 @@ pub mod startup {
/// Starts a step named `name` (a literal: no quotes or backslashes).
#[inline]
pub fn step(name: &'static str) -> Option<Step> {
on().then(|| Step {
let traced = on();
(traced || horizon_threads::HEAP_CENSUS).then(|| Step {
name,
started: Instant::now(),
traced,
_heap: horizon_threads::heap_tag(name),
})
}
+15 -4
View File
@@ -774,12 +774,23 @@ impl<G: ChunkGenerator> Source for Jobs<G> {
};
(kind, crate::spans::thread_cpu().unwrap_or_default())
});
let _tag = horizon_threads::heap_tag(match &job.job {
Job::Generate(..) => "stream.generate",
Job::Decorate(..) => "stream.decorate",
Job::Finish(..) => "stream.finish",
Job::Light(..) => "stream.light",
Job::Mesh(..) => "stream.mesh",
_ => "stream.other",
});
let done = self.locals.with(
worker,
|| WorkerLocal {
worker: self.generator.worker(),
faces: None,
mesh_scratch: MeshScratch::default(),
|| {
let _tag = horizon_threads::heap_tag("stream.worker_state");
WorkerLocal {
worker: self.generator.worker(),
faces: None,
mesh_scratch: MeshScratch::default(),
}
},
|local| self.run_job(job.job, local),
);
+3
View File
@@ -117,6 +117,9 @@ rng-trace = ["horizon-core/rng-trace", "horizon-client-lib/rng-trace"]
# Rust's allocations go to the system heap instead of mimalloc, where heap profilers that replace
# malloc (heaptrack, valgrind's massif) see them. For profiling only: mimalloc is faster.
system-allocator = []
# mimalloc with a tag on each block: `horizon-ctl memory` lists the live heap by startup step and
# thread (`heap_by_tag_mb`). For measuring only: each block costs 16 bytes more.
alloc-census = ["horizon-threads/alloc-census"]
# Compatibility with earlier experiment commands: main already uses mimalloc.
windows-mimalloc = []
# Opt-in Windows heap experiment; see docs/windows-allocators.md.
@@ -753,6 +753,9 @@ fn print(out: &mut String, t: i32, i: usize, m: &Mob, player_down: bool, now: i6
if let Some((acts, mem)) = brain {
let _ = write!(out, "\tact {acts}\tmem {mem}");
}
if horizon_client::mobs::finalize::ageable(m.species()) {
let _ = write!(out, "\tage {}", m.extra.age);
}
if m.species() == Species::Villager {
let _ = write!(out, "\tvx {}", villager(m));
}
@@ -1026,6 +1029,7 @@ pub fn main() -> horizon_client::Result<()> {
m.rider = Some(horizon_client::mobs::equine::Rider::default());
}
"noai" => m.no_ai = true,
"agelocked" => m.extra.age_locked = true,
// Warden.finalizeSpawn's own part.
"digcooldown" | "emerge" => horizon_client::mobs::warden::finalize(
&mut m,
+3
View File
@@ -170,6 +170,8 @@ pub struct ArrowState {
/// The block it stuck in (`lastState`).
pub last_state: Option<BlockStateId>,
pub pickup: Pickup,
/// Its entity type is `SpectralArrow` (the type is its own, not the pickup item's).
pub spectral: bool,
/// `pickupItemStack`: what picking the arrow up gives.
pub pickup_item: crate::items::ItemStack,
/// `AbstractArrow.firedFromWeapon`: the crossbow or bow it was shot from.
@@ -227,6 +229,7 @@ impl ArrowState {
shake: 0,
last_state: None,
pickup: Pickup::Disallowed,
spectral: false,
pickup_item: crate::items::ItemStack::new("minecraft:arrow", 1),
fired_from_weapon: None,
no_physics: false,
@@ -221,7 +221,7 @@ pub const COMMANDS: &[Cmd] = &[
"Replies protocol (MAJOR.MINOR.PATCH of the control protocol, docs/api/stability.md), release (the game's version, the string MCP serverInfo carries too) and features (the command groups the game answers, so a script can test for one before calling it).",
["horizon-ctl hello"], &[], mcp: true, local: false),
cmd!("memory", Session, "memory", "what holds the game's memory: resident set, allocator, client and server columns by part, renderer, GPU memory by resource",
"Columns count blocks, biomes, light, heightmaps and records, each shared section once; server_columns are the single-player server thread's own levels. GPU memory by label needs Vulkan or DirectX 12. What the census doesn't reach is the resident set less its parts.", ["horizon-ctl memory"], &[], mcp: true, local: false),
"Columns count blocks, biomes, light, heightmaps and records, each shared section once; server_columns are the single-player server thread's own levels. GPU memory by label needs Vulkan or DirectX 12. What the census doesn't reach is the resident set less its parts. A build with the alloc-census feature adds heap_by_tag_mb: the live heap by startup step, timed span, chunk job and thread.", ["horizon-ctl memory"], &[], mcp: true, local: false),
cmd!("worlds", Session, "worlds", "the saved worlds, newest first: folder, name, mode, last played, version",
"folder is what open, world-rename, world-backup and world-delete take.", ["horizon-ctl worlds"], &[], mcp: true, local: false),
cmd!("world-rename", Session, "world-rename FOLDER NAME", "rename a world, as Edit World does (its folder stays)",
+62 -2
View File
@@ -1483,6 +1483,7 @@ impl ArmorStand {
facts: &BlockFacts,
in_water: bool,
push: &mut dyn FnMut(Aabb, &mut Vec3),
border: Option<&BorderGeometry>,
) {
self.previous = self.position;
if self.marker {
@@ -1519,7 +1520,20 @@ impl ArmorStand {
1.0
};
let mut velocity = wanted;
if let Ok(moved) = scene.resolve(self.bounds(), wanted, 0.0, self.on_ground) {
// Entity.collide: the world border's walls stop the stand too.
let moved = SceneCollisions {
world: scene.world,
binding: scene.binding,
subject: scene.subject,
}
.resolve_in_border(
self.bounds(),
wanted,
0.0,
self.on_ground,
border.map(|b| (b, [self.position.x, self.position.z])),
);
if let Ok(moved) = moved {
self.position = Vec3::new(
self.position.x + moved.x,
self.position.y + moved.y,
@@ -2699,7 +2713,53 @@ mod armor_stand_motion_tests {
binding: collision,
subject: None,
};
stand.tick(&mut scene, facts, false, &mut |_, _| {});
stand.tick(&mut scene, facts, false, &mut |_, _| {}, None);
}
/// `Entity.collectColliders` (#7080): the world border's wall stops an
/// armor stand as it stops the player. A border of size 10 around
/// (20, 20) has its east wall at x = 25; the stand starts at x = 24 on the
/// test scene's flat ground, moving 1.5 east.
#[test]
fn the_world_border_stops_an_armor_stand() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../.local/horizon-client")
.join(horizon_core::VANILLA_VERSION);
if !root.join("movement-properties.nbt").exists() {
return;
}
let assets = crate::assets::Assets::load(&root).unwrap();
let render = assets.reload(&root).unwrap();
let facts = BlockFacts::load(&root, assets.registry).unwrap();
let collision = crate::collisions::load(&root, assets.registry).unwrap();
let world = World::test_scene(&assets, &render, 3).unwrap();
let border = BorderGeometry::new([20.0, 20.0], 10.0, 29_999_984).unwrap();
for bordered in [false, true] {
let mut stand = ArmorStand::new(1, Vec3::new(24.0, 4.0, 20.5), 0.0);
stand.on_ground = true;
stand.velocity = Vec3::new(1.5, 0.0, 0.0);
let mut scene = SceneCollisions {
world: &world,
binding: &collision,
subject: None,
};
stand.tick(
&mut scene,
&facts,
false,
&mut |_, _| {},
bordered.then_some(&border),
);
let east = stand.bounds().max_position().x;
if bordered {
assert!(
(east - 25.0).abs() < 1.0e-7,
"stops on the wall, got {east}"
);
} else {
assert!(east > 25.0, "no border: goes on, got {east}");
}
}
}
#[test]
+4 -4
View File
@@ -380,7 +380,7 @@ mod tests {
binding: &collision,
subject: None,
};
still.tick(&mut scene, &facts, in_water, &mut |_, _| {});
still.tick(&mut scene, &facts, in_water, &mut |_, _| {}, None);
assert_eq!(still.position.x, at.x);
assert_eq!(still.velocity.x, 0.0);
@@ -394,7 +394,7 @@ mod tests {
binding: &collision,
subject: None,
};
marker.tick(&mut scene, &facts, in_water, &mut |_, _| {});
marker.tick(&mut scene, &facts, in_water, &mut |_, _| {}, None);
assert_eq!(marker.position, at);
assert_eq!(marker.velocity, Vec3::ZERO);
@@ -406,7 +406,7 @@ mod tests {
binding: &collision,
subject: None,
};
carried.tick(&mut scene, &facts, in_water, &mut |_, _| {});
carried.tick(&mut scene, &facts, in_water, &mut |_, _| {}, None);
assert!((carried.position.x - (at.x + 0.014)).abs() < 1e-12);
assert!((carried.velocity.x - 0.014 * f64::from(0.8f32)).abs() < 1e-12);
for _ in 0..20 {
@@ -416,7 +416,7 @@ mod tests {
binding: &collision,
subject: None,
};
carried.tick(&mut scene, &facts, in_water, &mut |_, _| {});
carried.tick(&mut scene, &facts, in_water, &mut |_, _| {}, None);
}
assert!(carried.position.x > at.x + 0.5);
}
+171 -7
View File
@@ -27,7 +27,7 @@ fn string(s: &str) -> Tag {
}
/// Entity type ID (`id`).
pub fn kind(tag: &Tag) -> Option<String> {
text(tag.get("id"))
horizon_client_core::identifier::normalize_id(&text(tag.get("id"))?)
}
pub fn position(tag: &Tag) -> Option<Vec3> {
read_doubles::<3>(tag.get("Pos")).map(|p| Vec3::new(p[0], p[1], p[2]))
@@ -2238,7 +2238,12 @@ pub fn arrow_to_nbt(
return None;
};
let base = Base {
id: "minecraft:arrow",
// A spectral arrow is its own entity type.
id: if a.spectral || &*a.pickup_item.item == "minecraft:spectral_arrow" {
"minecraft:spectral_arrow"
} else {
"minecraft:arrow"
},
uuid,
position: p.position,
motion: p.velocity,
@@ -2298,10 +2303,17 @@ pub fn arrow_from_nbt(
let position = position(tag)?;
let owner = uuid_from_tag(tag.get("Owner"));
let from_player = player.is_some() && owner == player;
// `getDefaultPickupItem`: a `SpectralArrow` is picked up as its own item.
let spectral = kind(tag).as_deref() == Some("minecraft:spectral_arrow");
let default_item = if spectral {
"minecraft:spectral_arrow"
} else {
"minecraft:arrow"
};
let item = tag
.get("item")
.and_then(item_from_nbt)
.unwrap_or_else(|| crate::items::ItemStack::new("minecraft:arrow", 1));
.unwrap_or_else(|| crate::items::ItemStack::new(default_item, 1));
let kind = ProjectileKind::Arrow {
base_damage: float(tag.get("damage")).unwrap_or(2.0),
critical: flag(tag, "crit"),
@@ -2322,6 +2334,7 @@ pub fn arrow_from_nbt(
a.has_been_shot = flag(tag, "HasBeenShot");
a.fire_ticks = number(tag.get("Fire")).unwrap_or(0) as i16 as i32;
a.fall_distance = float(tag.get("fall_distance")).unwrap_or(0.0);
a.spectral = spectral;
a.pickup_item = item;
a.fired_from_weapon = tag.get("weapon").and_then(item_from_nbt);
p.stuck = a.in_ground;
@@ -2403,12 +2416,13 @@ fn put_projectile(tag: &mut Tag, p: &Projectile, owner: Option<[i32; 4]>) {
/// The thrown item projectiles Horizon saves: each entity type's id is also
/// the item it saves when none was loaded (`getDefaultItem`).
const THROWABLE_ITEMS: [&str; 5] = [
const THROWABLE_ITEMS: [&str; 6] = [
"minecraft:snowball",
"minecraft:egg",
"minecraft:ender_pearl",
"minecraft:splash_potion",
"minecraft:lingering_potion",
"minecraft:experience_bottle",
];
/// The item a throwable flies as: its stack (a potion's), the mod type's
/// item, or the item its entity type is named after.
@@ -2432,6 +2446,7 @@ pub fn throwable_kind(id: &str) -> Option<ProjectileKind> {
"minecraft:egg" => Some(ProjectileKind::Egg),
"minecraft:ender_pearl" => Some(ProjectileKind::EnderPearl),
// The entity type is the id; the `Item` read later may be another potion.
"minecraft:experience_bottle" => Some(ProjectileKind::ExperienceBottle),
_ => Some(ProjectileKind::Potion(
Box::new(crate::items::ItemStack::new(id, 1)),
id == "minecraft:lingering_potion",
@@ -2454,12 +2469,17 @@ pub fn saved_with_chunk(p: &Projectile) -> bool {
| ProjectileKind::Egg
| ProjectileKind::EnderPearl
| ProjectileKind::Potion(..)
| ProjectileKind::ExperienceBottle
| ProjectileKind::LlamaSpit
| ProjectileKind::ShulkerBullet { target: None, .. }
| ProjectileKind::Thrown(_) => true,
ProjectileKind::Arrow { .. } => p.arrow.is_some(),
// Not written yet: tridents save from their own list.
// Not written yet: tridents save from their own list, and a bullet
// with a target would lose it.
ProjectileKind::Trident
| ProjectileKind::LlamaSpit
| ProjectileKind::ShulkerBullet { .. }
| ProjectileKind::ShulkerBullet {
target: Some(_), ..
}
| ProjectileKind::WitherSkull { .. } => false,
}
}
@@ -2522,6 +2542,59 @@ pub fn throwable_from_nbt(tag: &Tag, player: Option<[i32; 4]>) -> Option<Project
Some(p)
}
/// `LlamaSpit` and `ShulkerBullet` as `/summon` or a chunk loads them:
/// `Projectile`'s `Owner`, `LeftOwner`, `NoGravity` and the motion. A bullet
/// loaded without a `Target` has none, so it falls (`ShulkerBullet.tick`).
pub fn spit_or_bullet_from_nbt(tag: &Tag, player: Option<[i32; 4]>) -> Option<Projectile> {
let kind = match kind(tag)?.as_str() {
"minecraft:llama_spit" => ProjectileKind::LlamaSpit,
"minecraft:shulker_bullet" => ProjectileKind::ShulkerBullet {
target: None,
dir: None,
steps: 0,
delta: [0.0; 3],
seed: 0,
},
_ => return None,
};
let owner = owner_uuid(tag);
let from_player = player.is_some() && owner == player;
let mut p = Projectile::new(kind, position(tag)?, from_player.then_some(Who::Player));
p.velocity = motion(tag);
[p.yaw, p.pitch] = rotation(tag);
p.left_owner = flag(tag, "LeftOwner");
p.owner_uuid = owner;
p.no_gravity = flag(tag, "NoGravity");
Some(p)
}
/// The save of [`spit_or_bullet_from_nbt`]'s kinds: `Projectile`'s keys.
pub fn spit_or_bullet_to_nbt(
p: &Projectile,
uuid: [i32; 4],
owner: Option<[i32; 4]>,
raw: Option<&Tag>,
) -> Option<Tag> {
if !matches!(
p.kind,
ProjectileKind::LlamaSpit | ProjectileKind::ShulkerBullet { target: None, .. }
) {
return None;
}
let base = Base {
id: p.kind.id(),
uuid,
position: p.position,
motion: p.velocity,
rotation: [p.yaw, p.pitch],
on_ground: false,
fall_distance: 0.0,
};
let mut tag = base.tag(raw);
put_projectile(&mut tag, p, owner);
Some(tag)
}
/// `Projectile`'s `Owner`.
pub fn owner_uuid(tag: &Tag) -> Option<[i32; 4]> {
uuid_from_tag(tag.get("Owner"))
@@ -3311,6 +3384,31 @@ mod tests {
use super::*;
use crate::items::ItemStack;
#[test]
fn mob_loading_accepts_default_namespace_and_preserves_custom_namespaces() {
let cow = crate::mobs::sensor_scan_tests::kind();
let types = [
("minecraft:cow".into(), cow.clone()),
("test:cow".into(), cow),
]
.into();
let pos = || {
Tag::List(
[4.5_f64, 220.0, 6.5]
.map(|v| Tag::Double(v.to_bits()))
.to_vec(),
)
};
for id in ["cow", ":cow", "minecraft:cow", "test:cow"] {
let tag = Tag::compound([("id", string(id)), ("Pos", pos()), ("NoAI", Tag::Byte(1))]);
let mob = mob_from_nbt(&tag, &types, 7, None).expect(id);
assert_eq!(mob.position, Vec3::new(4.5, 220.0, 6.5));
assert!(mob.no_ai);
}
let unknown = Tag::compound([("id", string("other:cow")), ("Pos", pos())]);
assert!(mob_from_nbt(&unknown, &types, 8, None).is_none());
}
/// A saved rocket and trident come back with their own Life, LifeTime,
/// flags and position (the keys vanilla's addAdditionalSaveData writes).
#[test]
@@ -5287,6 +5385,72 @@ mod mod_thrown_nbt_tests {
// a type no mod declares now is not a thrown kind.
assert!(throwable_kind("gone:bolt").is_none());
}
fn summoned(id: &str, extra: &[(&str, Tag)]) -> Tag {
let mut c = vec![
("id", Tag::String(id.into())),
("Pos", doubles(&[0.5, 110.0, 0.5])),
("Motion", doubles(&[0.0, 0.0, 0.0])),
];
c.extend(extra.iter().cloned());
Tag::compound(c)
}
/// Four summoned kinds had no loader and stayed inert (a spectral arrow,
/// a shulker bullet, a llama's spit and an experience bottle never fell).
#[test]
fn summoned_projectiles_of_every_kind_load_and_honour_no_gravity() {
use crate::entity_rules::EntityRules;
let flag = ("NoGravity", Tag::Byte(1));
let arrow = arrow_from_nbt(&summoned("minecraft:spectral_arrow", &[]), None, |_| None)
.unwrap()
.0;
assert!(arrow.is_spectral_arrow() && !arrow.no_gravity);
let held = arrow_from_nbt(
&summoned("minecraft:spectral_arrow", std::slice::from_ref(&flag)),
None,
|_| None,
)
.unwrap()
.0;
assert!(held.no_gravity);
// ...and it is saved as the entity type it is.
let saved = arrow_to_nbt(&arrow, [1; 4], None, None, None).unwrap();
assert_eq!(kind(&saved).as_deref(), Some("minecraft:spectral_arrow"));
let plain = arrow_from_nbt(&summoned("minecraft:arrow", &[]), None, |_| None)
.unwrap()
.0;
let saved = arrow_to_nbt(&plain, [1; 4], None, None, None).unwrap();
assert_eq!(kind(&saved).as_deref(), Some("minecraft:arrow"));
for id in ["minecraft:llama_spit", "minecraft:shulker_bullet"] {
let p = spit_or_bullet_from_nbt(&summoned(id, &[]), None).unwrap();
assert_eq!(p.kind.id(), id);
assert!(!p.no_gravity && p.position == Vec3::new(0.5, 110.0, 0.5));
let held =
spit_or_bullet_from_nbt(&summoned(id, std::slice::from_ref(&flag)), None).unwrap();
assert!(held.no_gravity);
}
assert!(spit_or_bullet_from_nbt(&summoned("minecraft:arrow", &[]), None).is_none());
// a spit and a target-less bullet are saved and selectable (`/data`, `@e`).
let spit = spit_or_bullet_from_nbt(&summoned("minecraft:llama_spit", &[]), None).unwrap();
assert!(saved_with_chunk(&spit));
let saved = spit_or_bullet_to_nbt(&spit, [1; 4], None, None).unwrap();
assert_eq!(kind(&saved).as_deref(), Some("minecraft:llama_spit"));
// A summoned experience bottle flies as the bottle item, with a bottle's gravity.
let bottle =
throwable_from_nbt(&summoned("minecraft:experience_bottle", &[]), None).unwrap();
assert_eq!(bottle.kind.id(), "minecraft:experience_bottle");
assert!(!bottle.no_gravity && (bottle.get_gravity() - 0.07).abs() < 1e-12);
let held =
throwable_from_nbt(&summoned("minecraft:experience_bottle", &[flag]), None).unwrap();
assert!(held.no_gravity && held.get_gravity() == 0.0);
let saved = throwable_to_nbt(&bottle, [1; 4], None, None).unwrap();
assert_eq!(kind(&saved).as_deref(), Some("minecraft:experience_bottle"));
let item = saved.get("Item").and_then(item_from_nbt).unwrap();
assert_eq!(&*item.item, "minecraft:experience_bottle");
}
#[test]
fn no_gravity_survives_save_and_load_for_orbs() {
// Entity.saveWithoutId / load: NoGravity round-trips on a kind that
+12 -8
View File
@@ -3782,13 +3782,13 @@ impl Game<'_> {
/// Item entity `i` leaves the level (`Entity.discard`); mods hear it.
pub(super) fn remove_dropped(&mut self, i: usize) -> ItemEntity {
let e = self.dropped.remove(i);
self.native_entities_left("minecraft:item", &[e.id]);
self.native_entity_left(e.id);
e
}
/// Experience orb `i` leaves the level (`Entity.discard`); mods hear it.
pub(super) fn remove_orb(&mut self, i: usize) -> ExperienceOrb {
let o = self.orbs.remove(i);
self.native_entities_left("minecraft:experience_orb", &[o.id]);
self.native_entity_left(o.id);
o
}
/// The item entity `spawn_item` just added, unless a native mod kept it
@@ -7325,6 +7325,9 @@ impl Game<'_> {
if running {
horizon_client::spans::time("tick_lan_packets", || self.tick_lan_packets());
horizon_client::spans::time("tick_blocks", || self.tick_blocks())?;
// What the blocks spawned (a dispensed minecart, a lit TNT, a
// placed boat) joins before the entities tick.
self.native_joins_flush(false, 0);
}
if running {
horizon_client::spans::time("tick_paintings", || self.tick_paintings());
@@ -7356,34 +7359,35 @@ impl Game<'_> {
self.handle_entity_portals();
self.handle_end_gateway_portals();
});
let before = self.dropped_ids();
horizon_client::spans::time("tick_items", || self.tick_items());
if self.remote.is_none() {
self.hoppers_catch_items();
}
// Merged, burnt, despawned or picked up.
self.native_items_left_since(&before);
self.native_leaves_flush();
self.native_joins_flush(false, 0);
horizon_client::spans::time("touch_arrows", || self.touch_arrows());
}
horizon_client::spans::time("spawn_loaded_entities", || self.spawn_loaded_entities());
// The ride the player's file names, once the vehicle is in the world.
self.restore_root_vehicle();
if running {
let mobs_before = self.mob_ids();
horizon_client::spans::time("tick_mobs", || self.tick_mobs())?;
// Dead, despawned or gone into a hive.
self.native_mobs_left_since(&mobs_before);
self.native_leaves_flush();
horizon_client::spans::time("recheck_golem_openers", || self.recheck_golem_openers());
self.tick_mod_container_viewers();
let before = self.orb_ids();
horizon_client::spans::time("tick_experience", || self.tick_experience());
// Merged, despawned or picked up.
self.native_orbs_left_since(&before);
self.native_leaves_flush();
horizon_client::spans::time("entity_portals", || {
self.find_entity_portals();
self.resolve_arrivals();
});
}
// The rest of the tick's spawns join, and what left is told of.
self.native_joins_flush(false, 0);
self.native_leaves_flush();
horizon_client::spans::time("tick_lan", || self.tick_lan(false));
self.tick_friends();
horizon_client::spans::time("tick_hand", || self.tick_hand());
+17 -11
View File
@@ -810,17 +810,23 @@ impl Game<'_> {
subject: None,
};
let mut tops: Vec<BlockPos> = Vec::new();
a.tick(&mut scene, facts, in_water, &mut |bounds, velocity| {
horizon_client::motion::bubble_column_push_over(
world,
registry,
facts,
collision,
bounds,
velocity,
|pos| tops.push(pos),
);
});
a.tick(
&mut scene,
facts,
in_water,
&mut |bounds, velocity| {
horizon_client::motion::bubble_column_push_over(
world,
registry,
facts,
collision,
bounds,
velocity,
|pos| tops.push(pos),
);
},
Some(self.border.geometry()),
);
for pos in tops {
self.bubble_column_particles(pos);
}
+1 -2
View File
@@ -2105,9 +2105,8 @@ impl Game<'_> {
Target::Spawner(id) => self.ominous_spawners.retain(|s| s.id != id),
}
// The orbs marked for removal are gone (Entity.kill removes them).
let before = self.orb_ids();
self.orbs.retain(|o| o.age < u32::MAX / 2);
self.native_orbs_left_since(&before);
self.native_leaves_flush();
}
fn cmd_kill(&mut self, ctx: &Context) -> R<i32> {
let targets = if ctx.get("targets").is_some() {
@@ -173,7 +173,8 @@ impl Game<'_> {
let raw = self.persist.raw.get(&id);
entity_nbt::arrow_to_nbt(p, random_uuid(), owner, in_block, raw)
.or_else(|| entity_nbt::fireball_to_nbt(p, random_uuid(), owner, raw))
.or_else(|| entity_nbt::throwable_to_nbt(p, random_uuid(), owner, raw))?
.or_else(|| entity_nbt::throwable_to_nbt(p, random_uuid(), owner, raw))
.or_else(|| entity_nbt::spit_or_bullet_to_nbt(p, random_uuid(), owner, raw))?
}
Target::Crystal(id) => {
let c = self.crystals.iter().find(|c| c.id == id)?;
@@ -879,7 +880,9 @@ impl Game<'_> {
let target = self.spawn_entity_from_nbt(tag);
self.native_mobs_joined(from, false);
// A mod kept the entity out: tryAddFreshEntityWithPassengers is false.
if target.is_none() && self.persist.joins_canceled != canceled {
if self.persist.joins_canceled != canceled
&& target.is_none_or(|t| self.target_position(t).is_none())
{
return Err(err("commands.summon.failed.uuid", &[]));
}
Ok(target)
+24 -8
View File
@@ -1053,9 +1053,9 @@ impl Game<'_> {
for e in &mut events[from..] {
if let MobEvent::Projectile(p) = e
&& let ProjectileKind::ShulkerBullet { target, .. } = &mut p.kind
&& *target == Who::Player
&& *target == Some(Who::Player)
{
*target = Who::Mob(id);
*target = Some(Who::Mob(id));
}
}
}
@@ -4777,7 +4777,16 @@ impl Game<'_> {
// flies as: the splash happens where the hit was, a lingering
// potion's cloud sits at the mob it hit (ThrownLingeringPotion),
// and the potion that lands names the hurt it deals.
(ProjectileKind::Potion(stack, _), hit) => {
(kind @ (ProjectileKind::Potion(..) | ProjectileKind::ExperienceBottle), hit) => {
// A bottle flies as the bottle item (`getDefaultItem`).
let bottle;
let stack = match kind {
ProjectileKind::Potion(stack, _) => stack,
_ => {
bottle = Box::new(ItemStack::new("minecraft:experience_bottle", 1));
&bottle
}
};
let block = match hit {
Hit::Block { pos, face, .. } => Some((
pos,
@@ -6492,12 +6501,15 @@ impl Game<'_> {
Vec::new()
};
let body = out.len();
// The GPU path: an ordinary mob, no outline, no layers the CPU path changes after
// drawing them (a player's, the HDR tiers below, the sulfur cube's shell).
// The GPU path: an ordinary mob, no layers the CPU path changes after drawing them
// (a player's, the HDR tiers below, the sulfur cube's shell). A glowing mob's
// silhouette is posed too (`SkinFrame::outline_since`), unless it is invisible:
// then only the outline draws.
let silhouette_on_gpu = outline.is_none() || !mob.invisible;
let posable = skinning
&& !is_player
&& !layers_only
&& outline.is_none()
&& silhouette_on_gpu
&& armor.is_empty()
&& !matches!(
mob.species(),
@@ -6576,14 +6588,18 @@ impl Game<'_> {
}
// CustomHeadLayer (a pumpkin, skull or head item) included.
if layers {
// Held items join the GPU path's draws unless an outline reads their vertices.
let skin = (skinning && outline.is_none()).then_some(&mut skinned);
// Held items join the GPU path's draws, their silhouette too.
let skin = (skinning && silhouette_on_gpu).then_some(&mut skinned);
self.mob_block_geometry(mob, camera, alpha, packed, skin, out);
// `native_mob_layer`: a native mod's layer, over the vanilla ones.
self.mod_mob_layer_geometry(mob, who, alpha as f32, camera, packed, out);
}
if let Some(rgb) = outline {
self.mark_outline(&out[entity_start..], rgb);
if let Some(((first, _, _), _)) = skinned {
let color = horizon_client::mobs::render::abgr(rgb);
self.skin_frame.borrow_mut().outline_since(first, color);
}
if mob.invisible {
out.truncate(entity_start);
continue;
@@ -1100,6 +1100,7 @@ impl Game<'_> {
| K::EnderPearl
| K::LlamaSpit
| K::Potion { .. }
| K::ExperienceBottle
| K::Thrown(_) => (0.25, 0.2125),
K::SmallFireball | K::ShulkerBullet { .. } | K::WitherSkull { .. } => {
(0.3125, 0.265_625)
+1 -1
View File
@@ -241,7 +241,7 @@ mod tests {
// A shulker bullet takes a blow, with no pick radius round its box.
let bullet = Projectile::new(
ProjectileKind::ShulkerBullet {
target: Who::Player,
target: Some(Who::Player),
dir: None,
steps: 0,
delta: [0.0; 3],
+3
View File
@@ -1456,6 +1456,7 @@ impl Game<'_> {
// A gateway keeps the item in its level (teleportSameDimension
// moves it): it doesn't leave, so no native_entity_leave_level.
self.dropped.remove(i);
self.native_leave_skip(id);
self.detach_entity(id);
tag
} else if let Some(i) = self.mobs.iter().position(|m| m.id == id) {
@@ -1479,11 +1480,13 @@ impl Game<'_> {
}
self.mobs.retain(|m| !gone.contains(&m.id));
for g in gone {
self.native_leave_skip(g);
self.detach_entity(g);
}
tag
} else if let Some(i) = self.creatures.projectiles.iter().position(|p| p.id == id) {
let pearl = Box::new(self.creatures.projectiles.remove(i));
self.native_leave_skip(id);
let (job, target) = self.gateway_job([gateway.x, gateway.y, gateway.z]);
let arrival = GatewayArrival {
traveller: Arriving::Pearl(pearl),
+1 -3
View File
@@ -623,7 +623,6 @@ impl Game<'_> {
items_ignore: items_ignore_explosion(self.ui.rules.bool("mob_griefing"), interaction),
spared: exploding.filter(|_| self.combat.explosion_source_named),
};
let (items_before, orbs_before) = (self.dropped_ids(), self.orb_ids());
let (items_len, orbs_len) = (self.dropped.len(), self.orbs.len());
let (mut items_kept, mut orbs_kept) = (0, 0);
let (mut boats_gone, mut carts_gone) = (0, 0);
@@ -723,8 +722,7 @@ impl Game<'_> {
}
self.dropped.drain(items_kept..items_len);
self.orbs.drain(orbs_kept..orbs_len);
self.native_items_left_since(&items_before);
self.native_orbs_left_since(&orbs_before);
self.native_leaves_flush();
// Frames, paintings, cushions and armour stands last: vanilla lists
// the entities in reach before hurting any, so the items they drop
// here are neither destroyed nor pushed by the same blast.
+6 -2
View File
@@ -618,8 +618,8 @@ impl Game<'_> {
seed,
);
let glowing = self.entity_glowing(item.id);
// Posed on the GPU when every layer can be (the outline reads the CPU's vertices).
let skinned = (skinning && !glowing)
// Posed on the GPU when every layer can be, its silhouette too when it glows.
let skinned = skinning
.then(|| self.submit_item_skinned(&state, &copies, vertex_light(packed), lights))
.flatten();
if skinned.is_none() {
@@ -630,6 +630,10 @@ impl Game<'_> {
if glowing {
let rgb = super::glowing::team_color(self.mob_team(item.id));
self.mark_outline(&out[entity_start..], rgb);
if let Some(((first, _, _), _)) = skinned {
let color = horizon_client::mobs::render::abgr(rgb);
self.skin_frame.borrow_mut().outline_since(first, color);
}
} else {
self.cull_skinned(out, entity_start, skinned);
}
+2 -4
View File
@@ -635,7 +635,6 @@ impl Game<'_> {
// over the 40 a vehicle takes) destroys them. Lightning is no fire
// source, so a TNT cart does not prime. `ExperienceOrb.hurtServer`:
// 5 damage off 5 health. Last first, since each removes its entry.
let orbs_before = self.orb_ids();
let mut left = self.thunder_count(strike, ThunderHit::Vehicle)
+ self.thunder_count(strike, ThunderHit::Orb);
for h in self.entity_handles_rev() {
@@ -659,7 +658,7 @@ impl Game<'_> {
_ => {}
}
}
self.native_orbs_left_since(&orbs_before);
self.native_leaves_flush();
// Every player in the box, the server's players too (0.6 x 1.8).
let strike_players = move |g: &mut Self| {
if let Some(p) = g.player.as_ref().map(|p| p.position)
@@ -679,7 +678,6 @@ impl Game<'_> {
strike_players(self);
self.each_guest_hit(strike_players);
// Dropped items burn up (ItemEntity health 5), save fire-resistant ones.
let before = self.dropped_ids();
let mut left = self.thunder_count(strike, ThunderHit::Item);
for h in self.entity_handles_rev() {
if left == 0 {
@@ -696,7 +694,7 @@ impl Game<'_> {
self.dropped.remove(i);
}
}
self.native_items_left_since(&before);
self.native_leaves_flush();
struck
}
/// `LightningBolt.shouldRenderAtSqrDistance`: a bolt is drawn within 64
+9
View File
@@ -141,6 +141,15 @@ impl Game<'_> {
json!({ "active": active, "cached": cached })
}),
"renderer": renderer,
// A build with the `alloc-census` feature: the live heap by the
// startup step, span or pool job that allocated it, else its thread.
"heap_by_tag_mb": horizon_threads::HEAP_CENSUS.then(|| {
horizon_threads::heap_census::census()
.into_iter()
.take(60)
.map(|(tag, bytes)| json!([tag, mb(bytes.max(0) as u64)]))
.collect::<Vec<_>>()
}),
})
}
}
+177 -21
View File
@@ -258,9 +258,38 @@ impl Game<'_> {
}
}
/// Entities joining the level (`native_entity_join_level`), for every kind in
/// the entity walk. Kept out of `native_hooks.rs`, whose lines may not hold macros.
/// The entities the walk does not hold: Horizon keeps these in lists of their own.
#[derive(Clone, Copy)]
enum Apart {
Crystal(usize),
Cushion(usize),
Painting(usize),
Frame(usize),
Dragon,
}
/// One entity a mod is told of: in the walk, or apart from it.
#[derive(Clone, Copy)]
enum Slot {
Walk(super::entity::EntityHandle),
Apart(Apart),
}
/// Entities joining and leaving the level (`native_entity_join_level`,
/// `native_entity_leave_level`), for every kind in the entity walk and the
/// five kept apart from it. Kept out of `native_hooks.rs`, whose lines may not hold macros.
impl Game<'_> {
/// Whether an entity may be waiting to be told of: one with an id above
/// the last announced, or one of the kinds that take their id late and
/// have none. Reads a counter and five lists' ids; no walk.
fn joins_pending(&self) -> bool {
self.next_entity > self.persist.joined_mark
|| self.tnts.iter().any(|t| t.id == 0)
|| self.falling.iter().any(|f| f.id == 0)
|| self.creatures.projectiles.iter().any(|p| p.id == 0)
|| self.tridents.iter().any(|t| t.id == 0)
|| self.rockets.iter().any(|r| r.id == 0)
}
/// Tells `native_entity_join_level` once for every entity added since the
/// last call, of any kind, in the order they were created (ids count up
/// from one counter). A mod that cancels takes the entity back out here,
@@ -268,11 +297,10 @@ impl Game<'_> {
/// `loaded_from_disk` is for the entities that came with a chunk. Returns
/// whether `watch` (an id, or 0) is still in the level. Walks the lists
/// with static dispatch and allocates nothing but the kind name of an
/// entity it announces; nothing runs when no mod is loaded or on a client
/// of a server (which tells nothing).
/// entity it announces; nothing runs when no mod is loaded, on a client
/// of a server (which tells nothing), or when nothing is new.
pub(super) fn native_joins_flush(&mut self, loaded_from_disk: bool, watch: u32) -> bool {
use super::entity::Entity;
if self.remote.is_some() || crate::mod_api::mods().is_empty() {
if self.remote.is_some() || crate::mod_api::mods().is_empty() || !self.joins_pending() {
return true;
}
for t in &mut self.tnts {
@@ -291,11 +319,9 @@ impl Game<'_> {
super::lan_entities::assign_entity_id(&mut self.next_entity, &mut r.id);
}
let mut kept = true;
while let Some((handle, id)) = self.next_unannounced() {
while let Some((slot, id)) = self.next_unannounced() {
self.persist.joined_mark = id;
let (kind, at) = super::entity::with_entity!(self, handle, e => {
(e.native_type(), e.position())
});
let (kind, at) = self.slot_kind_and_position(slot);
let mut canceled = false;
let dimension = self.dim.id();
self.native_entity_join_level(
@@ -311,32 +337,162 @@ impl Game<'_> {
self.forget_entity(id);
// Handles are indexes: the entity is taken out before any
// other change to its list.
self.discard_entity(handle);
self.discard_slot(slot);
kept &= id != watch;
} else {
self.persist.live.push((id, kind.into_owned(), true));
}
}
kept
}
/// The entity with the lowest id above the last one announced, by handle.
fn next_unannounced(&self) -> Option<(super::entity::EntityHandle, u32)> {
/// The entity with the lowest id above the last one announced.
fn next_unannounced(&self) -> Option<(Slot, u32)> {
use super::entity::Entity;
let mark = self.persist.joined_mark;
let mut best: Option<(super::entity::EntityHandle, u32)> = None;
for handle in self.entity_handles() {
let id = {
use super::entity::Entity;
super::entity::with_entity!(self, handle, e => e.id())
};
let mut best: Option<(Slot, u32)> = None;
let mut offer = |slot: Slot, id: u32| {
if id > mark && best.is_none_or(|(_, b)| id < b) {
best = Some((handle, id));
best = Some((slot, id));
}
};
for handle in self.entity_handles() {
offer(
Slot::Walk(handle),
super::entity::with_entity!(self, handle, e => e.id()),
);
}
for (i, c) in self.crystals.iter().enumerate() {
offer(Slot::Apart(Apart::Crystal(i)), c.id);
}
for (i, c) in self.cushions.iter().enumerate() {
offer(Slot::Apart(Apart::Cushion(i)), c.id);
}
for (i, p) in self.paintings.iter().enumerate() {
offer(Slot::Apart(Apart::Painting(i)), p.id);
}
for (i, f) in self.item_frames.iter().enumerate() {
offer(Slot::Apart(Apart::Frame(i)), f.id);
}
if let Some(d) = &self.dragon {
offer(Slot::Apart(Apart::Dragon), d.id);
}
best
}
/// The type id and position a mod is told of.
fn slot_kind_and_position(&self, slot: Slot) -> (std::borrow::Cow<'static, str>, Vec3) {
use super::entity::Entity;
let block = |p: [i32; 3]| {
Vec3::new(
f64::from(p[0]) + 0.5,
f64::from(p[1]) + 0.5,
f64::from(p[2]) + 0.5,
)
};
match slot {
Slot::Walk(h) => {
super::entity::with_entity!(self, h, e => (e.native_type(), e.position()))
}
Slot::Apart(Apart::Crystal(i)) => {
("minecraft:end_crystal".into(), self.crystals[i].position)
}
Slot::Apart(Apart::Cushion(i)) => {
("minecraft:cushion".into(), self.cushions[i].position)
}
Slot::Apart(Apart::Painting(i)) => {
("minecraft:painting".into(), block(self.paintings[i].pos))
}
Slot::Apart(Apart::Frame(i)) => {
let f = &self.item_frames[i];
let kind = if f.glow {
"minecraft:glow_item_frame"
} else {
"minecraft:item_frame"
};
(kind.into(), block(f.pos))
}
Slot::Apart(Apart::Dragon) => (
"minecraft:ender_dragon".into(),
self.dragon.as_ref().map_or(Vec3::ZERO, |d| d.position),
),
}
}
/// Takes the entity out of the level, before it ever ticked.
fn discard_slot(&mut self, slot: Slot) {
match slot {
Slot::Walk(h) => self.discard_entity(h),
Slot::Apart(Apart::Crystal(i)) => drop(self.crystals.remove(i)),
Slot::Apart(Apart::Cushion(i)) => drop(self.cushions.remove(i)),
Slot::Apart(Apart::Painting(i)) => drop(self.paintings.remove(i)),
Slot::Apart(Apart::Frame(i)) => drop(self.item_frames.remove(i)),
Slot::Apart(Apart::Dragon) => self.dragon = None,
}
}
/// Entities that were moved, not added (a portal's arrival keeps being
/// silent): everything now in the level counts as announced.
/// silent): everything now in the level counts as announced, and will
/// be told when it leaves.
pub(super) fn native_joins_skip(&mut self) {
if self.remote.is_none() && !crate::mod_api::mods().is_empty() {
while let Some((slot, id)) = self.next_unannounced() {
self.persist.joined_mark = id;
let kind = self.slot_kind_and_position(slot).0.into_owned();
self.persist.live.push((id, kind, true));
}
}
self.persist.joined_mark = self.persist.joined_mark.max(self.next_entity);
}
/// An entity taken out of the level without leaving it (an item through
/// an End gateway): forgotten, not told of.
pub(super) fn native_leave_skip(&mut self, id: u32) {
if let Ok(i) = self.persist.live.binary_search_by_key(&id, |l| l.0) {
self.persist.live.remove(i);
}
}
/// Entity `id` has left the level: told once, if mods were told it joined.
pub(super) fn native_entity_left(&mut self, id: u32) {
if self.remote.is_some() {
return;
}
if let Ok(i) = self.persist.live.binary_search_by_key(&id, |l| l.0) {
let (id, kind, _) = self.persist.live.remove(i);
let dimension = self.dim.id();
self.native_entity_leave_level(dimension, id, &kind);
}
}
/// Tells `native_entity_leave_level` for every announced entity that is
/// no longer in any list (died, despawned, picked up, burnt, blown up, gone
/// through a portal, unloaded), lowest id first. Marks the entities still
/// there in the registry and tells the rest: no allocation, and nothing
/// runs when no mod is loaded or none was announced.
pub(super) fn native_leaves_flush(&mut self) {
use super::entity::Entity;
if self.persist.live.is_empty() || self.remote.is_some() {
return;
}
for l in &mut self.persist.live {
l.2 = false;
}
let live = &mut self.persist.live;
let mut seen = |id: u32| {
if let Ok(i) = live.binary_search_by_key(&id, |l| l.0) {
live[i].2 = true;
}
};
super::entity::for_each_entity!(super::entity::entity_lists_of!(self), e => {
seen(e.id());
});
self.crystals.iter().for_each(|c| seen(c.id));
self.cushions.iter().for_each(|c| seen(c.id));
self.paintings.iter().for_each(|p| seen(p.id));
self.item_frames.iter().for_each(|f| seen(f.id));
if let Some(d) = &self.dragon {
seen(d.id);
}
while let Some(i) = self.persist.live.iter().position(|l| !l.2) {
let (id, kind, _) = self.persist.live.remove(i);
let dimension = self.dim.id();
self.native_entity_leave_level(dimension, id, &kind);
}
}
}
#[cfg(test)]
+24 -120
View File
@@ -697,7 +697,7 @@
//! change a cauldron.
//!
//! API 53 adds entities leaving a level (`native_entity_leave_level`), for
//! item entities, experience orbs and mobs so far.
//! every kind the join hook tells (see API 47).
//!
//! API 52 adds `native_block_entity_sync`: a mod block entity's block and update tag
//! sent to clients again (`Level.sendBlockUpdated`).
@@ -718,7 +718,7 @@
//! API 47 adds entities joining a level (`native_entity_join_level`); it tells
//! every kind the game keeps in its entity lists (items, orbs, mobs, boats,
//! minecarts, armor stands, TNT, falling blocks, projectiles, rockets,
//! tridents).
//! tridents, end crystals, the dragon, cushions, paintings, item frames).
//!
//! API 46 adds bounded world-space rim and colored-light requests
//! (`native_visuals`) and opt-in texture-matched emissive material
@@ -2309,6 +2309,16 @@ impl Game<'_> {
pub(super) fn native_commands(&mut self, commands: &mut Vec<&'static str>) {
mod_api::dispatch_commands(self, mod_api::mods(), commands);
}
/// Local player movement input, after reading it and before applying it.
pub(super) fn native_movement_input(
&self,
forward: &mut f32,
strafe: &mut f32,
jump: &mut bool,
sneak: &mut bool,
) {
mod_api::dispatch_movement_input(self, mod_api::mods(), forward, strafe, jump, sneak);
}
/// World field of view, after normal calculation and zoom and before projection/Nausea.
/// Called for world rendering and near-plane overlays; hand/HUD FOV uses its own path.
/// Only finite values in 1..=179 degrees replace the computed FOV.
@@ -6317,8 +6327,10 @@ impl Game<'_> {
/// a level closing with nobody in it included. Not for the entities still
/// loaded when the server stops, as in vanilla. `entity` is its ID and
/// `kind` its type (`minecraft:item`); it is no longer in the level's lists.
/// It only hears. 53 tells item entities, experience orbs and mobs (`kind`
/// the mob type); later APIs add other kinds, so check `kind`. Server-side.
/// It only hears. 53 told item entities, experience orbs and mobs; it now
/// tells every kind `native_entity_join_level` does (`kind` is the type), each
/// once, when the game next notices it gone (the tick or action that removed
/// it). Check `kind`. Server-side.
pub(super) fn native_entity_leave_level(&mut self, dimension: &str, entity: u32, kind: &str) {
mod_api::dispatch_entity_leave_level(self, mod_api::mods(), dimension, entity, kind);
}
@@ -6332,12 +6344,14 @@ impl Game<'_> {
/// back out as if never added, and a `/summon` of it fails. Tells every
/// kind in the game's entity lists (items, orbs, mobs, boats, minecarts,
/// armor stands, primed TNT, falling blocks, projectiles, rockets,
/// tridents), once each, in the order they were created; check `kind`. A
/// gameplay spawn is told when the next tick starts (a drop, an orb, a mob, a
/// `/summon` and a chunk's entities are told at once). A mod that
/// cancels one of the others sees it removed before it ticks again.
/// Not told: end crystals, the dragon, cushions, paintings, item frames and
/// the entities Horizon keeps without simulating. Server-side.
/// tridents, end crystals, the dragon, cushions, paintings, item frames),
/// once each, in the order they were created; check `kind`. A drop, an
/// orb, a mob, a `/summon` and a chunk's entities are told at once, any
/// other spawn between the tick's phases (it is checked after the blocks,
/// the vehicles, the projectiles, the items and the mobs, and at the
/// tick's end). A mod that cancels one of the others sees it removed
/// before it ticks again. Not told: the entities Horizon keeps without
/// simulating (displays, markers, interactions). Server-side.
pub(super) fn native_entity_join_level(
&mut self,
dimension: &str,
@@ -8258,22 +8272,6 @@ pub(super) fn mob_kind(mob: &horizon_client::mobs::Mob) -> String {
["minecraft:", mob.kind.id.trim_start_matches("minecraft:")].concat()
}
/// The ids of `before` (id, kind) that are not in `now`, grouped by kind in
/// first-seen order: the entities that left the level.
fn vanished_by_kind(
before: &[(u32, String)],
now: &std::collections::HashSet<u32>,
) -> Vec<(String, Vec<u32>)> {
let mut out: Vec<(String, Vec<u32>)> = Vec::new();
for (id, kind) in before.iter().filter(|(id, _)| !now.contains(id)) {
match out.iter_mut().find(|(k, _)| k == kind) {
Some((_, ids)) => ids.push(*id),
None => out.push((kind.clone(), Vec::from([*id]))),
}
}
out
}
/// Where the game calls the hook points.
impl Game<'_> {
/// The acting player as the hook points name one: the guest whose turn
@@ -8395,80 +8393,6 @@ impl Game<'_> {
self.native_mob_spawned(self.dim.id(), &kind, pos, natural);
}
}
/// `native_entity_leave_level` for each of `entities`, of `kind`, gone
/// from the level in place. A client of a server tells nothing.
pub(super) fn native_entities_left(&mut self, kind: &str, entities: &[u32]) {
if self.remote.is_some() {
return;
}
let dimension = self.dim.id();
for &entity in entities {
self.native_entity_leave_level(dimension, entity, kind);
}
}
/// The IDs of the level's item entities, for `native_items_left_since`
/// (none on a client of a server, which tells nothing).
pub(super) fn dropped_ids(&self) -> Vec<u32> {
if self.remote.is_some() || mod_api::mods().is_empty() {
return Vec::new();
}
self.dropped.iter().map(|e| e.id).collect()
}
/// The IDs of the level's experience orbs, for `native_orbs_left_since`.
pub(super) fn orb_ids(&self) -> Vec<u32> {
if self.remote.is_some() || mod_api::mods().is_empty() {
return Vec::new();
}
self.orbs.iter().map(|o| o.id).collect()
}
/// The (id, kind) of the level's mobs, for `native_mobs_left_since` (none
/// on a client of a server, which tells nothing).
pub(super) fn mob_ids(&self) -> Vec<(u32, String)> {
if self.remote.is_some() || mod_api::mods().is_empty() {
return Vec::new();
}
self.mobs.iter().map(|m| (m.id, mob_kind(m))).collect()
}
/// The mobs of `before` gone since have left the level (died, despawned,
/// entered a hive, unloaded). Mobs spawned meanwhile are not counted.
pub(super) fn native_mobs_left_since(&mut self, before: &[(u32, String)]) {
// Nothing was taken: no mod listens, or a client of a server.
if before.is_empty() {
return;
}
let now: std::collections::HashSet<u32> = self.mobs.iter().map(|m| m.id).collect();
for (kind, ids) in vanished_by_kind(before, &now) {
self.native_entities_left(&kind, &ids);
}
}
/// The item entities of `before` gone since have left the level.
pub(super) fn native_items_left_since(&mut self, before: &[u32]) {
// Nothing was taken: no mod listens, or a client of a server.
if before.is_empty() {
return;
}
let now: std::collections::HashSet<u32> = self.dropped.iter().map(|e| e.id).collect();
let gone: Vec<u32> = before
.iter()
.copied()
.filter(|id| !now.contains(id))
.collect();
self.native_entities_left("minecraft:item", &gone);
}
/// The experience orbs of `before` gone since have left the level.
pub(super) fn native_orbs_left_since(&mut self, before: &[u32]) {
// Nothing was taken: no mod listens, or a client of a server.
if before.is_empty() {
return;
}
let now: std::collections::HashSet<u32> = self.orbs.iter().map(|o| o.id).collect();
let gone: Vec<u32> = before
.iter()
.copied()
.filter(|id| !now.contains(id))
.collect();
self.native_entities_left("minecraft:experience_orb", &gone);
}
/// The acting player's blow killed mob `index`.
pub(super) fn native_kill_event(&mut self, index: usize) {
let (entity, kind) = (self.mobs[index].id, mob_kind(&self.mobs[index]));
@@ -9319,26 +9243,6 @@ fn mod_screen_opens(
#[cfg(test)]
mod tests {
#[test]
fn vanished_mobs_are_reported_by_kind() {
use std::collections::HashSet;
let z = || "minecraft:zombie".to_string();
let b = || "minecraft:bee".to_string();
let before = vec![(1, z()), (2, b()), (3, z()), (4, b())];
let now: HashSet<u32> = [1, 4, 99].into_iter().collect(); // 99 spawned meanwhile
assert_eq!(
super::vanished_by_kind(&before, &now),
vec![(b(), vec![2]), (z(), vec![3])]
);
let all: HashSet<u32> = [1, 2, 3, 4, 5].into_iter().collect();
assert!(super::vanished_by_kind(&before, &all).is_empty());
assert_eq!(
super::vanished_by_kind(&before, &HashSet::new()),
vec![(z(), vec![1, 3]), (b(), vec![2, 4])]
);
assert!(super::vanished_by_kind(&[], &now).is_empty());
}
/// Exercise the gameplay constructor, where name-based entities compete
/// with a mod block's sign template.
#[test]
+23 -7
View File
@@ -45,6 +45,10 @@ pub struct EntityMemory {
pub(super) joins_canceled: u32,
/// The highest entity id `native_entity_join_level` has been told of.
pub(super) joined_mark: u32,
/// The entities mods were told of and that have not left since: id (in
/// increasing order), type, and whether the last leave sweep saw it.
/// Only filled while a mod is loaded.
pub(super) live: Vec<(u32, String, bool)>,
}
/// The key that marks a compound queued with the chunk loads as a fresh add
@@ -355,6 +359,10 @@ impl Game<'_> {
kind.as_str(),
"minecraft:experience_orb"
| "minecraft:arrow"
| "minecraft:spectral_arrow"
| "minecraft:llama_spit"
| "minecraft:shulker_bullet"
| "minecraft:experience_bottle"
| "minecraft:fireball"
| "minecraft:small_fireball"
| "minecraft:wind_charge"
@@ -398,7 +406,7 @@ impl Game<'_> {
self.remember_tags(id, &tag);
}
}
"minecraft:arrow" => {
"minecraft:arrow" | "minecraft:spectral_arrow" => {
if let Some((mut p, in_block)) = entity_nbt::arrow_from_nbt(
&tag,
player_uuid,
@@ -431,6 +439,17 @@ impl Game<'_> {
self.remember_last_projectile_tags(&tag);
}
}
"minecraft:llama_spit" | "minecraft:shulker_bullet" => {
if let Some(mut p) = entity_nbt::spit_or_bullet_from_nbt(&tag, player_uuid)
{
self.claim_owner(&mut p);
self.next_entity += 1;
p.id = self.next_entity;
self.restore_portal_cooldown(p.id, &tag);
self.add_projectile(p);
self.remember_last_projectile_tags(&tag);
}
}
id if entity_nbt::throwable_kind(id).is_some() => {
if let Some(mut p) = entity_nbt::throwable_from_nbt(&tag, player_uuid) {
self.claim_owner(&mut p);
@@ -1326,7 +1345,6 @@ impl Game<'_> {
})
.map(|i| self.mobs[i].id)
.collect();
let mobs_before = self.mob_ids();
self.mobs.retain(|m| {
let keep = !leaving.contains(&m.id);
if !keep {
@@ -1334,7 +1352,6 @@ impl Game<'_> {
}
keep
});
let (items_before, orbs_before) = (self.dropped_ids(), self.orb_ids());
self.dropped.retain(|e| {
let keep = !gone(e.position);
if !keep {
@@ -1344,9 +1361,7 @@ impl Game<'_> {
});
self.orbs.retain(|o| !gone(o.position));
// Unloaded with their chunk: PersistentEntitySectionManager.stopTracking.
self.native_mobs_left_since(&mobs_before);
self.native_items_left_since(&items_before);
self.native_orbs_left_since(&orbs_before);
self.native_leaves_flush();
// the ones saved with the chunk go with it (a trident too, unsaved).
self.creatures.projectiles.retain(|p| {
let leaves = entity_nbt::saved_with_chunk(p) || p.arrow.is_some();
@@ -1512,7 +1527,8 @@ impl Game<'_> {
.map(|s| self.state_to_nbt(s));
let tag = entity_nbt::arrow_to_nbt(p, uuid, owner, in_block, raw.as_ref())
.or_else(|| entity_nbt::fireball_to_nbt(p, uuid, owner, raw.as_ref()))
.or_else(|| entity_nbt::throwable_to_nbt(p, uuid, owner, raw.as_ref()))?;
.or_else(|| entity_nbt::throwable_to_nbt(p, uuid, owner, raw.as_ref()))
.or_else(|| entity_nbt::spit_or_bullet_to_nbt(p, uuid, owner, raw.as_ref()))?;
Some((
chunk_of(p.position),
with_raw(tag, self.persist.raw.get(&p.id)),
+14
View File
@@ -5781,6 +5781,20 @@ impl Game<'_> {
if self.integrated.is_none() && self.server_address().is_some() {
keys.analog = None;
}
let [mut strafe, mut forward] = keys.direction();
let original = [strafe, forward];
self.native_movement_input(&mut forward, &mut strafe, &mut keys.jump, &mut keys.shift);
if [strafe, forward] != original {
if forward != original[1] {
keys.forward = forward > 0.0;
keys.backward = forward < 0.0;
}
if strafe != original[0] {
keys.left = strafe > 0.0;
keys.right = strafe < 0.0;
}
keys.analog = Some([strafe, forward]);
}
let input = u8::from(keys.forward)
| u8::from(keys.backward) << 1
| u8::from(keys.left) << 2
@@ -98,7 +98,7 @@ fn projectile_kind(kind: &str) -> Option<ProjectileKind> {
"minecraft:breeze_wind_charge" => ProjectileKind::BreezeWindCharge,
// Homing state stays on the server; the client interpolates its positions.
"minecraft:shulker_bullet" => ProjectileKind::ShulkerBullet {
target: horizon_client::mobs::Who::Player,
target: Some(horizon_client::mobs::Who::Player),
dir: None,
steps: 0,
delta: [0.0; 3],
+4 -1
View File
@@ -222,7 +222,10 @@ impl Game<'_> {
if r.trial {
tag.insert("PersistenceRequired".into(), Tag::Byte(1));
}
let tag = Tag::Compound(tag);
let mut tag = Tag::Compound(tag);
// EntityType.loadEntityRecursive applies the spawner's placement
// callback to every passenger as well as the root.
super::commands::place_summoned(&mut tag, r.position);
let mob = horizon_client::entity_nbt::mob_from_nbt(
&tag,
self.res.mob_types(),
+4 -1
View File
@@ -1,9 +1,12 @@
//! Horizon: native Rust client for Minecraft Java Edition 26.3 gameplay.
#![forbid(unsafe_code)]
#[cfg(not(feature = "system-allocator"))]
#[cfg(not(any(feature = "system-allocator", feature = "alloc-census")))]
#[global_allocator]
static ALLOCATOR: horizon_threads::Allocator = horizon_threads::Allocator;
#[cfg(all(feature = "alloc-census", not(feature = "system-allocator")))]
#[global_allocator]
static ALLOCATOR: horizon_threads::CensusAllocator = horizon_threads::CensusAllocator;
/// The mod API level this game serves, kept in the program for the launcher
/// to read without running it (`horizon_mods::api_level::MARK`). `main`
+133 -53
View File
@@ -636,6 +636,7 @@ pub fn tick(
if m.species() == Species::Endermite {
endermite_lifetime(m);
}
ageable_step(m, w, level_random, events);
// AbstractHorse.tick runs the stand counter down with or without AI.
if super::equine::is_equine(m.species())
&& !super::camel::is_camel(m.species())
@@ -1263,6 +1264,68 @@ fn play_ambient(m: &mut Mob, w: &MobWorld, events: &mut Vec<MobEvent>) {
fn turtle_ambient_silent(m: &Mob) -> bool {
m.fluids.in_water || !m.on_ground || m.is_baby()
}
/// `AgeableMob.aiStep` runs even when `Mob.serverAiStep` is disabled.
fn ageable_step(
m: &mut Mob,
w: &MobWorld,
level_random: &mut LegacyRandom,
events: &mut Vec<MobEvent>,
) {
// AgeableMob.aiStep: a forced age-up's particles (`sendParticles`, so the
// players are sent them), then the age.
if m.extra.forced_age_timer > 0 {
if m.extra.forced_age_timer % 4 == 0 {
let (w, h) = (f64::from(m.width()), f64::from(m.height()));
let r = &mut m.random;
let x = m.position.x + w * (2.0 * r.next_double() - 1.0);
let y = m.position.y + h * r.next_double() + 0.5;
let z = m.position.z + w * (2.0 * r.next_double() - 1.0);
// sendParticles(HAPPY_VILLAGER, x, y, z, 1, 0, 0, 0, 0): exactly there, at rest.
events.push(MobEvent::LevelParticles {
kind: "happy_villager",
at: [x, y, z],
spread: [0.0; 3],
speed: 0.0,
count: 1,
});
}
m.extra.forced_age_timer -= 1;
}
// AgeableMob.aiStep: an age counts only for its subclasses (a tadpole
// counts its own age up, a zombie's baby age never moves). `canAgeUp`:
// a baby grows unless an age lock holds it; a breeding pause counts down.
if m.alive() && super::finalize::ageable(m.species()) {
if can_age_up(m) {
let (bw, bh, _) = m.dims();
// AgeableMob.setAge(getAge() + 1): the crossing is where a species'
// ageBoundaryReached runs (a goat's and a hoglin's attack damage, a
// sulfur cube's size), which the age write used to skip.
let next = m.extra.age + 1;
super::finalize::set_age(m, next);
// setAge at the age boundary: refreshDimensions.
if m.extra.age >= 0 {
m.fudge_position_after_size_change(w, bw, bh);
if m.species() == Species::Villager {
super::villager_brain::age_boundary_reached(m, w, level_random, events);
}
// Turtle.ageBoundaryReached: a grown turtle drops a scute.
if m.species() == Species::Turtle {
events.push(MobEvent::GiftLoot {
mob: m.id,
table: "minecraft:gameplay/turtle_grow",
at: [m.position.x, m.position.y, m.position.z],
});
}
}
} else if m.extra.age > 0 {
m.extra.age -= 1;
}
}
if super::finalize::ageable(m.species()) {
make_age_locked_particle(m, events);
}
}
/// LivingEntity.aiStep (and Mob/AgeableMob/Animal additions).
fn ai_step(m: &mut Mob, w: &MobWorld, level_random: &mut LegacyRandom, events: &mut Vec<MobEvent>) {
if m.species() == Species::Parrot {
@@ -1337,59 +1400,7 @@ fn ai_step(m: &mut Mob, w: &MobWorld, level_random: &mut LegacyRandom, events: &
if m.kind.flags.climber {
m.climbing = m.horizontal_collision;
}
// AgeableMob.aiStep: a forced age-up's particles (`sendParticles`, so the
// players are sent them), then the age.
if m.extra.forced_age_timer > 0 {
if m.extra.forced_age_timer % 4 == 0 {
let (w, h) = (f64::from(m.width()), f64::from(m.height()));
let r = &mut m.random;
let x = m.position.x + w * (2.0 * r.next_double() - 1.0);
let y = m.position.y + h * r.next_double() + 0.5;
let z = m.position.z + w * (2.0 * r.next_double() - 1.0);
// sendParticles(HAPPY_VILLAGER, x, y, z, 1, 0, 0, 0, 0): exactly there, at rest.
events.push(MobEvent::LevelParticles {
kind: "happy_villager",
at: [x, y, z],
spread: [0.0; 3],
speed: 0.0,
count: 1,
});
}
m.extra.forced_age_timer -= 1;
}
// AgeableMob.aiStep: an age counts only for its subclasses (a tadpole
// counts its own age up, a zombie's baby age never moves). `canAgeUp`:
// a baby grows unless an age lock holds it; a breeding pause counts down.
if m.alive() && super::finalize::ageable(m.species()) {
if can_age_up(m) {
let (bw, bh, _) = m.dims();
// AgeableMob.setAge(getAge() + 1): the crossing is where a species'
// ageBoundaryReached runs (a goat's and a hoglin's attack damage, a
// sulfur cube's size), which the age write used to skip.
let next = m.extra.age + 1;
super::finalize::set_age(m, next);
// setAge at the age boundary: refreshDimensions.
if m.extra.age >= 0 {
m.fudge_position_after_size_change(w, bw, bh);
if m.species() == Species::Villager {
super::villager_brain::age_boundary_reached(m, w, level_random, events);
}
// Turtle.ageBoundaryReached: a grown turtle drops a scute.
if m.species() == Species::Turtle {
events.push(MobEvent::GiftLoot {
mob: m.id,
table: "minecraft:gameplay/turtle_grow",
at: [m.position.x, m.position.y, m.position.z],
});
}
}
} else if m.extra.age > 0 {
m.extra.age -= 1;
}
}
if super::finalize::ageable(m.species()) {
make_age_locked_particle(m, events);
}
ageable_step(m, w, level_random, events);
if m.alive() {
// Animal.aiStep
if m.extra.age != 0 {
@@ -3431,6 +3442,75 @@ mod age_up_tests {
use super::*;
use std::sync::Arc;
/// Official Java 26.3: NoAI leaves both age clocks running. AgeLocked
/// holds a baby's age, but a positive breeding cooldown still expires.
#[test]
fn age_clocks_tick_with_no_ai_and_respect_baby_age_lock() {
let ran = crate::mobs::world::with_test_world(&[], |w, types| {
for species in ["pig", "cow", "horse", "turtle"] {
for no_ai in [true, false] {
for locked in [false, true] {
for age in [-2, 2] {
let kind =
Arc::new(types.types[&format!("minecraft:{species}")].clone());
let mut m = Mob::new(1, kind, Vec3::new(4.0, 220.0, 4.0), 0.0);
m.no_ai = no_ai;
m.no_gravity = true;
m.persistent = true;
m.extra.age_locked = locked;
super::super::finalize::set_age(&mut m, age);
let (mut random, mut events) = (LegacyRandom::new(0), Vec::new());
let expected = if age > 0 {
[1, 0, 0, 0]
} else if locked {
[-2, -2, -2, -2]
} else {
[-1, 0, 0, 0]
};
for (elapsed, expected_age) in expected.into_iter().enumerate() {
let previous_age = m.extra.age;
let previous_position = m.position;
tick(&mut m, w, &mut random, &mut events);
assert_eq!(
m.extra.age,
expected_age,
"{species}: NoAI={no_ai}, AgeLocked={locked}, initial Age={age}, tick={}",
elapsed + 1
);
if no_ai {
assert_eq!(m.velocity, Vec3::ZERO);
// Growing dimensions can reposition the mob at adulthood.
if previous_age != -1 || expected_age != 0 {
assert_eq!(m.position, previous_position);
}
}
}
if species == "turtle" && age < 0 && !locked {
assert_eq!(
events
.iter()
.filter(|event| matches!(
event,
MobEvent::GiftLoot {
table: "minecraft:gameplay/turtle_grow",
..
}
))
.count(),
1,
"growing up drops one scute"
);
}
}
}
}
}
});
if !ran {
eprintln!("age clock regression needs prepared vanilla resources");
}
}
/// A baby goat `age` ticks from adulthood, from no vanilla data.
fn baby_goat(age: i32) -> Mob {
let mut kind = crate::mobs::sensor_scan_tests::kind();
+47 -8
View File
@@ -714,12 +714,14 @@ pub trait Hooks: Send + Sync {
/// back out as if never added, and a `/summon` of it fails. Tells every
/// kind in the game's entity lists (items, orbs, mobs, boats, minecarts,
/// armor stands, primed TNT, falling blocks, projectiles, rockets,
/// tridents), once each, in the order they were created; check `kind`. A
/// gameplay spawn is told when the next tick starts (a drop, an orb, a mob, a
/// `/summon` and a chunk's entities are told at once). A mod that
/// cancels one of the others sees it removed before it ticks again.
/// Not told: end crystals, the dragon, cushions, paintings, item frames and
/// the entities Horizon keeps without simulating. Server-side.
/// tridents, end crystals, the dragon, cushions, paintings, item frames),
/// once each, in the order they were created; check `kind`. A drop, an
/// orb, a mob, a `/summon` and a chunk's entities are told at once, any
/// other spawn between the tick's phases (it is checked after the blocks,
/// the vehicles, the projectiles, the items and the mobs, and at the
/// tick's end). A mod that cancels one of the others sees it removed
/// before it ticks again. Not told: the entities Horizon keeps without
/// simulating (displays, markers, interactions). Server-side.
///
/// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped.
fn entity_join_level(&self, host: &mut dyn Host, event: &mut EntityJoinLevelEvent<'_>) -> Flow {
@@ -763,8 +765,10 @@ pub trait Hooks: Send + Sync {
/// a level closing with nobody in it included. Not for the entities still
/// loaded when the server stops, as in vanilla. `entity` is its ID and
/// `kind` its type (`minecraft:item`); it is no longer in the level's lists.
/// It only hears. 53 tells item entities, experience orbs and mobs (`kind`
/// the mob type); later APIs add other kinds, so check `kind`. Server-side.
/// It only hears. 53 told item entities, experience orbs and mobs; it now
/// tells every kind `native_entity_join_level` does (`kind` is the type), each
/// once, when the game next notices it gone (the tick or action that removed
/// it). Check `kind`. Server-side.
fn entity_leave_level(&self, host: &mut dyn Host, event: &mut EntityLeaveLevelEvent<'_>) {
let _ = host;
let _ = event;
@@ -4508,6 +4512,11 @@ pub trait Hooks: Send + Sync {
fn living_jump(&self, event: &mut LivingJumpEvent<'_>) {
let _ = event;
}
/// Local player movement input, after reading it and before applying it.
fn movement_input(&self, host: &dyn Host, event: &mut MovementInputEvent) {
let _ = host;
let _ = event;
}
}
/// What [`Hooks::frame`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
@@ -13795,3 +13804,33 @@ pub fn dispatch_living_jump(mods: &[Box<dyn Hooks>], entity: u32, kind: &str) {
mod_hook.living_jump(&mut event);
}
}
/// What [`Hooks::movement_input`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct MovementInputEvent {
pub forward: f32,
pub strafe: f32,
pub jump: bool,
pub sneak: bool,
}
pub fn dispatch_movement_input(
host: &dyn Host,
mods: &[Box<dyn Hooks>],
forward: &mut f32,
strafe: &mut f32,
jump: &mut bool,
sneak: &mut bool,
) {
let mut event = MovementInputEvent {
forward: *forward,
strafe: *strafe,
jump: *jump,
sneak: *sneak,
};
for mod_hook in super::entered(mods, "movement_input") {
mod_hook.movement_input(host, &mut event);
}
*forward = event.forward;
*strafe = event.strafe;
*jump = event.jump;
*sneak = event.sneak;
}
+18 -1
View File
@@ -2033,9 +2033,26 @@ fn link(target: &Path, at: &Path) -> std::io::Result<()> {
}
Ok(())
} else {
fs::copy(target, at).map(|_| ())
link_file(target, at, |target, at| fs::hard_link(target, at))
}
}
/// A file of the overlay where symlinks need a privilege: a hard link to the base's file, which costs a name where
/// a copy costs the file's bytes (the base is ~28,000 files, 0.6 GiB), or a copy when no link can be made (another
/// volume, a filesystem without links, the NTFS limit on links to one file). A link shares the base file's data,
/// which is safe because the overlay is only ever replaced name by name (`Overlay::write` removes the name before it
/// writes), never written in place. A symlink is copied through, as before: linked, it would stay a symlink.
#[cfg(any(not(unix), test))]
fn link_file(
target: &Path,
at: &Path,
hard_link: impl FnOnce(&Path, &Path) -> std::io::Result<()>,
) -> std::io::Result<()> {
let symlink = fs::symlink_metadata(target).is_ok_and(|m| m.file_type().is_symlink());
if !symlink && hard_link(target, at).is_ok() {
return Ok(());
}
fs::copy(target, at).map(|_| ())
}
/// A tag file's JSON text with `more` appended to its values (no repeats).
fn merge_tag(text: Option<&str>, more: &[J], what: &str) -> Result<String> {
@@ -3774,3 +3774,168 @@ fn a_feature_gives_a_mods_sign_its_block_entity() {
assert_eq!(generate(true), (placed, placed));
let _ = fs::remove_dir_all(&scratch);
}
/// A small stand-in for the base root: nested folders, an empty file, an empty folder.
fn small_base(base: &Path) {
for (name, bytes) in [
("items.json", &b"{}"[..]),
("empty.log", &b""[..]),
("assets/minecraft/lang/en_us.json", &b"{\"a\": 1}"[..]),
(
"data/minecraft/tags/block/logs.json",
&b"{\"values\": []}"[..],
),
] {
let path = base.join(name);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, bytes).unwrap();
}
fs::create_dir_all(base.join("worldgen/empty-folder")).unwrap();
}
/// Every folder (`None`) and file (`Some(bytes)`) under `root`, by relative path.
fn tree(root: &Path) -> BTreeMap<String, Option<Vec<u8>>> {
fn walk(root: &Path, dir: &Path, out: &mut BTreeMap<String, Option<Vec<u8>>>) {
for entry in fs::read_dir(dir).unwrap().flatten() {
let path = entry.path();
let rel = path
.strip_prefix(root)
.unwrap()
.to_string_lossy()
.replace('\\', "/");
if path.is_dir() {
out.insert(rel, None);
walk(root, &path, out);
} else {
out.insert(rel, Some(fs::read(&path).unwrap()));
}
}
}
let mut out = BTreeMap::new();
walk(root, root, &mut out);
out
}
/// Writes into a file where it is (same data), not by replacing it: what a link shares and a copy does not.
fn edit_in_place(path: &Path, bytes: &[u8]) {
use std::io::Write;
let mut file = fs::File::options()
.write(true)
.truncate(true)
.open(path)
.unwrap();
file.write_all(bytes).unwrap();
}
/// The overlay looks like the base, and shares its files (a hard link on Windows, a symlink elsewhere) rather than
/// holding copies of them.
#[test]
fn the_overlay_shares_the_base_files() {
let scratch = std::env::temp_dir().join(format!("horizon-mod-share-{}", std::process::id()));
let _ = fs::remove_dir_all(&scratch);
let (base, out) = (scratch.join("base"), scratch.join("root"));
small_base(&base);
Overlay::new(&base, &out).unwrap();
assert_eq!(tree(&out), tree(&base));
edit_in_place(
&base.join("assets/minecraft/lang/en_us.json"),
b"{\"a\": 2}",
);
assert_eq!(
fs::read(out.join("assets/minecraft/lang/en_us.json")).unwrap(),
b"{\"a\": 2}",
"the overlay copied the base file instead of linking it"
);
let _ = fs::remove_dir_all(&scratch);
}
/// Writing a file of the overlay replaces the name, so the base file it was linked to keeps its bytes and the other
/// files of the overlay still follow the base.
#[test]
fn writing_into_the_overlay_leaves_the_base_alone() {
let scratch = std::env::temp_dir().join(format!("horizon-mod-write-{}", std::process::id()));
let _ = fs::remove_dir_all(&scratch);
let (base, out) = (scratch.join("base"), scratch.join("root"));
small_base(&base);
let overlay = Overlay::new(&base, &out).unwrap();
let before = tree(&base);
overlay
.write(
"data/minecraft/tags/block/logs.json",
b"{\"values\": [\"mod:log\"]}",
)
.unwrap();
overlay.write("items.json", b"{\"mod\": 1}").unwrap();
overlay.write("assets/mod/new.json", b"{}").unwrap();
assert_eq!(tree(&base), before, "a write reached the base");
assert_eq!(
fs::read(out.join("data/minecraft/tags/block/logs.json")).unwrap(),
b"{\"values\": [\"mod:log\"]}"
);
assert_eq!(fs::read(out.join("items.json")).unwrap(), b"{\"mod\": 1}");
edit_in_place(
&base.join("assets/minecraft/lang/en_us.json"),
b"{\"a\": 3}",
);
assert_eq!(
fs::read(out.join("assets/minecraft/lang/en_us.json")).unwrap(),
b"{\"a\": 3}"
);
// A rebuild starts by removing the overlay: that removes names, not the base's data.
fs::remove_dir_all(&out).unwrap();
assert_eq!(fs::read(base.join("items.json")).unwrap(), b"{}");
let _ = fs::remove_dir_all(&scratch);
}
/// Where a hard link cannot be made (another volume, a filesystem without links) a file is copied, as it always was
/// on Windows; where it can, the file is linked.
#[test]
fn a_file_is_linked_or_else_copied() {
let scratch = std::env::temp_dir().join(format!("horizon-mod-linkfile-{}", std::process::id()));
let _ = fs::remove_dir_all(&scratch);
fs::create_dir_all(&scratch).unwrap();
let target = scratch.join("base.json");
fs::write(&target, b"original").unwrap();
let linked = scratch.join("linked.json");
link_file(&target, &linked, |from, to| fs::hard_link(from, to)).unwrap();
edit_in_place(&target, b"edited");
assert_eq!(fs::read(&linked).unwrap(), b"edited", "not a link");
let copied = scratch.join("copied.json");
fs::write(&target, b"original").unwrap();
link_file(&target, &copied, |_, _| {
Err(std::io::Error::other("no links here"))
})
.unwrap();
edit_in_place(&target, b"edited");
assert_eq!(fs::read(&copied).unwrap(), b"original", "not a copy");
// A target that is not there is an error whichever way the file is made.
let missing = link_file(
&scratch.join("missing.json"),
&scratch.join("x.json"),
|from, to| fs::hard_link(from, to),
);
assert!(missing.is_err());
let _ = fs::remove_dir_all(&scratch);
}
/// A symlink in the base is copied through, as before; linked, the overlay's file would be a symlink too.
#[cfg(unix)]
#[test]
fn a_symlink_file_is_copied_through() {
let scratch = std::env::temp_dir().join(format!("horizon-mod-symfile-{}", std::process::id()));
let _ = fs::remove_dir_all(&scratch);
fs::create_dir_all(&scratch).unwrap();
fs::write(scratch.join("real.json"), b"real").unwrap();
std::os::unix::fs::symlink("real.json", scratch.join("alias.json")).unwrap();
let at = scratch.join("copy.json");
link_file(&scratch.join("alias.json"), &at, |from, to| {
fs::hard_link(from, to)
})
.unwrap();
assert!(!fs::symlink_metadata(&at).unwrap().file_type().is_symlink());
assert_eq!(fs::read(&at).unwrap(), b"real");
let _ = fs::remove_dir_all(&scratch);
}
+57 -7
View File
@@ -94,9 +94,10 @@ pub enum ProjectileKind {
/// flight that bursts on whatever it meets.
WindCharge,
BreezeWindCharge,
/// `ShulkerBullet`: homing on `target` in axis-aligned steps.
/// `ShulkerBullet`: homing on `target` in axis-aligned steps; one with
/// no target (a summoned one) falls like any projectile.
ShulkerBullet {
target: Who,
target: Option<Who>,
/// The current step direction (3D data value), if any.
dir: Option<u8>,
steps: i32,
@@ -116,6 +117,8 @@ pub enum ProjectileKind {
/// type is the entity's own and `/summon` can pair either with any `Item`,
/// so it is kept next to the stack (a thrown potion's follows its item).
Potion(Box<crate::items::ItemStack>, bool),
/// `ThrownExperienceBottle`: its own entity type, gravity 0.07.
ExperienceBottle,
/// a content mod's type.
Thrown(&'static ThrownType),
}
@@ -148,6 +151,7 @@ impl ProjectileKind {
| Self::EnderPearl
| Self::LlamaSpit
| Self::Potion(..)
| Self::ExperienceBottle
| Self::Thrown(_) => 0.25,
Self::SmallFireball
| Self::WindCharge
@@ -175,6 +179,7 @@ impl ProjectileKind {
// `ThrownSplashPotion`/`ThrownLingeringPotion`: the item names it.
Self::Potion(_, true) => "minecraft:lingering_potion",
Self::Potion(_, false) => "minecraft:splash_potion",
Self::ExperienceBottle => "minecraft:experience_bottle",
Self::Thrown(t) => t.id,
}
}
@@ -314,13 +319,16 @@ impl crate::entity_rules::EntityRules for Projectile {
}
/// Each projectile class's `getDefaultGravity`.
fn get_default_gravity(&self) -> f64 {
match self.kind {
match &self.kind {
ProjectileKind::Arrow { .. } | ProjectileKind::Trident => 0.05,
ProjectileKind::SmallFireball
| ProjectileKind::LargeFireball { .. }
| ProjectileKind::WitherSkull { .. } => 0.0,
ProjectileKind::LlamaSpit => 0.06,
// `ThrownExperienceBottle.getDefaultGravity` is 0.07.
ProjectileKind::ExperienceBottle => 0.07,
ProjectileKind::Potion { .. } => 0.05,
ProjectileKind::ShulkerBullet { .. } => 0.04,
_ => 0.03,
}
}
@@ -465,7 +473,7 @@ impl Projectile {
|| self
.arrow
.as_ref()
.is_some_and(|a| &*a.pickup_item.item == "minecraft:spectral_arrow")
.is_some_and(|a| a.spectral || &*a.pickup_item.item == "minecraft:spectral_arrow")
}
/// `Arrow.ID_EFFECT_COLOR` (`updateColor`): the opaque colour of the
/// potion the arrow carries, from its tipped pickup item or the effect a
@@ -598,7 +606,7 @@ impl Projectile {
);
let mut p = Self::new(
ProjectileKind::ShulkerBullet {
target,
target: Some(target),
dir: Some(1),
steps: 0,
delta: [0.0; 3],
@@ -629,7 +637,11 @@ impl Projectile {
]
}
fn target_box(&self, w: &impl ProjectileWorld) -> Option<Aabb> {
let ProjectileKind::ShulkerBullet { target, .. } = self.kind else {
let ProjectileKind::ShulkerBullet {
target: Some(target),
..
} = self.kind
else {
return None;
};
w.targets()
@@ -743,7 +755,8 @@ impl Projectile {
v.z + (dl[2] - v.z) * 0.2,
);
} else {
self.velocity.y -= 0.04;
// `Entity.applyGravity`: `getGravity` is 0 under NoGravity.
self.velocity.y -= crate::entity_rules::EntityRules::get_gravity(self);
}
let from = self.position;
let to = from.add_vector(self.velocity);
@@ -1004,6 +1017,10 @@ impl Projectile {
pub fn drawn_item(&self) -> Option<crate::items::ItemStack> {
match &self.kind {
ProjectileKind::Potion(stack, _) => Some((**stack).clone()),
ProjectileKind::ExperienceBottle => Some(crate::items::ItemStack::new(
"minecraft:experience_bottle",
1,
)),
ProjectileKind::Thrown(t) => Some(crate::items::ItemStack::new(t.item, 1)),
_ => None,
}
@@ -1018,6 +1035,7 @@ impl Projectile {
"item/fire_charge"
}
ProjectileKind::Potion(stack, _) => potion_sprite(&stack.item),
ProjectileKind::ExperienceBottle => "item/experience_bottle",
ProjectileKind::LlamaSpit => "entity/llama/llama_spit",
// Drawn as `WindChargeModel` and `ShulkerBulletModel` by the game.
ProjectileKind::WindCharge
@@ -1298,6 +1316,38 @@ mod tests {
assert_eq!(settings("ruby:late"), (5, 3, true));
}
/// `ShulkerBullet.tick` with no target: `applyGravity` (0.04, none under
/// NoGravity), then the move, no drag. 20 ticks from y 110 fall 8.4.
#[test]
fn a_shulker_bullet_without_a_target_falls_unless_no_gravity() {
let bullet = || {
Projectile::new(
ProjectileKind::ShulkerBullet {
target: None,
dir: None,
steps: 0,
delta: [0.0; 3],
seed: 0,
},
Vec3::new(0.5, 110.0, 0.5),
None,
)
};
let w = Empty(vec![]);
let mut falling = bullet();
let mut held = bullet();
held.no_gravity = true;
for _ in 0..20 {
falling.tick(&w);
held.tick(&w);
}
assert!(
(falling.position.y - 101.6).abs() < 1e-6,
"{}",
falling.position.y
);
assert_eq!(held.position, Vec3::new(0.5, 110.0, 0.5));
}
#[test]
fn llama_spit_moves_first_then_drags_and_falls() {
let mut p = Projectile::new(ProjectileKind::LlamaSpit, Vec3::new(0.0, 100.0, 0.0), None);
+29 -1
View File
@@ -1,6 +1,8 @@
//! A mod crate's view of the mod API: `Hooks` is implemented with the names `mod_api` exports.
//! The test is that this file builds; #7044's mod could not name `key`'s event.
use horizon_client::mod_api::{Hooks, Host, InputKeyEvent, KeyEvent, winit};
use horizon_client::mod_api::{
Hooks, Host, InputKeyEvent, KeyEvent, MovementInputEvent, dispatch_movement_input, winit,
};
struct Keys;
@@ -18,3 +20,29 @@ impl Hooks for Keys {
fn a_mod_implements_the_key_hooks() {
let _: Box<dyn Hooks> = Box::new(Keys);
}
#[test]
fn movement_input_writes_back_all_fields() {
struct TestHost;
impl Host for TestHost {}
struct Movement;
impl Hooks for Movement {
fn movement_input(&self, _host: &dyn Host, event: &mut MovementInputEvent) {
event.forward = -0.5;
event.strafe = 0.25;
event.jump = true;
event.sneak = true;
}
}
let mods: Vec<Box<dyn Hooks>> = vec![Box::new(Movement)];
let (mut forward, mut strafe, mut jump, mut sneak) = (1.0, 0.0, false, false);
dispatch_movement_input(
&TestHost,
&mods,
&mut forward,
&mut strafe,
&mut jump,
&mut sneak,
);
assert_eq!((forward, strafe, jump, sneak), (-0.5, 0.25, true, true));
}
+171
View File
@@ -467,6 +467,124 @@ fn upgrade_condition(c: &mut Value) {
{
*c = json!(named);
}
match c["type"].as_str() {
Some("minecraft:entity_properties") => {
if let Some(p) = c.get_mut("predicate") {
upgrade_entity_predicate(p);
}
}
Some("minecraft:damage_source_properties") => {
for key in ["direct_entity", "source_entity"] {
if let Some(p) = c["predicate"].get_mut(key) {
upgrade_entity_predicate(p);
}
}
}
_ => {}
}
}
/// The fields of a 1.21 entity predicate that are, in 26.3, sub-predicates
/// keyed by their registry name (`minecraft:<field>`), as 26.3's own data
/// writes them (`"minecraft:flags": {"is_on_fire": true}`).
const ENTITY_FIELDS: [&str; 15] = [
"distance",
"movement",
"location",
"stepping_on",
"movement_affected_by",
"effects",
"nbt",
"flags",
"equipment",
"periodic_tick",
"team",
"slots",
"components",
"predicates",
"type_specific",
];
/// Entity predicates nested in an entity predicate (1.21 field names).
const ENTITY_NESTED: [&str; 3] = ["vehicle", "passenger", "targeted_entity"];
/// Kinds of 1.21's `type_specific` whose `variant` 26.3 matches as a component
/// (`"minecraft:components": {"minecraft:cat/variant": ...}`).
const VARIANT_KINDS: [&str; 14] = [
"axolotl",
"cat",
"chicken",
"cow",
"fox",
"frog",
"horse",
"llama",
"mooshroom",
"painting",
"parrot",
"pig",
"rabbit",
"wolf",
];
/// A 1.21 entity predicate in 26.3's shape, in place: `type` is
/// `minecraft:entity_type`, every other field `minecraft:<field>`, `vehicle`,
/// `passenger` and `targeted_entity` recursively; `type_specific {"type": k,
/// ...}` is `minecraft:type_specific/<k>` (`slime` is `cube_mob`), a variant
/// kind's `variant` and a sheep's `color` go to `minecraft:components`. The
/// game's loot code reads only these keys, so an old-shaped predicate never
/// matched. A predicate already in 26.3's shape stays as it is.
pub(crate) fn upgrade_entity_predicate(p: &mut Value) {
let Some(o) = p.as_object_mut() else {
return;
};
let key = |f: &str| format!("minecraft:{f}");
if let Some(t) = o.remove("type") {
o.insert(key("entity_type"), t);
}
for f in ENTITY_NESTED {
if let Some(mut inner) = o.remove(f) {
upgrade_entity_predicate(&mut inner);
o.insert(key(f), inner);
}
}
for f in ENTITY_FIELDS {
if let Some(v) = o.remove(f) {
o.insert(key(f), v);
}
}
let Some(Value::Object(mut ts)) = o.remove("minecraft:type_specific") else {
return;
};
let kind = ts.remove("type");
let kind = kind.as_ref().and_then(Value::as_str).unwrap_or("any");
let kind = match kind.trim_start_matches("minecraft:") {
"slime" => "cube_mob",
k => k,
};
let component = match kind {
k if VARIANT_KINDS.contains(&k) => {
ts.remove("variant").map(|v| (format!("{k}/variant"), v))
}
"sheep" => ts.remove("color").map(|v| ("sheep/color".to_owned(), v)),
_ => None,
};
if let Some((name, v)) = component {
let components = o.entry(key("components")).or_insert_with(|| json!({}));
if let Some(c) = components.as_object_mut() {
c.insert(key(&name), v);
}
}
// A player's `looking_at`, a lightning bolt's `entity_struck`.
for f in ["looking_at", "entity_struck"] {
if let Some(v) = ts.get_mut(f) {
upgrade_entity_predicate(v);
}
}
// A variant kind with nothing else to match is the component alone.
if !ts.is_empty() || !(kind == "any" || VARIANT_KINDS.contains(&kind)) {
o.insert(key(&format!("type_specific/{kind}")), Value::Object(ts));
}
}
/// 26.3's named tool conditions for the 1.21.1 predicates they replace.
@@ -1424,6 +1542,59 @@ mod tests {
assert_eq!(new, glass);
}
/// Entity predicates in 26.3's keys, as its own tables write them; the
/// 1.21 shape never matched in the game's loot code.
#[test]
fn entity_predicates_get_26_3_keys() {
let mut t = json!({"pools": [{"rolls": 1, "entries": [{"type": "minecraft:item", "name": "m:hide",
"functions": [{"function": "minecraft:furnace_smelt", "conditions": [{
"condition": "minecraft:entity_properties", "entity": "this",
"predicate": {"flags": {"is_on_fire": true}}}]}],
"conditions": [
{"condition": "minecraft:entity_properties", "entity": "this",
"predicate": {"type": "minecraft:cow", "vehicle": {"type": "#minecraft:boats"},
"type_specific": {"type": "minecraft:frog", "variant": "minecraft:warm"}}},
{"condition": "minecraft:damage_source_properties", "predicate": {
"source_entity": {"type": "minecraft:player", "equipment": {"mainhand": {"items": "minecraft:shears"}}},
"is_direct": true}},
{"condition": "minecraft:entity_properties", "entity": "attacker",
"predicate": {"type_specific": {"type": "minecraft:slime", "size": {"min": 2}}}},
{"condition": "minecraft:entity_properties", "entity": "this",
"predicate": {"type_specific": {"type": "sheep", "sheared": false, "color": "black"}}}
]}]}]});
upgrade_loot(&mut t);
let e = &t["pools"][0]["entries"][0];
assert_eq!(
e["modifier"][0]["condition"]["predicate"],
json!({"minecraft:flags": {"is_on_fire": true}})
);
let c = &e["condition"]["terms"];
assert_eq!(
c[0]["predicate"],
json!({"minecraft:entity_type": "minecraft:cow",
"minecraft:vehicle": {"minecraft:entity_type": "#minecraft:boats"},
"minecraft:components": {"minecraft:frog/variant": "minecraft:warm"}})
);
assert_eq!(
c[1]["predicate"],
json!({"source_entity": {"minecraft:entity_type": "minecraft:player",
"minecraft:equipment": {"mainhand": {"items": "minecraft:shears"}}}, "is_direct": true})
);
assert_eq!(
c[2]["predicate"],
json!({"minecraft:type_specific/cube_mob": {"size": {"min": 2}}})
);
assert_eq!(
c[3]["predicate"],
json!({"minecraft:type_specific/sheep": {"sheared": false},
"minecraft:components": {"minecraft:sheep/color": "black"}})
);
// Already 26.3's shape: unchanged.
let again = t.clone();
upgrade_loot(&mut t);
assert_eq!(t, again);
}
#[test]
fn types_bare_number_ranges() {
// When Dungeons Arise's and Structory's chests: rolls and counts as
+99 -4
View File
@@ -42,10 +42,12 @@ pub fn read(path: &Path) -> Result<JarInfo, String> {
zip.by_name(name).ok()?.read_to_string(&mut s).ok()?;
Some(s)
};
let mut info = if let Some(t) = text(&mut zip, "META-INF/neoforge.mods.toml")
.or_else(|| text(&mut zip, "META-INF/mods.toml"))
{
mods_toml(&t)?
let tomls: Vec<String> = ["META-INF/neoforge.mods.toml", "META-INF/mods.toml"]
.iter()
.filter_map(|n| text(&mut zip, n))
.collect();
let mut info = if !tomls.is_empty() {
metadata(&tomls)?
} else if let Some(t) = text(&mut zip, "fabric.mod.json") {
fabric(&t)?
} else if let Some(t) = text(&mut zip, "mcmod.info") {
@@ -84,6 +86,71 @@ fn manifest_value(manifest: &str, key: &str) -> Option<String> {
.filter(|v| !v.is_empty())
}
/// The jar's mod from its metadata files (`neoforge.mods.toml` first, then
/// `mods.toml`). A jar built for one loader may carry the other loader's file
/// as an unfilled template (`modId = "${id}"`, `[[dependencies.${id}]]`), which
/// its own loader never reads: the first file that parses and names a real mod
/// id is the one. When none parses, the first file is read line by line
/// ([`mods_toml_lines`]), as a loader that reads past what it does not need.
pub fn metadata(tomls: &[String]) -> Result<JarInfo, String> {
let real = |i: &JarInfo| !i.mod_id.is_empty() && !i.mod_id.contains("${");
let mut first_error = None;
for t in tomls {
match mods_toml(t) {
Ok(i) if real(&i) => return Ok(i),
Ok(_) => {}
Err(e) => {
first_error.get_or_insert(e);
}
}
}
tomls
.iter()
.map(|t| mods_toml_lines(t))
.find(real)
.ok_or_else(|| first_error.unwrap_or_else(|| "mods.toml: no [[mods]] modId".into()))
}
/// The first `[[mods]]` table's `modId`, `displayName` and `version` and the
/// top-level `license`, read line by line (`key = "value"`, `#` comments), for
/// a metadata file a TOML parser refuses. Dependencies are not read.
pub fn mods_toml_lines(text: &str) -> JarInfo {
let mut info = JarInfo::default();
// 0: top level, 1: the first [[mods]], 2: past it.
let mut section = 0;
for line in text.lines() {
let line = line.trim();
if line.starts_with('[') {
let name = line.trim_matches(['[', ']']).trim();
section = match (section, name) {
(0, "mods") => 1,
_ => 2,
};
continue;
}
let Some((key, value)) = line.split_once('=') else {
continue;
};
let value = value.trim();
let Some(value) = value
.strip_prefix('"')
.and_then(|v| v.split_once('"'))
.or_else(|| value.strip_prefix('\'').and_then(|v| v.split_once('\'')))
.map(|(v, _)| v.to_owned())
else {
continue;
};
match (section, key.trim()) {
(0, "license") => info.license = value,
(1, "modId") if info.mod_id.is_empty() => info.mod_id = value,
(1, "displayName") => info.name = value,
(1, "version") => info.version = value,
_ => {}
}
}
info
}
/// `[[mods]]` (the first one is the jar's mod), the top-level `license`, and
/// `[[dependencies.<id>]]` with `type = "required"` (or the older
/// `mandatory = true`).
@@ -295,6 +362,34 @@ mandatory = true
assert_eq!(info.depends, ["geckolib", "curios"]);
}
/// Drip Sounds' Forge jar carries an unfilled NeoForge template beside its
/// `mods.toml`; a file no parser reads still gives its id line by line.
#[test]
fn templates_and_broken_files_are_read_tolerantly() {
let template = "license = \"LGPL\"\n[[mods]]\nmodId = \"${id}\"\nversion = \"${version}\"\n\n[[dependencies.${id}]]\nmodId = \"minecraft\"\n";
let forge = "license = \"LGPL-3.0-only\"\n[[mods]]\nmodId = \"waterdripsound\"\ndisplayName = \"Drip Sounds\"\nversion = \"0.6.3\"\n[[dependencies.waterdripsound]]\nmodId = \"cloth_config\"\nmandatory = true\n";
assert!(mods_toml(template).is_err());
let info = metadata(&[template.to_owned(), forge.to_owned()]).unwrap();
assert_eq!(
(info.mod_id.as_str(), info.version.as_str()),
("waterdripsound", "0.6.3")
);
assert_eq!(info.depends, ["cloth_config"]);
// Nothing parses: the lines still name the mod (a duplicate key is a TOML error).
let broken = "license = 'MIT' # x\n[[mods]]\nmodId = \"ruby\"\nmodId = \"ruby\"\nversion = \"2\"\ndisplayName = \"Ruby\"\n[[mods]]\nmodId = \"other\"\n";
assert!(mods_toml(broken).is_err());
let info = metadata(&[template.to_owned(), broken.to_owned()]).unwrap();
assert_eq!(
(
info.mod_id.as_str(),
info.name.as_str(),
info.license.as_str()
),
("ruby", "Ruby", "MIT")
);
assert!(metadata(&[template.to_owned()]).is_err());
}
#[test]
fn reads_fabric_metadata() {
let info = fabric(
+4
View File
@@ -16,5 +16,9 @@ libc = "0.2"
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Graphics_Gdi", "Win32_System_Console", "Win32_System_IO", "Win32_System_Memory", "Win32_System_ProcessStatus", "Win32_System_Threading", "Win32_System_WindowsProgramming", "Win32_UI_WindowsAndMessaging"] }
[features]
# Counts live heap bytes by startup step and thread (`CensusAllocator`); for measuring only.
alloc-census = []
[lints]
workspace = true
+269
View File
@@ -0,0 +1,269 @@
//! Who holds the heap, for `horizon-ctl memory` (feature `alloc-census`, for
//! measuring only): mimalloc with a tag in front of each block, and the live
//! bytes of each tag. A block's tag is what ran on its thread when it was
//! allocated: the innermost [`tag`] (a startup step, a timed span or a pool
//! job: `startup::time`, `spans::time` and `jobs` set them), else the
//! thread's name. A block keeps its tag when another thread frees or grows
//! it, so a column generated by a pool job and kept by the server thread
//! counts for the job.
//!
//! Each block costs a header of 16 bytes (its alignment when larger) and an
//! atomic add: heaptrack's interpreter needed more than 7 GB for the game
//! (18 GB with line tables), this needs nothing beside it.
use std::{
alloc::{GlobalAlloc, Layout},
cell::Cell,
sync::atomic::{AtomicIsize, AtomicPtr, AtomicUsize, Ordering::Relaxed},
};
const TAGS: usize = 512;
/// Blocks allocated while this thread names itself, and past the table's end.
const UNTAGGED: u32 = 0;
const NONE: u32 = u32::MAX;
static LIVE: [AtomicIsize; TAGS] = [const { AtomicIsize::new(0) }; TAGS];
static NAME_PTR: [AtomicPtr<u8>; TAGS] = [const { AtomicPtr::new(std::ptr::null_mut()) }; TAGS];
static NAME_LEN: [AtomicUsize; TAGS] = [const { AtomicUsize::new(0) }; TAGS];
/// The next free slot; slot 0 is [`UNTAGGED`].
static NEXT: AtomicUsize = AtomicUsize::new(1);
thread_local! {
static STEP: Cell<u32> = const { Cell::new(NONE) };
static THREAD: Cell<u32> = const { Cell::new(NONE) };
/// Allocations this thread made before its tag settled: std names a
/// thread after its first few, so the name is read again until then.
static NAMING: Cell<u32> = const { Cell::new(0) };
static BUSY: Cell<bool> = const { Cell::new(false) };
}
/// The slot of `name`, registering it. A race may give one name two slots;
/// [`census`] adds them up.
fn slot(name: &'static str) -> u32 {
let n = NEXT.load(Relaxed).min(TAGS);
for i in 1..n {
let ptr = NAME_PTR[i].load(Relaxed);
if !ptr.is_null() && NAME_LEN[i].load(Relaxed) == name.len() {
// SAFETY: the slot holds a leaked or static string of that length.
let s = unsafe { std::slice::from_raw_parts(ptr, name.len()) };
if s == name.as_bytes() {
return i as u32;
}
}
}
let i = NEXT.fetch_add(1, Relaxed);
if i >= TAGS {
return UNTAGGED;
}
NAME_LEN[i].store(name.len(), Relaxed);
NAME_PTR[i].store(name.as_ptr().cast_mut(), Relaxed);
i as u32
}
/// The OS name of the calling thread, read without allocating.
#[cfg(any(target_vendor = "apple", target_os = "linux"))]
fn thread_name(buf: &mut [u8; 64]) -> &[u8] {
// SAFETY: writes at most `buf.len()` bytes, NUL included, into `buf`.
let ok = unsafe {
libc::pthread_getname_np(libc::pthread_self(), buf.as_mut_ptr().cast(), buf.len())
} == 0;
let len = buf.iter().position(|&b| b == 0).unwrap_or(0);
if ok { &buf[..len] } else { &[] }
}
#[cfg(not(any(target_vendor = "apple", target_os = "linux")))]
fn thread_name(_: &mut [u8; 64]) -> &[u8] {
&[]
}
/// The calling thread's own tag: its name, looked up again for its first
/// allocations. Allocations made while it registers a name are untagged.
fn thread_tag() -> u32 {
let known = THREAD.get();
if known != NONE && NAMING.get() >= 256 {
return known;
}
if BUSY.replace(true) {
return UNTAGGED;
}
NAMING.set(NAMING.get().saturating_add(1));
let mut buf = [0u8; 64];
let name = match std::str::from_utf8(thread_name(&mut buf)) {
Ok("") | Err(_) => "unnamed thread",
Ok(name) => name,
};
let tag = if known != NONE && slot_name(known) == Some(name) {
known
} else {
// Thread names are few: each is leaked once.
let name: &'static str = match name {
"unnamed thread" => "unnamed thread",
name => Box::leak(name.to_owned().into_boxed_str()),
};
slot(name)
};
THREAD.set(tag);
BUSY.set(false);
tag
}
fn slot_name(i: u32) -> Option<&'static str> {
let i = i as usize;
let ptr = NAME_PTR.get(i)?.load(Relaxed);
if ptr.is_null() {
return None;
}
// SAFETY: registered slots hold leaked or static UTF-8 of that length.
Some(unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(ptr, NAME_LEN[i].load(Relaxed)))
})
}
fn current() -> u32 {
match STEP.get() {
NONE => thread_tag(),
step => step,
}
}
/// While it lives, the calling thread's blocks are tagged `name`.
pub struct Tag(u32);
impl Drop for Tag {
fn drop(&mut self) {
STEP.set(self.0);
}
}
/// Tag the calling thread's blocks `name` until the guard drops.
pub fn tag(name: &'static str) -> Tag {
let previous = STEP.get();
let was = BUSY.replace(true);
let tag = slot(name);
BUSY.set(was);
STEP.set(tag);
Tag(previous)
}
/// Live bytes by tag, largest first.
pub fn census() -> Vec<(&'static str, isize)> {
let mut by_name: Vec<(&'static str, isize)> = Vec::new();
for i in 0..NEXT.load(Relaxed).min(TAGS) {
let bytes = LIVE[i].load(Relaxed);
let name = slot_name(i as u32).unwrap_or("untagged");
match by_name.iter_mut().find(|(n, _)| *n == name) {
Some(entry) => entry.1 += bytes,
None => by_name.push((name, bytes)),
}
}
by_name.sort_by_key(|(_, bytes)| std::cmp::Reverse(*bytes));
by_name
}
/// mimalloc with a tag in front of each block (see the module).
pub struct CensusAllocator;
/// The header in front of a block of `layout`: 16 bytes, or its alignment.
fn header(layout: Layout) -> usize {
layout.align().max(16)
}
// SAFETY: forwards every call to mimalloc with the header added; the tag sits
// in the header's last four bytes, inside the block mimalloc returned.
unsafe impl GlobalAlloc for CensusAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let h = header(layout);
let Ok(outer) = Layout::from_size_align(layout.size() + h, layout.align().max(16)) else {
return std::ptr::null_mut();
};
// SAFETY: `outer` has a nonzero size.
let base = unsafe { mimalloc::MiMalloc.alloc(outer) };
if base.is_null() {
return base;
}
let tag = current();
LIVE[tag as usize].fetch_add(layout.size() as isize, Relaxed);
// SAFETY: `h` bytes precede the caller's block inside the allocation.
unsafe {
let block = base.add(h);
block.sub(4).cast::<u32>().write_unaligned(tag);
block
}
}
unsafe fn dealloc(&self, block: *mut u8, layout: Layout) {
let h = header(layout);
// SAFETY: `block` came from `alloc` with this layout.
unsafe {
let tag = block.sub(4).cast::<u32>().read_unaligned();
LIVE[tag as usize].fetch_sub(layout.size() as isize, Relaxed);
let outer =
Layout::from_size_align_unchecked(layout.size() + h, layout.align().max(16));
mimalloc::MiMalloc.dealloc(block.sub(h), outer);
}
}
unsafe fn realloc(&self, block: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let h = header(layout);
// SAFETY: `block` came from `alloc` with this layout; the header moves with it.
unsafe {
let outer =
Layout::from_size_align_unchecked(layout.size() + h, layout.align().max(16));
let tag = block.sub(4).cast::<u32>().read_unaligned();
let base = mimalloc::MiMalloc.realloc(block.sub(h), outer, new_size + h);
if base.is_null() {
return base;
}
LIVE[tag as usize].fetch_add(new_size as isize - layout.size() as isize, Relaxed);
base.add(h)
}
}
}
#[cfg(test)]
mod tests {
use super::{CensusAllocator, census, tag};
use std::alloc::{GlobalAlloc, Layout};
fn live(name: &str) -> isize {
census().iter().find(|(n, _)| *n == name).map_or(0, |e| e.1)
}
#[test]
fn a_block_counts_for_its_step_until_another_thread_frees_it() {
let layout = Layout::from_size_align(1 << 20, 64).unwrap();
let block = {
let _tag = tag("test.step");
// SAFETY: a nonzero layout.
unsafe { CensusAllocator.alloc(layout) }
};
assert_eq!(live("test.step"), 1 << 20);
// SAFETY: `block` came from this allocator with `layout`.
let block = unsafe { CensusAllocator.realloc(block, layout, 3 << 20) } as usize;
assert_eq!(live("test.step"), 3 << 20, "growing keeps the step's tag");
let grown = Layout::from_size_align(3 << 20, 64).unwrap();
std::thread::spawn(move || {
let _other = tag("test.other");
// SAFETY: `block` came from this allocator with `grown`.
unsafe { CensusAllocator.dealloc(block as *mut u8, grown) };
})
.join()
.unwrap();
assert_eq!(live("test.step"), 0);
assert_eq!(live("test.other"), 0);
}
#[test]
fn without_a_step_a_block_counts_for_its_thread() {
let layout = Layout::from_size_align(4096, 8).unwrap();
let (name, block) = std::thread::Builder::new()
.name("census-test".into())
.spawn(move || {
// SAFETY: a nonzero layout.
let block = unsafe { CensusAllocator.alloc(layout) } as usize;
(std::thread::current().name().unwrap().to_owned(), block)
})
.unwrap()
.join()
.unwrap();
// Linux keeps 15 bytes of a thread's name; the test's is shorter.
assert_eq!(live(&name), 4096);
// SAFETY: `block` came from this allocator with `layout`.
unsafe { CensusAllocator.dealloc(block as *mut u8, layout) };
assert_eq!(live(&name), 0);
}
}
+9 -1
View File
@@ -65,6 +65,10 @@ pub trait Source: Send + Sync {
fn idle(&self, _worker: usize) {}
/// Every worker is idle: free what the workers share.
fn all_idle(&self) {}
/// What a heap-census build tags its jobs' allocations (`crate::heap_tag`).
fn label(&self) -> &'static str {
std::any::type_name::<Self>()
}
}
/// How long a worker waits with no job before it frees its buffers.
@@ -384,7 +388,11 @@ fn work(inner: &Inner, worker: usize) {
}
// A panicking job loses its own work, not the worker: the panic
// hook has already reported it.
let _ = catch_unwind(AssertUnwindSafe(|| entry.source.run(worker)));
let _ = catch_unwind(AssertUnwindSafe(|| {
let _tag =
crate::HEAP_CENSUS.then(|| crate::heap_census::tag(entry.source.label()));
entry.source.run(worker)
}));
if far {
inner.far.fetch_sub(1, Ordering::SeqCst);
}
+13
View File
@@ -4,6 +4,7 @@
/// The game window's boot screen, before wgpu's first frame paints over it.
#[cfg(target_os = "windows")]
pub mod boot_paint;
pub mod heap_census;
pub mod jobs;
pub mod memory_limit;
@@ -207,6 +208,18 @@ pub fn catch_stop_signals() {}
/// 16% of a chunk's generation time), mimalloc keeps a heap per thread.
pub use mimalloc::MiMalloc as Allocator;
/// The global allocator of a build with the `alloc-census` feature: live
/// heap bytes by startup step and thread, for `horizon-ctl memory`.
pub use heap_census::CensusAllocator;
/// Whether this build counts the heap by tag (feature `alloc-census`).
pub const HEAP_CENSUS: bool = cfg!(feature = "alloc-census");
/// Tag the calling thread's allocations `name` while the guard lives, in a
/// build that counts the heap ([`HEAP_CENSUS`]); nothing otherwise.
#[inline]
pub fn heap_tag(name: &'static str) -> Option<heap_census::Tag> {
HEAP_CENSUS.then(|| heap_census::tag(name))
}
/// `mi_option_purge_delay` in mimalloc.h (index 15 in v2 and v3); the bindings
/// don't name it.
const PURGE_DELAY: libmimalloc_sys::mi_option_t = 15;
+4 -4
View File
@@ -14,7 +14,7 @@ use std::{
path::PathBuf,
sync::{
Mutex, OnceLock,
atomic::{AtomicU64, Ordering},
atomic::{AtomicU32, Ordering},
},
time::{SystemTime, UNIX_EPOCH},
};
@@ -24,7 +24,7 @@ const VERSION: u32 = 2;
const FILE_NAME: &str = "behaviours-v2.bin";
const MAX_FILE_BYTES: usize = 128 * 1024 * 1024;
const MAX_DECODED_BYTES: usize = 256 * 1024 * 1024;
static NEXT_TEMP: AtomicU64 = AtomicU64::new(0);
static NEXT_TEMP: AtomicU32 = AtomicU32::new(0);
static DIR: OnceLock<PathBuf> = OnceLock::new();
/// Keep snapshots in `dir` (the client passes `<game folder>/.horizon-cache`).
@@ -776,9 +776,9 @@ fn write(
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::AtomicU32;
static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);
static NEXT_FIXTURE: AtomicU32 = AtomicU32::new(0);
struct FixtureDir(PathBuf);
impl Drop for FixtureDir {
+2 -2
View File
@@ -11,7 +11,7 @@ use std::{
fs::{self, File, OpenOptions},
io::{self, Read, Seek, SeekFrom, Write},
path::{Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
sync::atomic::{AtomicU32, Ordering},
};
const SECTOR: usize = 4096;
@@ -387,7 +387,7 @@ impl RegionFile {
}
type BTreeChanges<'a> = std::collections::BTreeMap<usize, Option<&'a ChunkRecord>>;
static NEXT_TEMP: AtomicU64 = AtomicU64::new(0);
static NEXT_TEMP: AtomicU32 = AtomicU32::new(0);
/// Writes a complete new region to a temporary sibling, then publishes it with
/// an atomic, non-overwriting hard link. Drop rolls back unpublished files.
+2 -2
View File
@@ -10,10 +10,10 @@ use std::{
fs,
io::{Seek, SeekFrom, Write},
path::{Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
sync::atomic::{AtomicU32, Ordering},
};
static NEXT: AtomicU64 = AtomicU64::new(0);
static NEXT: AtomicU32 = AtomicU32::new(0);
struct Directory(PathBuf);
impl Directory {
fn new() -> Self {
+2
View File
@@ -841,6 +841,8 @@ API 108 adds `native_server_data_loaded(reload)` fires once the server's data ta
API 109 adds `native_compute_fov(partial, camera, degrees)` receives the computed world FOV after settings, ordinary FOV effects and zoom, before projection/Nausea. `camera` is its world position and `partial` the interpolation fraction. Mods can narrow or widen the angle; only finite values in 1..=179 degrees are accepted, otherwise the original computed angle is used. It runs for world rendering and near-plane overlays, not just once per frame. Hand/HUD FOV and the title panorama keep their existing paths. Bundles need "hooks": 109.
`native_movement_input(forward, strafe, jump, sneak)` runs on the client each local player tick after input is read and before it is applied. Mods may change all four values; positive strafe is left. The server receives direction flags, not scalar magnitudes, so remote servers may correct modified movement. Bundles need "hooks": @API:movement_input@.
API 110 adds mobs drawn by client mods: `native_mob_submit(mob, draw, drawn)` is asked each frame for every mob in view but players,
with its id, kind, custom name and the frame's body and head turns and walk cycle; a mod draws a model of its own with the
block entity drawing helpers under `draw`'s pose (at the feet, turned with the body) and sets `drawn` to leave out the vanilla
+3
View File
@@ -214,6 +214,9 @@
"Starting": "Startet",
"Show in folder": "Im Ordner anzeigen",
"Settings": "Einstellungen",
"Settings were not saved.": "Die Einstellungen wurden nicht gespeichert.",
"Your changes are still in this window. Retry to save them. {error}": "Deine Änderungen sind noch in diesem Fenster vorhanden. Versuche erneut, sie zu speichern. {error}",
"Retry": "Erneut versuchen",
"Doesn't port": "Wird nicht portiert",
"Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.": "Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.",
"Could not read {name}: {error}": "{name} konnte nicht gelesen werden: {error}",
+3
View File
@@ -214,6 +214,9 @@
"Starting": "Iniciando",
"Show in folder": "Mostrar en la carpeta",
"Settings": "Ajustes",
"Settings were not saved.": "No se han guardado los ajustes.",
"Your changes are still in this window. Retry to save them. {error}": "Tus cambios siguen en esta ventana. Vuelve a intentarlo para guardarlos. {error}",
"Retry": "Reintentar",
"Doesn't port": "No se porta",
"Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.": "Nunca se envía: el nombre o UUID de tu cuenta, nombres de mundos, direcciones de servidores, chat ni tu dirección IP. Cada informe lleva solo la fecha del día, bajo un ID de instalación aleatorio que puedes eliminar.",
"Could not read {name}: {error}": "No se pudo leer {name}: {error}",
+3
View File
@@ -214,6 +214,9 @@
"Starting": "Démarrage",
"Show in folder": "Afficher dans le dossier",
"Settings": "Paramètres",
"Settings were not saved.": "Les paramètres n’ont pas été enregistrés.",
"Your changes are still in this window. Retry to save them. {error}": "Vos modifications sont toujours dans cette fenêtre. Réessayez pour les enregistrer. {error}",
"Retry": "Réessayer",
"Doesn't port": "Non porté",
"Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.": "Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.",
"Could not read {name}: {error}": "Impossible de lire {name} : {error}",
+3
View File
@@ -5,6 +5,9 @@
"Versions": "バージョン",
"Patch notes": "更新履歴",
"Settings": "設定",
"Settings were not saved.": "設定を保存できませんでした。",
"Your changes are still in this window. Retry to save them. {error}": "変更内容はこのウィンドウに残っています。もう一度保存をお試しください。{error}",
"Retry": "再試行",
"Accounts, updates, and the launcher itself.": "アカウント、アップデート、ランチャーの設定",
"Account": "アカウント",
"Updates": "アップデート",
+3
View File
@@ -214,6 +214,9 @@
"Starting": "Uruchamianie",
"Show in folder": "Pokaż w folderze",
"Settings": "Ustawienia",
"Settings were not saved.": "Nie udało się zapisać ustawień.",
"Your changes are still in this window. Retry to save them. {error}": "Twoje zmiany nadal są w tym oknie. Spróbuj ponownie je zapisać. {error}",
"Retry": "Spróbuj ponownie",
"Doesn't port": "Nieprzenoszone",
"Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.": "Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.",
"Could not read {name}: {error}": "Nie udało się odczytać {name}: {error}",
+3
View File
@@ -5,6 +5,9 @@
"Versions": "Versões",
"Patch notes": "Notas da atualização",
"Settings": "Configurações",
"Settings were not saved.": "As configurações não foram salvas.",
"Your changes are still in this window. Retry to save them. {error}": "Suas alterações ainda estão nesta janela. Tente novamente para salvá-las. {error}",
"Retry": "Tentar novamente",
"Accounts, updates, and the launcher itself.": "Contas, atualizações e o próprio launcher.",
"Account": "Conta",
"Updates": "Atualizações",
+3
View File
@@ -214,6 +214,9 @@
"Starting": "Запуск",
"Show in folder": "Показать в папке",
"Settings": "Настройки",
"Settings were not saved.": "Не удалось сохранить настройки.",
"Your changes are still in this window. Retry to save them. {error}": "Ваши изменения остаются в этом окне. Попробуйте сохранить их ещё раз. {error}",
"Retry": "Повторить",
"Doesn't port": "Не портируется",
"Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.": "Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.",
"Could not read {name}: {error}": "Не удалось прочитать {name}: {error}",
+3
View File
@@ -5,6 +5,9 @@
"Versions": "Sürümler",
"Patch notes": "Sürüm Notları",
"Settings": "Ayarlar",
"Settings were not saved.": "Ayarlar kaydedilemedi.",
"Your changes are still in this window. Retry to save them. {error}": "Değişiklikleriniz hâlâ bu pencerede duruyor. Kaydetmek için tekrar deneyin. {error}",
"Retry": "Tekrar dene",
"Accounts, updates, and the launcher itself.": "Hesaplar, güncellemeler ve başlatıcının kendisi.",
"Account": "Hesap",
"Updates": "Güncellemeler",
+3
View File
@@ -214,6 +214,9 @@
"Starting": "Запуск",
"Show in folder": "Показати в папці",
"Settings": "Налаштування",
"Settings were not saved.": "Не вдалося зберегти налаштування.",
"Your changes are still in this window. Retry to save them. {error}": "Ваші зміни залишаються в цьому вікні. Спробуйте зберегти їх ще раз. {error}",
"Retry": "Повторити",
"Doesn't port": "Не портується",
"Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.": "Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.",
"Could not read {name}: {error}": "Не вдалося прочитати {name}: {error}",
+3
View File
@@ -214,6 +214,9 @@
"Starting": "正在启动",
"Show in folder": "在文件夹中显示",
"Settings": "设置",
"Settings were not saved.": "设置未保存。",
"Your changes are still in this window. Retry to save them. {error}": "你的更改仍保留在此窗口中。请重试以保存更改。{error}",
"Retry": "重试",
"Doesn't port": "未移植",
"Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.": "Never sent: your account name or UUID, world names, server addresses, chat, or your IP address. Each report is dated to the day, under a random install ID you can delete.",
"Could not read {name}: {error}": "无法读取 {name}:{error}",
+11 -5
View File
@@ -2317,7 +2317,7 @@ pub fn remove_version(tag: &str) -> Result<(), String> {
let mut settings = Settings::load();
if settings.selected.as_deref() == Some(tag) {
settings.selected = installed().first().cloned();
settings.save();
settings.save()?;
}
Ok(())
}
@@ -2666,7 +2666,7 @@ pub fn install_tag(tag: &str, as_update: bool, progress: impl Fn(f32)) -> Result
let mut settings = Settings::load();
if !as_update || select_after_update(settings.selected.as_deref(), &before) {
settings.selected = Some(release.tag.clone());
settings.save();
settings.save()?;
}
if as_update && release.nightly.is_some() {
crate::nightly::after_update(&release.tag);
@@ -3400,19 +3400,25 @@ impl Settings {
pub fn keep_newest(&self) -> usize {
self.keep_versions.unwrap_or(KEEP_NEWEST).max(1)
}
pub fn save(&self) {
pub fn save(&self) -> Result<(), String> {
// The window and background jobs share the process's temporary file.
static SAVE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _guard = SAVE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
// A write that stops halfway leaves settings.json unreadable, and `load` answers
// defaults for it: the file is replaced only once it is whole.
let dir = data_dir();
let path = dir.join("settings.json");
let tmp = dir.join(format!("settings.{}.tmp", std::process::id()));
let json =
serde_json::to_vec_pretty(self).map_err(|e| format!("{}: {e}", path.display()))?;
let written = fs::create_dir_all(&dir)
.and_then(|()| fs::write(&tmp, serde_json::to_string_pretty(self).unwrap()))
.and_then(|()| fs::write(&tmp, json))
.and_then(|()| fs::rename(&tmp, &path));
if let Err(e) = written {
let _ = fs::remove_file(&tmp);
eprintln!("Horizon: settings.json is not saved: {e}");
return Err(format!("{}: {e}", path.display()));
}
Ok(())
}
}
+1 -1
View File
@@ -114,7 +114,7 @@ pub fn import(path: &Path, requested: Option<&str>) -> Result<String, String> {
// Read again: the window may have saved other settings during the copy.
let mut settings = backend::Settings::load();
settings.selected = Some(tag.clone());
settings.save();
settings.save()?;
Ok(tag)
}
+20 -19
View File
@@ -1599,7 +1599,7 @@ fn execute(command: Cmd, as_json: bool) -> Result<(), String> {
if let Some(key) = key {
let value = value.ok_or("settings KEY VALUE: the value is missing")?;
set_setting(&mut settings, &key, &value)?;
settings.save();
settings.save()?;
horizon_launcher::proxy::announce(settings.proxy.clone());
}
let all = settings_json(&settings);
@@ -1615,7 +1615,7 @@ fn execute(command: Cmd, as_json: bool) -> Result<(), String> {
let mut settings = backend::Settings::load();
if let Some(line) = args {
settings.game_args = line;
settings.save();
settings.save()?;
}
let line = settings.game_args;
if as_json {
@@ -1677,7 +1677,7 @@ fn execute(command: Cmd, as_json: bool) -> Result<(), String> {
Cmd::DesktopEntry { remove } => {
let mut settings = backend::Settings::load();
settings.desktop_entry = Some(!remove);
settings.save();
settings.save()?;
if remove {
horizon_launcher::desktop::remove()?;
println!("Removed Horizon from the applications menu.");
@@ -2273,7 +2273,15 @@ fn github_native(
.collect::<Result<Vec<_>, _>>()
});
github_mods::discard(found);
for o in done? {
let done = done?;
// The window's trust notice, once. Nothing is turned on: native mods are always on.
let mut settings = backend::Settings::load();
if !settings.native_mods_noticed {
eprintln!("{}", native_status::NOTICE);
settings.native_mods_noticed = true;
settings.save()?;
}
for o in done {
println!(
"Installed {} {} from GitHub ({}), sha256 {}.",
o.id,
@@ -2282,13 +2290,6 @@ fn github_native(
o.sha256
);
}
// The window's trust notice, once. Nothing is turned on: native mods are always on.
let mut settings = backend::Settings::load();
if !settings.native_mods_noticed {
eprintln!("{}", native_status::NOTICE);
settings.native_mods_noticed = true;
settings.save();
}
println!("Build it next: horizon-launcher native-build --trust-native-code");
println!(
"Developing a mod? horizon-launcher settings native-profile dev (or native-build --profile dev): a mod edit rebuilds about 6× faster, while game logic runs up to about twice as slow. Use it while developing, not for playing."
@@ -2575,6 +2576,14 @@ fn mods(action: ModsCmd, as_json: bool) -> Result<(), String> {
Some(target) => java_instance::install(&*source, target, &p, &home, &line)?,
None => installed::install(&*source, &p, everything, &home, &line)?,
};
// The first native mod: the window's trust notice, once.
let mut settings = backend::Settings::load();
let native = done.iter().any(|e| native_status::is_native_file(&e.file));
if native && !settings.native_mods_noticed {
eprintln!("{}", native_status::NOTICE);
settings.native_mods_noticed = true;
settings.save()?;
}
if as_json {
json(done.iter().map(entry_json).collect());
}
@@ -2584,14 +2593,6 @@ fn mods(action: ModsCmd, as_json: bool) -> Result<(), String> {
println!(" not ported: {s}");
}
}
// The first native mod: the window's trust notice, once.
let mut settings = backend::Settings::load();
let native = done.iter().any(|e| native_status::is_native_file(&e.file));
if native && !settings.native_mods_noticed {
eprintln!("{}", native_status::NOTICE);
settings.native_mods_noticed = true;
settings.save();
}
}
ModsCmd::Update { project, check, .. } => {
let list = installed::Installed::load(&home).entries;
+1 -1
View File
@@ -128,7 +128,7 @@ impl Launcher {
// The question and Settings say what the "I'm playing" ping holds, beside the switches.
if !self.settings.ping_notice_seen {
self.settings.ping_notice_seen = true;
self.settings.save();
self.save_settings();
}
self.collection.view = None;
}
+1 -1
View File
@@ -30,7 +30,7 @@ impl Launcher {
let toggle = switch("dev-offline", self.settings.dev_offline, &t).on_click(cx.listener(
|this, _: &ClickEvent, _, cx| {
this.settings.dev_offline = !this.settings.dev_offline;
this.settings.save();
this.save_settings();
cx.notify();
},
));
+1 -1
View File
@@ -125,7 +125,7 @@ impl Launcher {
self.github.newer.clear();
// The review above was the trust notice; the Mods page offers Build.
self.settings.native_mods_noticed = true;
self.settings.save();
self.save_settings();
}
self.instance_mods_changed();
self.github.message = Some(result);
+1 -1
View File
@@ -183,7 +183,7 @@ impl Launcher {
.child(tr("Got it"))
.on_click(cx.listener(|this, _: &ClickEvent, _, cx| {
this.settings.ping_notice_seen = true;
this.settings.save();
this.save_settings();
cx.notify();
})),
),
+14 -5
View File
@@ -380,6 +380,13 @@ fn find_in(all: &[Instance], query: &str) -> Result<Instance, String> {
/// and forgets it: its choices, and the selection when Play started it. Horizon's own instance,
/// the game's home, stays.
pub fn delete(query: &str) -> Result<Instance, String> {
let (gone, saved) = delete_with_selection(query)?;
saved?;
Ok(gone)
}
/// A completed deletion stays completed even if saving the new selection fails.
pub fn delete_with_selection(query: &str) -> Result<(Instance, Result<(), String>), String> {
let gone = delete_in(
&backend::game_home(),
&backend::data_dir(),
@@ -387,11 +394,13 @@ pub fn delete(query: &str) -> Result<Instance, String> {
&crate::desktop_seams::to_trash,
)?;
let mut settings = backend::Settings::load();
if settings.instance.as_ref() == Some(&gone.id) {
let saved = if settings.instance.as_ref() == Some(&gone.id) {
settings.instance = None;
settings.save();
}
Ok(gone)
settings.save()
} else {
Ok(())
};
Ok((gone, saved))
}
fn delete_in(
@@ -429,7 +438,7 @@ pub fn select(query: &str) -> Result<Instance, String> {
let instance = find(query)?;
let mut settings = backend::Settings::load();
settings.instance = (instance.kind != Kind::Default).then(|| instance.id.clone());
settings.save();
settings.save()?;
Ok(instance)
}
+7 -4
View File
@@ -116,7 +116,7 @@ impl Launcher {
self.native_update = None;
self.settings.instance =
(instance.kind != InstanceKind::Default).then(|| instance.id.clone());
self.settings.save();
self.save_settings();
}
/// A texture from the selected version's prepared files, its first frame, drawn as pixels.
@@ -849,13 +849,16 @@ impl Launcher {
if self.client.is_some() || self.starting_client.is_some() {
return;
}
match instances::delete(id) {
Ok(_) => {
match instances::delete_with_selection(id) {
Ok((_, saved)) => {
self.instances.confirm_delete = None;
self.instances.delete_error = None;
// Play starts Horizon again; the window's copy of the settings says so too.
if self.settings.instance.as_deref() == Some(id) {
self.settings.instance = None;
self.settings.save();
self.save_settings();
} else if let Err(error) = saved {
self.settings_save_error = Some(error);
}
self.reload_instances();
}
+1 -1
View File
@@ -85,7 +85,7 @@ impl Launcher {
})
.on_click(cx.listener(move |this, _: &ClickEvent, _, cx| {
this.settings.language = choice;
this.settings.save();
this.save_settings();
i18n::set(this.settings.language());
this.language_menu = false;
cx.notify();
+77 -20
View File
@@ -243,6 +243,8 @@ struct Launcher {
/// Why the last picture for Home's background was refused.
background_error: Option<String>,
settings: Settings,
/// The last failed settings write, kept across page changes until a save succeeds.
settings_save_error: Option<String>,
/// The Discord, sponsor and website links: built in until links.json on main has been read.
links: backend::Links,
preparing: bool,
@@ -413,7 +415,7 @@ impl Launcher {
s.partner_seen_secs += 10;
if s.partner_seen_secs % 60 == 0 || s.partner_seen_secs >= partner::SEEN_BEFORE_HIDE
{
s.save();
this.save_settings();
cx.notify();
}
true
@@ -511,6 +513,7 @@ impl Launcher {
proxy_focused: false,
proxy_focus: cx.focus_handle(),
settings,
settings_save_error: None,
links: backend::Links::default(),
preparing: false,
prepare_log: VecDeque::new(),
@@ -636,6 +639,53 @@ impl Launcher {
this
}
/// Keeps the current choices in memory if writing fails; every page offers the same retry.
fn save_settings(&mut self) -> bool {
self.settings_save_error = self.settings.save().err();
self.settings_save_error.is_none()
}
fn settings_save_notice(&self, cx: &mut Context<Self>) -> Option<Div> {
let error = self.settings_save_error.as_ref()?;
let t = self.theme;
Some(
div()
.flex_none()
.px(px(theme::PAGE_GUTTER))
.py(px(12.))
.child(
crate::plate::plate(&t.night)
.flex()
.items_center()
.gap(px(16.))
.p(px(16.))
.child(
div()
.relative()
.flex_1()
.min_w_0()
.child(
shell::text(theme::night::UI, t.danger)
.child(tr("Settings were not saved.")),
)
.child(
shell::text(theme::night::CAPTION, t.ink_soft).child(trf(
"Your changes are still in this window. Retry to save them. {error}",
&[("error", error)],
)),
),
)
.child(
button("settings-save-retry", "Retry", Kind::Secondary, &t)
.on_click(cx.listener(|this, _: &ClickEvent, _, cx| {
this.save_settings();
cx.notify();
})),
),
),
)
}
fn go(&mut self, page: Page) {
// The game may have made up a name since, and the CLI or another window may have
// changed the accounts: read them again (small files, once a switch).
@@ -1030,12 +1080,14 @@ impl Launcher {
if as_update.is_none_or(|(_, select)| select) {
self.settings.selected = Some(tag);
}
self.settings.save();
// After the save: the selected version is never pruned.
let tx = self.tx.clone();
std::thread::spawn(move || {
let _ = tx.send(Msg::Pruned(backend::prune_old_versions()));
});
// Pruning reads settings from disk: protect the selection only once
// it has been saved there. A failed save leaves all versions in place.
if self.save_settings() {
let tx = self.tx.clone();
std::thread::spawn(move || {
let _ = tx.send(Msg::Pruned(backend::prune_old_versions()));
});
}
}
Err(e) => self.install_error = Some((tag, e)),
}
@@ -1043,8 +1095,12 @@ impl Launcher {
Msg::Removed(tag, result) => {
self.removing.retain(|t| *t != tag);
self.installed = backend::installed();
self.settings.selected =
selection_after_removal(&self.installed, Settings::load().selected);
let selected = if self.settings_save_error.is_some() {
self.settings.selected.take()
} else {
Settings::load().selected
};
self.settings.selected = selection_after_removal(&self.installed, selected);
if let Err(e) = result {
self.install_error = Some((tag, e));
}
@@ -1242,7 +1298,7 @@ impl Launcher {
self.client_error = None;
if !self.settings.owner_notice_seen {
self.settings.owner_notice_seen = true;
self.settings.save();
self.save_settings();
}
}
changed = true;
@@ -1642,7 +1698,7 @@ impl Launcher {
if let Some(path) = paths.pop() {
let _ = this.update(cx, |this, cx| {
this.settings.minecraft_dir = Some(path);
this.settings.save();
this.save_settings();
cx.notify();
});
}
@@ -2615,7 +2671,7 @@ impl Launcher {
let hide = partner::hide_button("partner-hide", &t).on_click(cx.listener(
|this, _: &ClickEvent, _, cx| {
this.settings.partner_hidden = true;
this.settings.save();
this.save_settings();
cx.notify();
},
));
@@ -3263,7 +3319,7 @@ impl Launcher {
let toggle = switch("auto-install", self.settings.auto_install(), &t).on_click(
cx.listener(|this, _: &ClickEvent, _, cx| {
this.settings.install_new_versions = Some(!this.settings.auto_install());
this.settings.save();
this.save_settings();
if this.settings.auto_install() {
this.install_update();
}
@@ -3273,14 +3329,14 @@ impl Launcher {
let prune = switch("auto-prune", self.settings.auto_prune(), &t).on_click(cx.listener(
|this, _: &ClickEvent, _, cx| {
this.settings.prune_old_versions = Some(!this.settings.auto_prune());
this.settings.save();
this.save_settings();
cx.notify();
},
));
let opening = switch("opening", !self.settings.skip_opening, &t).on_click(cx.listener(
|this, _: &ClickEvent, _, cx| {
this.settings.skip_opening = !this.settings.skip_opening;
this.settings.save();
this.save_settings();
cx.notify();
},
));
@@ -3289,7 +3345,7 @@ impl Launcher {
cx.listener(|this, _: &ClickEvent, _, cx| {
let on = !this.settings.desktop_entry();
this.settings.desktop_entry = Some(on);
this.settings.save();
this.save_settings();
let _ = if on {
horizon_launcher::desktop::install(backend::exe_path()).map(|_| ())
} else {
@@ -3390,7 +3446,7 @@ impl Launcher {
move |choice, window, cx| {
let _ = this.update(cx, |this, cx| {
this.settings.appearance = choice;
this.settings.save();
this.save_settings();
titlebar::apply_appearance(window, choice);
this.theme = this.settings_theme(window);
cx.notify();
@@ -3655,7 +3711,7 @@ impl Launcher {
line_edit::Key::Escape => window.blur(cx),
line_edit::Key::Edited => {
this.settings.game_args = this.args_input.text().to_string();
this.settings.save();
this.save_settings();
}
_ => {}
}
@@ -3747,7 +3803,7 @@ impl Launcher {
fn save_proxy(&mut self) {
let value = self.proxy_input.text().trim().to_owned();
self.settings.proxy = (!value.is_empty()).then_some(value);
self.settings.save();
self.save_settings();
horizon_launcher::proxy::announce(self.settings.proxy.clone());
}
@@ -4054,7 +4110,7 @@ impl Launcher {
button(format!("use-{tag}"), "Use", Kind::Ghost, &t).on_click(cx.listener(
move |this, _: &ClickEvent, _, cx| {
this.settings.selected = Some(t1.clone());
this.settings.save();
this.save_settings();
cx.notify();
},
)),
@@ -4831,6 +4887,7 @@ impl Render for Launcher {
.bg(t.side)
.child(sky)
.children(titlebar::render(&t, window))
.children(self.settings_save_notice(cx))
.child(root.flex_1().min_h_0())
.children(titlebar::resize_handles(window))
}
+2 -2
View File
@@ -1092,7 +1092,7 @@ impl Launcher {
button("native-notice-install", "Install", Kind::Primary, &t).on_click(
cx.listener(|this, _: &ClickEvent, _, cx| {
this.settings.native_mods_noticed = true;
this.settings.save();
this.save_settings();
if let Some((source, id, known)) =
this.mods.native_notice.take()
{
@@ -1548,7 +1548,7 @@ impl Launcher {
this.settings.native_profile = Some(choice);
this.settings.native_release = false;
this.native_done = None;
this.settings.save();
this.save_settings();
cx.notify();
});
}
+1 -1
View File
@@ -35,7 +35,7 @@ impl Launcher {
self.owner_screen_open = false;
if !self.settings.owner_notice_seen {
self.settings.owner_notice_seen = true;
self.settings.save();
self.save_settings();
}
}
+1 -1
View File
@@ -1182,7 +1182,7 @@ impl Launcher {
super::partner::hide_button("partner-servers-hide", &t).on_click(cx.listener(
|this, _: &ClickEvent, _, cx| {
this.settings.partner_hidden = true;
this.settings.save();
this.save_settings();
cx.notify();
},
)),
+12 -6
View File
@@ -60,7 +60,7 @@ impl Launcher {
})
.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
this.settings.accent = accent;
this.settings.save();
this.save_settings();
this.theme = this.settings_theme(window);
cx.notify();
})),
@@ -154,13 +154,19 @@ impl Launcher {
}))
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _: &MouseUpEvent, _, _| this.finish_hue()),
cx.listener(|this, _: &MouseUpEvent, _, cx| {
this.finish_hue();
cx.notify();
}),
)
// `on_mouse_up` fires only while the strip is hovered, so a drag released off it would
// keep its accent in memory and never write it.
.on_mouse_up_out(
MouseButton::Left,
cx.listener(|this, _: &MouseUpEvent, _, _| this.finish_hue()),
cx.listener(|this, _: &MouseUpEvent, _, cx| {
this.finish_hue();
cx.notify();
}),
)
}
@@ -168,7 +174,7 @@ impl Launcher {
fn finish_hue(&mut self) {
if self.hue_dragging {
self.hue_dragging = false;
self.settings.save();
self.save_settings();
}
}
@@ -215,7 +221,7 @@ impl Launcher {
match themes::keep_background(&backend::data_dir(), path) {
Ok(name) => {
self.settings.background = Some(name);
self.settings.save();
self.save_settings();
self.background_error = None;
}
Err(e) => self.background_error = Some(e),
@@ -242,7 +248,7 @@ impl Launcher {
button("background-reset", "Landscape", Kind::Ghost, &t).on_click(cx.listener(
|this, _: &ClickEvent, _, cx| {
this.settings.background = None;
this.settings.save();
this.save_settings();
cx.notify();
},
)),
+5 -2
View File
@@ -24,7 +24,10 @@ impl Launcher {
/// The game started: get out of its way as the player asked. Read from `settings.json`, so
/// `horizon-launcher settings while-playing …` counts at once.
pub(super) fn step_aside(&mut self, cx: &mut Context<Self>) {
self.settings.while_playing = Settings::load().while_playing;
// Keep a choice whose save failed available for Retry instead of replacing it from disk.
if self.settings_save_error.is_none() {
self.settings.while_playing = Settings::load().while_playing;
}
let how = self.settings.while_playing.here();
if how == WhilePlaying::Stay {
return;
@@ -67,7 +70,7 @@ impl Launcher {
move |choice, _, cx| {
let _ = this.update(cx, |this, cx| {
this.settings.while_playing = choice;
this.settings.save();
this.save_settings();
cx.notify();
});
}
@@ -585,6 +585,7 @@ public final class VanillaMobAiOracle {
new net.minecraft.world.item.ItemStack(BuiltInRegistries.ITEM.getValue(
net.minecraft.resources.Identifier.parse("minecraft:red_harness"))));
case "noai" -> mob.setNoAi(true);
case "agelocked" -> method(AgeableMob.class, "setAgeLocked", boolean.class).invoke(mob, true);
case "profession" -> {
Villager villager = (Villager) mob;
villager.setVillagerData(villager.getVillagerData().withProfession(level.registryAccess(),
@@ -1109,6 +1110,7 @@ public final class VanillaMobAiOracle {
mem.sort(null);
sb.append("\tmem ").append(String.join(" ", mem));
}
if (m instanceof AgeableMob ageable) sb.append("\tage ").append(ageable.getAge());
if (m instanceof Villager villager) sb.append("\tvx ").append(villager(villager));
if (m instanceof net.minecraft.world.entity.animal.golem.CopperGolem golem) {
Object opened = get(golem, "openedChestPos");
+1
View File
@@ -350,6 +350,7 @@ class StressSceneTests(unittest.TestCase):
self.assertEqual(summons("stress_items", "item"), 2000)
self.assertEqual(sum(summons("stress_held", k) for k in stress_scenes.HOLDERS), 1000)
self.assertEqual(sum(summons("stress_eyes", k) for k in stress_scenes.EYED), 1000)
self.assertEqual(sum(summons("stress_glowing", k) for k in stress_scenes.MOBS), 1000)
self.assertEqual(summons("stress_frames", "item_frame"), 512)
self.assertEqual(summons("stress_armor_stands", "armor_stand"), 500)
self.assertEqual(stress_scenes.setup("stress_empty"), stress_scenes.SETTINGS + stress_scenes.floor(
+33
View File
@@ -1686,6 +1686,38 @@ def summoned_potions():
return ops
def trial_spawner_passengers():
"""Configured passengers must exist as entities with a vehicle (#7254)."""
ops = [*setup('/fill 1 219 1 13 219 8 stone',
'/fill 1 220 1 13 224 8 air', '/gamerule spawn_mobs true')]
# Exercise namespace and position independently, plus the explicit control.
for i, (kind, pos) in enumerate([
('chicken', ''), ('minecraft:chicken', ''),
('chicken', ',Pos:[4.5d,220.0d,6.5d]'),
('minecraft:chicken', ',Pos:[4.5d,220.0d,6.5d]'),
]):
parent, rider = f'trial_parent_{i}', f'trial_rider_{i}'
ops += setup(
'/setblock 4 220 4 trial_spawner[trial_spawner_state=active]'
'{normal_config:{spawn_range:3,total_mobs:1.0f,simultaneous_mobs:1.0f,'
'total_mobs_added_per_player:0.0f,simultaneous_mobs_added_per_player:0.0f,'
'ticks_between_spawn:1000},spawn_data:{entity:{id:"zombie",'
f'Tags:["{parent}"],Pos:[4.5d,220.0d,6.5d],NoAI:1b,'
f'Passengers:[{{id:"{kind}",NoAI:1b,Tags:["{rider}"]{pos}}}]'
'}},next_mob_spawns_at:0L}',
'/data modify block 4 220 4 registered_players append from entity @s UUID')
ops += [
{'op': 'tick', 'n': 5},
cmd(f'/execute if entity @e[tag={parent}]'),
cmd(f'/execute if entity @e[tag={rider}]'),
cmd(f'/execute as @e[tag={rider}] on vehicle if entity @s[tag={parent}]'),
cmd(f'/data get entity @e[tag={parent},limit=1] Passengers[0].Tags'),
*setup(f'/kill @e[tag={parent}]', f'/kill @e[tag={rider}]',
'/setblock 4 220 4 air'),
]
return ops + setup('/fill 1 219 1 13 224 8 air', '/gamerule spawn_mobs false')
def groups():
return [
('setup', group_setup()),
@@ -1698,6 +1730,7 @@ def groups():
('wither spawn', wither_spawn()),
('riding teleport', riding_teleport()),
('mob riding', mob_riding()),
('trial spawner passengers', trial_spawner_passengers()),
('camel riders', camel_riders()),
('scores, teams and bossbars', scores()),
('execute', execute()),
@@ -0,0 +1,16 @@
{
"hooks": [
{
"name": "movement_input",
"target": "horizon_client::game::native_hooks::Game<'_>::native_movement_input",
"neoforge": "MovementInputUpdateEvent",
"side": "client",
"cancellable": false,
"mod_api": {
"contract": "listen"
}
}
],
"rust_summary": "Local player movement input before application (`native_movement_input`).",
"doc_summary": "`native_movement_input(forward, strafe, jump, sneak)` lets client mods change local movement after input is read and before it is applied. Positive strafe is left; servers receive direction flags rather than scalar magnitudes. Bundles need hooks API @API:movement_input@."
}
+10 -10
View File
@@ -61,7 +61,7 @@ StandingSignBlock.getStateForPlacement port crates/horizon-world/src/level/place
StandingSignBlock.mirror port crates/horizon-world/src/worldgen/structure.rs::StateTransforms::mirror
StandingSignBlock.rotate port crates/horizon-world/src/worldgen/structure.rs::StateTransforms::rotate
StandingSignBlock.updateShape port crates/horizon-world/src/level/shapes.rs::Sim::update_shape_class
StemBlock.getCloneItemStack missing unsure: minor: middle-click on a stem may not return its seed item; no stem case found in the pick code
StemBlock.getCloneItemStack port crates/horizon-client/src/game/block_items.rs::clone_item a pumpkin or melon stem, attached or not, picks the seed it grew from; any other block picks its own item
StemBlock.isValidBonemealTarget port crates/horizon-world/src/level/bonemeal.rs::Sim::bonemeal_valid
StemBlock.performBonemeal port crates/horizon-world/src/level/bonemeal.rs::Sim::perform_bonemeal
StemBlock.randomTick port crates/horizon-world/src/level/tick.rs::Sim::stem_random_tick
@@ -75,8 +75,8 @@ SuspiciousEffectHolder.tryGet port crates/horizon-items/src/flowers.rs::stew_eff
TallSeagrassBlock.canSurvive port crates/horizon-world/src/level/survive.rs::Sim::can_survive_class
TallSeagrassBlock.getStateForPlacement port crates/horizon-world/src/level/place.rs::Sim::placement_class
TallSeagrassBlock.mayPlaceOn port crates/horizon-world/src/level/survive.rs::Sim::may_place_on
TestBlock missing minor: game-test block for operators; Horizon keeps only its NBT
TestInstanceBlock missing minor: game-test instance block; Horizon keeps only its NBT
TestBlock skip:debug game-test tooling: an operator's test block runs a structure when triggered and Horizon keeps only its block entity NBT
TestInstanceBlock skip:debug game-test tooling: the instance block runs and exports a test structure; Horizon keeps its NBT
TintedParticleLeavesBlock.spawnFallingLeavesParticle port crates/horizon-client/src/animate_tick.rs::block_animate_tick falling tinted leaf particle
TorchBlock.animateTick port crates/horizon-client/src/animate_tick.rs::block_animate_tick torch flame
TorchflowerCropBlock.randomTick port crates/horizon-world/src/level/tick.rs::Sim::random_tick_block growth arm
@@ -100,9 +100,9 @@ WallHangingSignBlock.canAttachTo inline crates/horizon-world/src/level/place.rs:
WallHangingSignBlock.canPlace port crates/horizon-world/src/level/place.rs::Sim::wall_hanging_sign_can_place
WallHangingSignBlock.createBlockStateDefinition skip:data states come from the generated block state table
WallHangingSignBlock.getStateForPlacement port crates/horizon-world/src/level/place.rs::Sim::placement_class
WallHangingSignBlock.isHittingEditableSide missing unsure: player: a hanging sign clicked on its side may not chain onto a wall hanging sign
WallHangingSignBlock.isHittingEditableSide inline crates/horizon-world/src/level/interact.rs::Behaviours::block_use_item_on the faces of the sign it faces and its opposite are the editable ones; a hanging sign aimed at any other face passes
WallHangingSignBlock.rotate port crates/horizon-world/src/worldgen/structure.rs::StateTransforms::rotate
WallHangingSignBlock.shouldTryToChainAnotherHangingSign missing unsure: player: a second hanging sign used on a wall sign may not chain on
WallHangingSignBlock.shouldTryToChainAnotherHangingSign inline crates/horizon-world/src/level/interact.rs::Behaviours::block_use_item_on passing on a non-editable face lets the held hanging sign place its own block, which chains onto the wall sign
WallHangingSignBlock.updateShape port crates/horizon-world/src/level/shapes.rs::Sim::update_shape_class
WallHangingSignBlock.useItemOn port crates/horizon-world/src/level/interact.rs::Behaviours::use_item_on gap: player: chaining a hanging sign onto a wall hanging sign is not ported here
WallSignBlock.canSurvive port crates/horizon-world/src/level/survive.rs::Sim::can_survive_class
@@ -132,7 +132,7 @@ BannerPatternLayers.removeLast inline crates/horizon-client/src/cauldron.rs::int
BannerPatternLayers.Builder skip:java builder for banner pattern layers
BannerPatterns skip:data registry bootstrap
BarrelBlockEntity.loadAdditional port crates/horizon-client-core/src/block_entity_nbt.rs::canonical gap: minor: canonical NBT form only, no load step of its own
BarrelBlockEntity.recheckOpen missing unsure: player: a barrel lid may stay open after a player walks away; only golem openers are rechecked on a timer
BarrelBlockEntity.recheckOpen missing the lid's periodic recheck of whether its viewers are still there; Horizon draws no barrel lid at all (containers.rs holds its 27 slots), so nothing rechecks one, where a golem's openers are rechecked on a timer
BarrelBlockEntity.saveAdditional port crates/horizon-client-core/src/block_entity_nbt.rs::canonical gap: minor: canonical NBT form only
BarrelBlockEntity.startOpen port crates/horizon-client/src/game/block_entity_anims.rs::Game::barrel_openers_changed
BarrelBlockEntity.stopOpen port crates/horizon-client/src/game/block_entity_anims.rs::Game::barrel_openers_changed
@@ -162,15 +162,15 @@ PotentSulfurBlockEntity.setLevel inline crates/horizon-client/src/game/potent_su
PotentSulfurBlockEntity.spawnGeyserParticle port crates/horizon-client/src/game/potent_sulfur.rs::Game::spawn_geyser_particle ticked in #3516; `PotentSulfurBlockEntity.spawnGeyserParticle` is cited at crates/horizon-client/src/game/potent_sulfur.rs:380
PotentSulfurBlockEntity.spawnNoxiousGasCloudParticle port crates/horizon-client/src/game/potent_sulfur.rs::Game::spawn_noxious_gas_cloud_particle ticked in #3516; `PotentSulfurBlockEntity.spawnNoxiousGasCloudParticle` is cited at crates/horizon-client/src/game/potent_sulfur.rs:395
RandomizableContainerBlockEntity.applyImplicitComponents port crates/horizon-client/src/block_entity_item.rs::place the item's minecraft:container_loot hands the block its LootTable and LootTableSeed
RandomizableContainerBlockEntity.canOpen missing unsure: minor: the spectator and loot-table open checks were not found
RandomizableContainerBlockEntity.canOpen inline crates/horizon-client/src/game/advancements.rs::Game::container_locked_for the negative of it: a spectator opens any container, and otherwise the lock decides (LockCode.canUnlock)
RandomizableContainerBlockEntity.collectImplicitComponents port crates/horizon-client/src/block_entity_item.rs::collect the block's LootTable and LootTableSeed leave on the item as minecraft:container_loot
RandomizableContainerBlockEntity.createMenu port crates/horizon-client/src/game.rs::Game::open_menu gap: minor: the spectator and loot-table checks are not ported here
SculkSensorBlockEntity.loadAdditional port crates/horizon-client-render/src/sculk.rs::SculkEntity::load
SculkSensorBlockEntity.saveAdditional port crates/horizon-client-render/src/sculk.rs::SculkEntity::to_nbt
SculkSensorBlockEntity.VibrationUser.canReceiveVibration port crates/horizon-client-render/src/sculk.rs::valid_vibration
SculkSensorBlockEntity.VibrationUser.onReceiveVibration port crates/horizon-client-render/src/sculk.rs::Data::schedule
TestBlockEntity missing minor: game-test block entity; reset, trigger and logging are not ported, NBT only
TestInstanceBlockEntity missing minor: game-test structure tooling; export and chunk forcing are not ported
TestBlockEntity skip:debug game-test tooling: reset, trigger and logging belong to the test runner, which Horizon does not have; its NBT is kept
TestInstanceBlockEntity skip:debug game-test tooling: export and chunk forcing belong to the test runner, which Horizon does not have
TrialSpawnerConfig.calculateTargetSimultaneousMobs port crates/horizon-client/src/mobs/trial_spawner.rs::TrialConfig::target_simultaneous_mobs
TrialSpawnerConfig.calculateTargetTotalMobs port crates/horizon-client/src/mobs/trial_spawner.rs::TrialConfig::target_total_mobs
TrialSpawnerConfig.withSpawning skip:java builder-style setter on the config record
@@ -204,7 +204,7 @@ ImposterProtoChunk skip:other wrapper so the vanilla status pipeline can read a
LinearPalette skip:java palette storage; Horizon's chunk.rs keeps block ids in plain arrays
PalettedContainerFactory skip:java factory for palette storage; Horizon's chunk.rs keeps block ids in plain arrays
SingleValuePalette skip:java palette storage; Horizon's chunk.rs keeps block ids in plain arrays
UpgradeData missing unsure: minor: block upgrades of old chunks on load are not ported
UpgradeData missing the block upgrades an old chunk takes when it loads (the Sides bits a save carries); the datafix writes and keeps UpgradeData (datafix/chunk_leaves.rs) but no load path applies the upgrades
ChunkDependencies.getRadiusOf inline crates/horizon-client-render/src/streaming.rs::Ranges::margins fixed margins instead of a per-status radius
ChunkPyramid.Builder skip:java builder for the status pyramid; Horizon uses fixed margins
ChunkStatusTasks.buildTerrain inline crates/horizon-world/src/worldgen/generator.rs::Pipeline::generate_with_context terrain pass
1 RodBlock.mirror port crates/horizon-world/src/worldgen/structure.rs::StateTransforms::mirror rod facing mirrored through the state table
61 StandingSignBlock.mirror port crates/horizon-world/src/worldgen/structure.rs::StateTransforms::mirror
62 StandingSignBlock.rotate port crates/horizon-world/src/worldgen/structure.rs::StateTransforms::rotate
63 StandingSignBlock.updateShape port crates/horizon-world/src/level/shapes.rs::Sim::update_shape_class
64 StemBlock.getCloneItemStack missing port crates/horizon-client/src/game/block_items.rs::clone_item unsure: minor: middle-click on a stem may not return its seed item; no stem case found in the pick code a pumpkin or melon stem, attached or not, picks the seed it grew from; any other block picks its own item
65 StemBlock.isValidBonemealTarget port crates/horizon-world/src/level/bonemeal.rs::Sim::bonemeal_valid
66 StemBlock.performBonemeal port crates/horizon-world/src/level/bonemeal.rs::Sim::perform_bonemeal
67 StemBlock.randomTick port crates/horizon-world/src/level/tick.rs::Sim::stem_random_tick
75 TallSeagrassBlock.canSurvive port crates/horizon-world/src/level/survive.rs::Sim::can_survive_class
76 TallSeagrassBlock.getStateForPlacement port crates/horizon-world/src/level/place.rs::Sim::placement_class
77 TallSeagrassBlock.mayPlaceOn port crates/horizon-world/src/level/survive.rs::Sim::may_place_on
78 TestBlock missing skip:debug minor: game-test block for operators; Horizon keeps only its NBT game-test tooling: an operator's test block runs a structure when triggered and Horizon keeps only its block entity NBT
79 TestInstanceBlock missing skip:debug minor: game-test instance block; Horizon keeps only its NBT game-test tooling: the instance block runs and exports a test structure; Horizon keeps its NBT
80 TintedParticleLeavesBlock.spawnFallingLeavesParticle port crates/horizon-client/src/animate_tick.rs::block_animate_tick falling tinted leaf particle
81 TorchBlock.animateTick port crates/horizon-client/src/animate_tick.rs::block_animate_tick torch flame
82 TorchflowerCropBlock.randomTick port crates/horizon-world/src/level/tick.rs::Sim::random_tick_block growth arm
100 WallHangingSignBlock.canPlace port crates/horizon-world/src/level/place.rs::Sim::wall_hanging_sign_can_place
101 WallHangingSignBlock.createBlockStateDefinition skip:data states come from the generated block state table
102 WallHangingSignBlock.getStateForPlacement port crates/horizon-world/src/level/place.rs::Sim::placement_class
103 WallHangingSignBlock.isHittingEditableSide missing inline crates/horizon-world/src/level/interact.rs::Behaviours::block_use_item_on unsure: player: a hanging sign clicked on its side may not chain onto a wall hanging sign the faces of the sign it faces and its opposite are the editable ones; a hanging sign aimed at any other face passes
104 WallHangingSignBlock.rotate port crates/horizon-world/src/worldgen/structure.rs::StateTransforms::rotate
105 WallHangingSignBlock.shouldTryToChainAnotherHangingSign missing inline crates/horizon-world/src/level/interact.rs::Behaviours::block_use_item_on unsure: player: a second hanging sign used on a wall sign may not chain on passing on a non-editable face lets the held hanging sign place its own block, which chains onto the wall sign
106 WallHangingSignBlock.updateShape port crates/horizon-world/src/level/shapes.rs::Sim::update_shape_class
107 WallHangingSignBlock.useItemOn port crates/horizon-world/src/level/interact.rs::Behaviours::use_item_on gap: player: chaining a hanging sign onto a wall hanging sign is not ported here
108 WallSignBlock.canSurvive port crates/horizon-world/src/level/survive.rs::Sim::can_survive_class
132 BannerPatternLayers.Builder skip:java builder for banner pattern layers
133 BannerPatterns skip:data registry bootstrap
134 BarrelBlockEntity.loadAdditional port crates/horizon-client-core/src/block_entity_nbt.rs::canonical gap: minor: canonical NBT form only, no load step of its own
135 BarrelBlockEntity.recheckOpen missing unsure: player: a barrel lid may stay open after a player walks away; only golem openers are rechecked on a timer the lid's periodic recheck of whether its viewers are still there; Horizon draws no barrel lid at all (containers.rs holds its 27 slots), so nothing rechecks one, where a golem's openers are rechecked on a timer
136 BarrelBlockEntity.saveAdditional port crates/horizon-client-core/src/block_entity_nbt.rs::canonical gap: minor: canonical NBT form only
137 BarrelBlockEntity.startOpen port crates/horizon-client/src/game/block_entity_anims.rs::Game::barrel_openers_changed
138 BarrelBlockEntity.stopOpen port crates/horizon-client/src/game/block_entity_anims.rs::Game::barrel_openers_changed
162 PotentSulfurBlockEntity.spawnGeyserParticle port crates/horizon-client/src/game/potent_sulfur.rs::Game::spawn_geyser_particle ticked in #3516; `PotentSulfurBlockEntity.spawnGeyserParticle` is cited at crates/horizon-client/src/game/potent_sulfur.rs:380
163 PotentSulfurBlockEntity.spawnNoxiousGasCloudParticle port crates/horizon-client/src/game/potent_sulfur.rs::Game::spawn_noxious_gas_cloud_particle ticked in #3516; `PotentSulfurBlockEntity.spawnNoxiousGasCloudParticle` is cited at crates/horizon-client/src/game/potent_sulfur.rs:395
164 RandomizableContainerBlockEntity.applyImplicitComponents port crates/horizon-client/src/block_entity_item.rs::place the item's minecraft:container_loot hands the block its LootTable and LootTableSeed
165 RandomizableContainerBlockEntity.canOpen missing inline crates/horizon-client/src/game/advancements.rs::Game::container_locked_for unsure: minor: the spectator and loot-table open checks were not found the negative of it: a spectator opens any container, and otherwise the lock decides (LockCode.canUnlock)
166 RandomizableContainerBlockEntity.collectImplicitComponents port crates/horizon-client/src/block_entity_item.rs::collect the block's LootTable and LootTableSeed leave on the item as minecraft:container_loot
167 RandomizableContainerBlockEntity.createMenu port crates/horizon-client/src/game.rs::Game::open_menu gap: minor: the spectator and loot-table checks are not ported here
168 SculkSensorBlockEntity.loadAdditional port crates/horizon-client-render/src/sculk.rs::SculkEntity::load
169 SculkSensorBlockEntity.saveAdditional port crates/horizon-client-render/src/sculk.rs::SculkEntity::to_nbt
170 SculkSensorBlockEntity.VibrationUser.canReceiveVibration port crates/horizon-client-render/src/sculk.rs::valid_vibration
171 SculkSensorBlockEntity.VibrationUser.onReceiveVibration port crates/horizon-client-render/src/sculk.rs::Data::schedule
172 TestBlockEntity missing skip:debug minor: game-test block entity; reset, trigger and logging are not ported, NBT only game-test tooling: reset, trigger and logging belong to the test runner, which Horizon does not have; its NBT is kept
173 TestInstanceBlockEntity missing skip:debug minor: game-test structure tooling; export and chunk forcing are not ported game-test tooling: export and chunk forcing belong to the test runner, which Horizon does not have
174 TrialSpawnerConfig.calculateTargetSimultaneousMobs port crates/horizon-client/src/mobs/trial_spawner.rs::TrialConfig::target_simultaneous_mobs
175 TrialSpawnerConfig.calculateTargetTotalMobs port crates/horizon-client/src/mobs/trial_spawner.rs::TrialConfig::target_total_mobs
176 TrialSpawnerConfig.withSpawning skip:java builder-style setter on the config record
204 LinearPalette skip:java palette storage; Horizon's chunk.rs keeps block ids in plain arrays
205 PalettedContainerFactory skip:java factory for palette storage; Horizon's chunk.rs keeps block ids in plain arrays
206 SingleValuePalette skip:java palette storage; Horizon's chunk.rs keeps block ids in plain arrays
207 UpgradeData missing unsure: minor: block upgrades of old chunks on load are not ported the block upgrades an old chunk takes when it loads (the Sides bits a save carries); the datafix writes and keeps UpgradeData (datafix/chunk_leaves.rs) but no load path applies the upgrades
208 ChunkDependencies.getRadiusOf inline crates/horizon-client-render/src/streaming.rs::Ranges::margins fixed margins instead of a per-status radius
209 ChunkPyramid.Builder skip:java builder for the status pyramid; Horizon uses fixed margins
210 ChunkStatusTasks.buildTerrain inline crates/horizon-world/src/worldgen/generator.rs::Pipeline::generate_with_context terrain pass
+1 -1
View File
@@ -52,7 +52,7 @@ GameRuleMap.equals skip:java value equality of the map
GameRuleMap.of port crates/horizon-client-core/src/gamerules.rs::GameRules::get no stored map: a rule that was never set reads its table default
GameRuleMap.setFromIf skip:other no caller in the pinned sources (only withOther uses it)
GameRuleMap.withOther skip:other no caller in the pinned sources
BlendingData port crates/horizon-world/src/worldgen/blending.rs::BlendingData unpack/pack, calculateData for the border columns, heights, densities and biomes; the client reads the saved chunks (old_world.rs)
BlendingData home crates/horizon-world/src/worldgen/blending.rs BlendingData and Blender are in that file: unpack/pack and calculated (getOrUpdateBlendingData + calculateData) for the border columns, heights, densities and biomes; the client reads the saved chunks (old_world.rs)
BlendingData.Packed.validateArraySize skip:java codec size check
AllOfPredicate.test port crates/horizon-world/src/worldgen/block_predicate.rs::BlockPredicate::test_inner the All arm
AnyOfPredicate.test port crates/horizon-world/src/worldgen/block_predicate.rs::BlockPredicate::test_inner the Any arm
1 RegionFileVersion.configure missing minor: region-file-compression is read from server.properties (horizon-server-files properties.rs) but nothing applies it; chunk saves always write zlib (save.rs compress_records)
52 GameRuleMap.of port crates/horizon-client-core/src/gamerules.rs::GameRules::get no stored map: a rule that was never set reads its table default
53 GameRuleMap.setFromIf skip:other no caller in the pinned sources (only withOther uses it)
54 GameRuleMap.withOther skip:other no caller in the pinned sources
55 BlendingData port home crates/horizon-world/src/worldgen/blending.rs::BlendingData crates/horizon-world/src/worldgen/blending.rs unpack/pack, calculateData for the border columns, heights, densities and biomes; the client reads the saved chunks (old_world.rs) BlendingData and Blender are in that file: unpack/pack and calculated (getOrUpdateBlendingData + calculateData) for the border columns, heights, densities and biomes; the client reads the saved chunks (old_world.rs)
56 BlendingData.Packed.validateArraySize skip:java codec size check
57 AllOfPredicate.test port crates/horizon-world/src/worldgen/block_predicate.rs::BlockPredicate::test_inner the All arm
58 AnyOfPredicate.test port crates/horizon-world/src/worldgen/block_predicate.rs::BlockPredicate::test_inner the Any arm
+1 -1
View File
@@ -180,7 +180,7 @@ CollectingNeighborUpdater.addAndRun port crates/horizon-world/src/level/update.r
CollectingNeighborUpdater.neighborChanged port crates/horizon-world/src/level/update.rs::Sim::neighbor_changed_at Simple update
CollectingNeighborUpdater.runUpdates port crates/horizon-world/src/level/update.rs::Sim::run_updates
CollectingNeighborUpdater.shapeUpdate port crates/horizon-world/src/level/update.rs::Sim::neighbor_shape_changed
CollectingNeighborUpdater.updateNeighborsAtExceptFromFacing port crates/horizon-world/src/level/update.rs::Sim::update_neighbors_at_except
CollectingNeighborUpdater.updateNeighborsAtExceptFromFacing port crates/horizon-world/src/level/update.rs::Sim::update_neighbors_at_except_oriented the Multi update with one side skipped; the orientation is threaded through here (callers pass the one they have)
CollectingNeighborUpdater.FullNeighborUpdate.runNext port crates/horizon-world/src/level/update.rs::Update::next Full arm
CollectingNeighborUpdater.MultiNeighborUpdate.forEachUpdatedPos inline crates/horizon-world/src/level/update.rs::Update::next Multi arm steps the index and skips the facing side
CollectingNeighborUpdater.MultiNeighborUpdate.runNext port crates/horizon-world/src/level/update.rs::Update::next Multi arm
1 RepeatingPlacement.modify port crates/horizon-world/src/worldgen/placement.rs::PlacementModifier::modify_inner repeat arm for Count and the noise counts emits the count
180 CollectingNeighborUpdater.neighborChanged port crates/horizon-world/src/level/update.rs::Sim::neighbor_changed_at Simple update
181 CollectingNeighborUpdater.runUpdates port crates/horizon-world/src/level/update.rs::Sim::run_updates
182 CollectingNeighborUpdater.shapeUpdate port crates/horizon-world/src/level/update.rs::Sim::neighbor_shape_changed
183 CollectingNeighborUpdater.updateNeighborsAtExceptFromFacing port crates/horizon-world/src/level/update.rs::Sim::update_neighbors_at_except crates/horizon-world/src/level/update.rs::Sim::update_neighbors_at_except_oriented the Multi update with one side skipped; the orientation is threaded through here (callers pass the one they have)
184 CollectingNeighborUpdater.FullNeighborUpdate.runNext port crates/horizon-world/src/level/update.rs::Update::next Full arm
185 CollectingNeighborUpdater.MultiNeighborUpdate.forEachUpdatedPos inline crates/horizon-world/src/level/update.rs::Update::next Multi arm steps the index and skips the facing side
186 CollectingNeighborUpdater.MultiNeighborUpdate.runNext port crates/horizon-world/src/level/update.rs::Update::next Multi arm
+2 -2
View File
@@ -1,4 +1,4 @@
ExperimentalRedstoneWireEvaluator port crates/horizon-world/src/level/redstone/experimental.rs::Sim::experimental_wire_update two queues and insertion-ordered power changes with oriented neighbor notifications
ExperimentalRedstoneWireEvaluator home crates/horizon-world/src/level/redstone/experimental.rs the two-queue evaluator, Evaluator in that file, is what Sim::experimental_wire_update drives: wires found with their orientation, power written in first-discovery order
InstantNeighborUpdater skip:other never instantiated in vanilla 26.3; Level always uses CollectingNeighborUpdater
NeighborUpdater.executeShapeUpdate port crates/horizon-world/src/level/update.rs::Sim::execute Shape arm: wire skip flag, then update_shape and update_or_destroy
NeighborUpdater.executeUpdate port crates/horizon-world/src/level/hooks.rs::Sim::neighbor_changed per-block neighbour change dispatch
@@ -31,7 +31,7 @@ LevelStorageSource.isNewLevelIdAcceptable port crates/horizon-client/src/title.r
LevelStorageSource.levelExists inline crates/horizon-client/src/title.rs::Title::target_folder folder-exists check
LevelStorageSource.loadLevelSummaries port crates/horizon-client/src/title.rs::list_worlds
LevelStorageSource.makeLevelSummary inline crates/horizon-client/src/title.rs::list_worlds per-folder name, version, mode and last played
LevelStorageSource.parseFeatureFlagsFromSummary missing unsure: list_worlds does not read enabled_features, so experimental worlds are not flagged
LevelStorageSource.parseFeatureFlagsFromSummary port crates/horizon-client/src/save.rs::Level::read_summary the summary read goes through read_root, which parses WorldDataConfiguration's enabled_features; gap: minor: the world list still shows no experimental marker for them
LevelStorageSource.parseValidator skip:other symlink allow-list (allowed_symlinks.txt); Horizon does not read that file
LevelStorageSource.readDataConfig port crates/horizon-client/src/save.rs::Level::read DataPacks and enabled_features are read with the level (data_packs, enabled_features), and datapack::enabled_ids layers the world's own packs over the bundle with them
LevelSummary.backupStatus inline crates/horizon-client/src/title.rs::should_backup FILE_FIXING_REQUIRED when the world needs its files fixed or comes from a newer version; UPGRADE_TO_SNAPSHOT never applies to a stable build
1 ExperimentalRedstoneWireEvaluator port home crates/horizon-world/src/level/redstone/experimental.rs::Sim::experimental_wire_update crates/horizon-world/src/level/redstone/experimental.rs two queues and insertion-ordered power changes with oriented neighbor notifications the two-queue evaluator, Evaluator in that file, is what Sim::experimental_wire_update drives: wires found with their orientation, power written in first-discovery order
2 InstantNeighborUpdater skip:other never instantiated in vanilla 26.3; Level always uses CollectingNeighborUpdater
3 NeighborUpdater.executeShapeUpdate port crates/horizon-world/src/level/update.rs::Sim::execute Shape arm: wire skip flag, then update_shape and update_or_destroy
4 NeighborUpdater.executeUpdate port crates/horizon-world/src/level/hooks.rs::Sim::neighbor_changed per-block neighbour change dispatch
31 LevelStorageSource.levelExists inline crates/horizon-client/src/title.rs::Title::target_folder folder-exists check
32 LevelStorageSource.loadLevelSummaries port crates/horizon-client/src/title.rs::list_worlds
33 LevelStorageSource.makeLevelSummary inline crates/horizon-client/src/title.rs::list_worlds per-folder name, version, mode and last played
34 LevelStorageSource.parseFeatureFlagsFromSummary missing port crates/horizon-client/src/save.rs::Level::read_summary unsure: list_worlds does not read enabled_features, so experimental worlds are not flagged the summary read goes through read_root, which parses WorldDataConfiguration's enabled_features; gap: minor: the world list still shows no experimental marker for them
35 LevelStorageSource.parseValidator skip:other symlink allow-list (allowed_symlinks.txt); Horizon does not read that file
36 LevelStorageSource.readDataConfig port crates/horizon-client/src/save.rs::Level::read DataPacks and enabled_features are read with the level (data_packs, enabled_features), and datapack::enabled_ids layers the world's own packs over the bundle with them
37 LevelSummary.backupStatus inline crates/horizon-client/src/title.rs::should_backup FILE_FIXING_REQUIRED when the world needs its files fixed or comes from a newer version; UPGRADE_TO_SNAPSHOT never applies to a stable build
+17 -16
View File
@@ -1,9 +1,10 @@
SetStewEffectFunction.Builder skip:java builder for the stew effect list; Horizon keeps the effects in loot.rs Context::run (set_stew_effect)
SetWritableBookPagesFunction missing minor: no vanilla loot table uses set_writable_book_pages; Horizon's loot run has no arm for it
SetWrittenBookPagesFunction missing minor: no vanilla loot table uses set_written_book_pages; Horizon's loot run has no arm for it
SetWritableBookPagesFunction missing `horizon-world/src/loot.rs::modify` has no `set_writable_book_pages` arm, and no vanilla loot table asks for one
SetWrittenBookPagesFunction missing no `set_written_book_pages` arm in `loot.rs::modify` (the arms run from `sequence` and `set_count` through `set_potion` to `set_stew_effect`); no vanilla loot table asks for one
SmeltItemFunction.run port crates/horizon-world/src/loot.rs::Context::run furnace_smelt arm: smelting recipe, use_input_count and stack cap
SmeltItemFunction.smelted skip:java static builder factory
ToggleTooltips home crates/horizon-client/src/game/commands/items.rs the `/item modify` arm toggle_tooltips writes the tooltip_display component's hide_tooltip and hidden_components as run does; the loot run has no arm, and no vanilla loot table uses it
ToggleTooltips home crates/horizon-client/src/game/commands/items.rs the `/item modify` arm of cmd_item_modify writes the tooltip_display component's hide_tooltip and hidden_components as run does; the loot run has no arm, and no vanilla loot table uses it
ToggleTooltips.run inline crates/horizon-client/src/game/commands/items.rs::Game::cmd_item_modify the `minecraft:toggle_tooltips` arm of the `/item modify` command adds each false toggle to `tooltip_display`'s hidden_components and keeps hide_tooltip; the loot run has no arm, so a loot table's toggle_tooltips is ignored (no vanilla loot table uses one)
LootContextParamSets skip:data loot parameter set registry and validation; Horizon reads its loot tables
AllOfCondition.allOf skip:java static builder
AllOfCondition.combine inline crates/horizon-items/src/items.rs::LootContext::condition all_of arm: every term must hold
@@ -16,11 +17,11 @@ CompositeLootItemCondition.Builder skip:java builder (addTerm)
ConditionUserBuilder skip:java builder interface for loot conditions
DamageSourceCondition.test port crates/horizon-world/src/loot.rs::Context::condition damage_source_properties arm (mob death loot)
EnchantmentActiveCheck.test inline crates/horizon-client/src/game/enchant_effects.rs::Game::tick_soul_speed the soul speed modifiers switch on and off here; the ENCHANTMENT_ACTIVE flag itself is not read as a condition
EntityHasScoreCondition.hasScore missing minor: no vanilla loot table uses entity_scores
EntityHasScoreCondition.test missing minor: no vanilla loot table uses entity_scores
EntityHasScoreCondition.hasScore missing `loot.rs::condition` has no `entity_scores` arm, and no vanilla loot table asks for one
EntityHasScoreCondition.test missing same as `hasScore`: the condition dispatch (`inverted`, `all_of`, `any_of`, `entity_properties`, `killed_by_player`, the chance and location checks) has no scoreboard arm
EntityHasScoreCondition.validate skip:data datapack validation
EnvironmentAttributeCheck.createCodec skip:java codec
EnvironmentAttributeCheck.test missing minor: no vanilla data uses environment_attribute_check
EnvironmentAttributeCheck.test missing no `environment_attribute_check` arm in `loot.rs::condition`; no vanilla data uses it
ExplosionCondition.test port crates/horizon-items/src/items.rs::LootContext::condition survives_explosion arm: 1 / radius under an explosion (block loot; only block tables use it)
FloatValueCheck.validate skip:data datapack validation
IntValueCheck.validate skip:data datapack validation
@@ -39,7 +40,7 @@ MatchBlock.blockMatches skip:java static builder
MatchBlock.getReferencedContextParams skip:data validation metadata for context params
MatchBlock.test port crates/horizon-items/src/items.rs::LootContext::condition match_block arm: block id, state and property ranges; gap: minor: the block-entity part of the predicate is not read
MatchTool.test port crates/horizon-items/src/items.rs::LootContext::condition match_tool arm; gap: minor: item ids, tags and enchantment levels only, other ItemPredicate parts are not read
TimeCheck missing minor: time_check is used by no vanilla data file (datapacks only)
TimeCheck missing no `time_check` arm in `loot.rs::condition`; no vanilla data file uses it, so only a datapack reaches it
WeatherCheck.test port crates/horizon-client/src/game/trident.rs::Game::channel_lightning channeling's thunder check (thundering only); gap: minor: raining is not read, channeling only uses thundering
NbtProviders skip:data registry bootstrap
AggregateProvider skip:java codec only
@@ -58,13 +59,13 @@ PowerProvider.mapCodec skip:java codec
PowerProvider.validate skip:data datapack validation
RangeProvider.mapCodec skip:java codec
RangeProvider.validate skip:data datapack validation
StoredNumberAccess missing minor: storage-backed numbers; no vanilla data uses storage providers
StoredNumberAccess missing no storage-backed number provider: `loot.rs::level_value` and the int and float readers know constant, uniform, binomial, linear, clamped, fraction, levels_squared and lookup only
UnaryProvider skip:java codec only
floats/Average.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float avg arm
floats/ContextFloatProviders skip:data registry bootstrap
floats/Difference.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float sub arm
floats/EnchantmentLevelProvider.getFloatUnsafe port crates/horizon-items/src/enchant.rs::Enchantments::condition random_chance arm: chance at the enchantment level; gap: minor: only the random_chance use is read
floats/EnvironmentAttributeValue missing minor: no vanilla data uses environment_attribute providers
floats/EnvironmentAttributeValue missing no `environment_attribute` provider in `loot.rs::level_value`
floats/Length.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float length arm
floats/Maximum.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float max arm
floats/Minimum.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float min arm
@@ -73,7 +74,7 @@ floats/Product.getFloatUnsafe port crates/horizon-client/src/game/commands/compu
floats/Quotient.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float div arm
ResolvableFloat.getFromItem port crates/horizon-client-render/src/providers.rs::Providers::speed furnace fuel speed multiplier (COOKING_FUEL); gap: minor: only the furnace fuel component is read
ResolvableFloat.wrap skip:java codec helper
floats/StorageValue missing minor: no vanilla data uses storage providers
floats/StorageValue missing no `storage` provider in `loot.rs::level_value`
floats/Sum.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float add arm
floats/Truncate.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float truncate arm
floats/UniformGenerator.getFloatUnsafe port crates/horizon-world/src/loot.rs::Context::float uniform arm: min + random * (max - min)
@@ -85,7 +86,7 @@ ints/ContextIntProviders.bootstrap skip:data registry bootstrap
ints/ContextIntProviders.compostable port crates/horizon-client/src/game/composter.rs::Game::compostable the item's compostable layer provider; evaluated when the composter takes it (Game::composter_add)
ints/ContextIntProviders.cooking port crates/horizon-client-render/src/providers.rs::Providers::burn_time fuel burn times (the cooking/time_* providers)
ints/Difference.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int sub arm
ints/EnvironmentAttributeValue missing minor: no vanilla data uses environment_attribute providers
ints/EnvironmentAttributeValue missing no `environment_attribute` provider in the int reader (`loot.rs::int`)
ints/FloorModulus.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int floor_mod arm
ints/FloorQuotient.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int floor_div arm
ints/Maximum.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int max arm
@@ -95,10 +96,10 @@ ints/Product.getIntUnsafe port crates/horizon-client/src/game/commands/compute.r
ints/Quotient.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int div arm
ResolvableInt.getFromItem port crates/horizon-client-render/src/providers.rs::Providers::burn_time furnace fuel burn time (COOKING_FUEL); gap: minor: only the furnace fuel component is read
ResolvableInt.wrap skip:java codec helper
ints/ScoreboardValue.getIntUnsafe missing minor: no vanilla data uses score providers
ints/ScoreboardValue.getScoreInfo missing minor: no vanilla data uses score providers
ints/ScoreboardValue.getIntUnsafe missing no `score` provider in `loot.rs::int`
ints/ScoreboardValue.getScoreInfo missing no `score` provider in `loot.rs::int`
ints/ScoreboardValue.validate skip:data datapack validation
ints/StorageValue missing minor: no vanilla data uses storage providers
ints/StorageValue missing no `storage` provider in `loot.rs::int`
ints/Sum.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int add arm
ints/UniformGenerator.getIntUnsafe port crates/horizon-world/src/loot.rs::Context::int uniform arm: inclusive random int between min and max
ints/WeightedListValue.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_select weighted list pick (the default branch)
@@ -118,7 +119,7 @@ DirectoryValidator.validateSymlink skip:server server check of symlinks in worl
PathAllowList.getForFileSystem skip:server server file allow-list
PathAllowList.readPlain skip:server server file allow-list
PathAllowList.ConfigEntry.parse skip:server server file allow-list
BlockHitResult.hitBorder missing minor: BlockHit has no world-border flag; vanilla projectiles check it on a border hit
BlockHitResult.hitBorder missing `BlockHit` (`horizon-core/src/geometry/mod.rs`) carries the location, the direction, the block and `inside`, no world-border flag, so a projectile's border hit is not told apart from any other
BlockHitResult.withDirection skip:java copy helper (Rust struct update)
BlockHitResult.withPosition skip:java copy helper (Rust struct update)
HitResult.distanceTo port crates/horizon-client/src/game/fishing.rs::rebound the bobber's block-hit bounce: the squared distance to the hook scales the normalised velocity (`FishingHook.onHitBlock`)
@@ -149,7 +150,7 @@ IdenticalMerger.forMergedIndexes port crates/horizon-core/src/geometry/shape/joi
IndirectMerger.<init> skip:java list wrapper constructor
IndirectMerger.forMergedIndexes port crates/horizon-core/src/geometry/shape/join.rs::merge
IndirectMerger.getList skip:java list accessor
MinecartCollisionContext missing player: vanilla lets a rail cart pass through the block under its rail and the slope block of an ascending rail (MinecartCollisionContext); minecarts.rs move_cart has no such exclusion, so carts may bump on those blocks (read from code, not checked in game)
MinecartCollisionContext missing `game/minecarts.rs::move_cart` has no exclusion for the block a rail rests on or the slope block of an ascending rail, so a cart's move collides with them as with any other block
NonOverlappingMerger.forMergedIndexes port crates/horizon-core/src/geometry/shape/join.rs::disjoint
NonOverlappingMerger.forNonSwappedIndexes port crates/horizon-core/src/geometry/shape/join.rs::disjoint
NonOverlappingMerger.getDouble skip:java list accessor
1 SetStewEffectFunction.Builder skip:java builder for the stew effect list; Horizon keeps the effects in loot.rs Context::run (set_stew_effect)
2 SetWritableBookPagesFunction missing minor: no vanilla loot table uses set_writable_book_pages; Horizon's loot run has no arm for it `horizon-world/src/loot.rs::modify` has no `set_writable_book_pages` arm, and no vanilla loot table asks for one
3 SetWrittenBookPagesFunction missing minor: no vanilla loot table uses set_written_book_pages; Horizon's loot run has no arm for it no `set_written_book_pages` arm in `loot.rs::modify` (the arms run from `sequence` and `set_count` through `set_potion` to `set_stew_effect`); no vanilla loot table asks for one
4 SmeltItemFunction.run port crates/horizon-world/src/loot.rs::Context::run furnace_smelt arm: smelting recipe, use_input_count and stack cap
5 SmeltItemFunction.smelted skip:java static builder factory
6 ToggleTooltips home crates/horizon-client/src/game/commands/items.rs the `/item modify` arm toggle_tooltips writes the tooltip_display component's hide_tooltip and hidden_components as run does; the loot run has no arm, and no vanilla loot table uses it the `/item modify` arm of cmd_item_modify writes the tooltip_display component's hide_tooltip and hidden_components as run does; the loot run has no arm, and no vanilla loot table uses it
7 ToggleTooltips.run inline crates/horizon-client/src/game/commands/items.rs::Game::cmd_item_modify the `minecraft:toggle_tooltips` arm of the `/item modify` command adds each false toggle to `tooltip_display`'s hidden_components and keeps hide_tooltip; the loot run has no arm, so a loot table's toggle_tooltips is ignored (no vanilla loot table uses one)
8 LootContextParamSets skip:data loot parameter set registry and validation; Horizon reads its loot tables
9 AllOfCondition.allOf skip:java static builder
10 AllOfCondition.combine inline crates/horizon-items/src/items.rs::LootContext::condition all_of arm: every term must hold
17 ConditionUserBuilder skip:java builder interface for loot conditions
18 DamageSourceCondition.test port crates/horizon-world/src/loot.rs::Context::condition damage_source_properties arm (mob death loot)
19 EnchantmentActiveCheck.test inline crates/horizon-client/src/game/enchant_effects.rs::Game::tick_soul_speed the soul speed modifiers switch on and off here; the ENCHANTMENT_ACTIVE flag itself is not read as a condition
20 EntityHasScoreCondition.hasScore missing minor: no vanilla loot table uses entity_scores `loot.rs::condition` has no `entity_scores` arm, and no vanilla loot table asks for one
21 EntityHasScoreCondition.test missing minor: no vanilla loot table uses entity_scores same as `hasScore`: the condition dispatch (`inverted`, `all_of`, `any_of`, `entity_properties`, `killed_by_player`, the chance and location checks) has no scoreboard arm
22 EntityHasScoreCondition.validate skip:data datapack validation
23 EnvironmentAttributeCheck.createCodec skip:java codec
24 EnvironmentAttributeCheck.test missing minor: no vanilla data uses environment_attribute_check no `environment_attribute_check` arm in `loot.rs::condition`; no vanilla data uses it
25 ExplosionCondition.test port crates/horizon-items/src/items.rs::LootContext::condition survives_explosion arm: 1 / radius under an explosion (block loot; only block tables use it)
26 FloatValueCheck.validate skip:data datapack validation
27 IntValueCheck.validate skip:data datapack validation
40 MatchBlock.getReferencedContextParams skip:data validation metadata for context params
41 MatchBlock.test port crates/horizon-items/src/items.rs::LootContext::condition match_block arm: block id, state and property ranges; gap: minor: the block-entity part of the predicate is not read
42 MatchTool.test port crates/horizon-items/src/items.rs::LootContext::condition match_tool arm; gap: minor: item ids, tags and enchantment levels only, other ItemPredicate parts are not read
43 TimeCheck missing minor: time_check is used by no vanilla data file (datapacks only) no `time_check` arm in `loot.rs::condition`; no vanilla data file uses it, so only a datapack reaches it
44 WeatherCheck.test port crates/horizon-client/src/game/trident.rs::Game::channel_lightning channeling's thunder check (thundering only); gap: minor: raining is not read, channeling only uses thundering
45 NbtProviders skip:data registry bootstrap
46 AggregateProvider skip:java codec only
59 PowerProvider.validate skip:data datapack validation
60 RangeProvider.mapCodec skip:java codec
61 RangeProvider.validate skip:data datapack validation
62 StoredNumberAccess missing minor: storage-backed numbers; no vanilla data uses storage providers no storage-backed number provider: `loot.rs::level_value` and the int and float readers know constant, uniform, binomial, linear, clamped, fraction, levels_squared and lookup only
63 UnaryProvider skip:java codec only
64 floats/Average.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float avg arm
65 floats/ContextFloatProviders skip:data registry bootstrap
66 floats/Difference.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float sub arm
67 floats/EnchantmentLevelProvider.getFloatUnsafe port crates/horizon-items/src/enchant.rs::Enchantments::condition random_chance arm: chance at the enchantment level; gap: minor: only the random_chance use is read
68 floats/EnvironmentAttributeValue missing minor: no vanilla data uses environment_attribute providers no `environment_attribute` provider in `loot.rs::level_value`
69 floats/Length.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float length arm
70 floats/Maximum.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float max arm
71 floats/Minimum.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float min arm
74 floats/Quotient.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float div arm
75 ResolvableFloat.getFromItem port crates/horizon-client-render/src/providers.rs::Providers::speed furnace fuel speed multiplier (COOKING_FUEL); gap: minor: only the furnace fuel component is read
76 ResolvableFloat.wrap skip:java codec helper
77 floats/StorageValue missing minor: no vanilla data uses storage providers no `storage` provider in `loot.rs::level_value`
78 floats/Sum.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float add arm
79 floats/Truncate.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float truncate arm
80 floats/UniformGenerator.getFloatUnsafe port crates/horizon-world/src/loot.rs::Context::float uniform arm: min + random * (max - min)
86 ints/ContextIntProviders.compostable port crates/horizon-client/src/game/composter.rs::Game::compostable the item's compostable layer provider; evaluated when the composter takes it (Game::composter_add)
87 ints/ContextIntProviders.cooking port crates/horizon-client-render/src/providers.rs::Providers::burn_time fuel burn times (the cooking/time_* providers)
88 ints/Difference.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int sub arm
89 ints/EnvironmentAttributeValue missing minor: no vanilla data uses environment_attribute providers no `environment_attribute` provider in the int reader (`loot.rs::int`)
90 ints/FloorModulus.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int floor_mod arm
91 ints/FloorQuotient.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int floor_div arm
92 ints/Maximum.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int max arm
96 ints/Quotient.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int div arm
97 ResolvableInt.getFromItem port crates/horizon-client-render/src/providers.rs::Providers::burn_time furnace fuel burn time (COOKING_FUEL); gap: minor: only the furnace fuel component is read
98 ResolvableInt.wrap skip:java codec helper
99 ints/ScoreboardValue.getIntUnsafe missing minor: no vanilla data uses score providers no `score` provider in `loot.rs::int`
100 ints/ScoreboardValue.getScoreInfo missing minor: no vanilla data uses score providers no `score` provider in `loot.rs::int`
101 ints/ScoreboardValue.validate skip:data datapack validation
102 ints/StorageValue missing minor: no vanilla data uses storage providers no `storage` provider in `loot.rs::int`
103 ints/Sum.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int add arm
104 ints/UniformGenerator.getIntUnsafe port crates/horizon-world/src/loot.rs::Context::int uniform arm: inclusive random int between min and max
105 ints/WeightedListValue.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_select weighted list pick (the default branch)
119 PathAllowList.getForFileSystem skip:server server file allow-list
120 PathAllowList.readPlain skip:server server file allow-list
121 PathAllowList.ConfigEntry.parse skip:server server file allow-list
122 BlockHitResult.hitBorder missing minor: BlockHit has no world-border flag; vanilla projectiles check it on a border hit `BlockHit` (`horizon-core/src/geometry/mod.rs`) carries the location, the direction, the block and `inside`, no world-border flag, so a projectile's border hit is not told apart from any other
123 BlockHitResult.withDirection skip:java copy helper (Rust struct update)
124 BlockHitResult.withPosition skip:java copy helper (Rust struct update)
125 HitResult.distanceTo port crates/horizon-client/src/game/fishing.rs::rebound the bobber's block-hit bounce: the squared distance to the hook scales the normalised velocity (`FishingHook.onHitBlock`)
150 IndirectMerger.<init> skip:java list wrapper constructor
151 IndirectMerger.forMergedIndexes port crates/horizon-core/src/geometry/shape/join.rs::merge
152 IndirectMerger.getList skip:java list accessor
153 MinecartCollisionContext missing player: vanilla lets a rail cart pass through the block under its rail and the slope block of an ascending rail (MinecartCollisionContext); minecarts.rs move_cart has no such exclusion, so carts may bump on those blocks (read from code, not checked in game) `game/minecarts.rs::move_cart` has no exclusion for the block a rail rests on or the slope block of an ascending rail, so a cart's move collides with them as with any other block
154 NonOverlappingMerger.forMergedIndexes port crates/horizon-core/src/geometry/shape/join.rs::disjoint
155 NonOverlappingMerger.forNonSwappedIndexes port crates/horizon-core/src/geometry/shape/join.rs::disjoint
156 NonOverlappingMerger.getDouble skip:java list accessor
+1 -1
View File
@@ -95,7 +95,7 @@ AbstractHorseRenderer.extractRenderState port crates/horizon-client/src/mobs/equ
AbstractMinecartRenderer.submit port crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry gap: player: no hurt tilt when a cart is hit (hurt_time is not read here); minor: no per-cart offset jitter
AbstractMinecartRenderer.newRender missing minor: experimental minecart movement (new render path) is not implemented
AbstractMinecartRenderer.oldRender port crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry rail-based draw is the path Horizon has
AbstractMinecartRenderer.newExtractState missing minor: experimental minecart movement (new render path) is not implemented
AbstractMinecartRenderer.newExtractState port crates/horizon-client/src/game/remote.rs::Game::remote_minecart_lerp the cart's position and rotation sampled at the frame's partial tick from the steps a NewMinecartBehavior cart has queued; None when the cart has no lerp, and the renderer then keeps the entity's own interpolated position and rotation
AbstractMinecartRenderer.oldExtractState port crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry rail front and back points are taken from the cart's rail position
AbstractMinecartRenderer.getRenderOffset missing minor: only the new render path offsets the cart
AbstractSkeletonRenderer.extractRenderState port crates/horizon-client/src/mobs/anim.rs::setup gap: player: a skeleton's freeze-conversion shake (its isShaking) is not drawn; the aggressive and bow flags are read in setup
1 ServerData.write port crates/horizon-protocol/src/servers.rs::ServerEntry::to_tag gap: minor: no acceptedCodeOfConduct entry (Horizon never stores code of conduct acceptance)
95 AbstractMinecartRenderer.submit port crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry gap: player: no hurt tilt when a cart is hit (hurt_time is not read here); minor: no per-cart offset jitter
96 AbstractMinecartRenderer.newRender missing minor: experimental minecart movement (new render path) is not implemented
97 AbstractMinecartRenderer.oldRender port crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry rail-based draw is the path Horizon has
98 AbstractMinecartRenderer.newExtractState missing port crates/horizon-client/src/game/remote.rs::Game::remote_minecart_lerp minor: experimental minecart movement (new render path) is not implemented the cart's position and rotation sampled at the frame's partial tick from the steps a NewMinecartBehavior cart has queued; None when the cart has no lerp, and the renderer then keeps the entity's own interpolated position and rotation
99 AbstractMinecartRenderer.oldExtractState port crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry rail front and back points are taken from the cart's rail position
100 AbstractMinecartRenderer.getRenderOffset missing minor: only the new render path offsets the cart
101 AbstractSkeletonRenderer.extractRenderState port crates/horizon-client/src/mobs/anim.rs::setup gap: player: a skeleton's freeze-conversion shake (its isShaking) is not drawn; the aggressive and bow flags are read in setup
+6 -6
View File
@@ -9,7 +9,7 @@ EnderDragonRenderer.submit port crates/horizon-client/src/game/dragon.rs::Game::
EnderDragonRenderer.submitRays port crates/horizon-client/src/game/dragon.rs::Game::dragon_rays
EnderDragonRenderer.submitCrystalBeams port crates/horizon-client/src/game/dragon.rs::Game::crystal_beam
EnderDragonRenderer.extractRenderState inline crates/horizon-client/src/game/dragon.rs::Game::dragon_geometry flap, death fade, hurt overlay, nearest-crystal beam and egg distance read there
EndermanRenderer.getRenderOffset port crates/horizon-client/src/mobs/render.rs EndermanRenderer.getRenderOffset's random side offset for a creepy enderman
EndermanRenderer.getRenderOffset port crates/horizon-client/src/mobs/render.rs::enderman_render_offset EndermanRenderer.getRenderOffset's random side offset for a creepy enderman
EndermanRenderer.extractRenderState inline crates/horizon-client/src/mobs/enderman.rs::anim gap: minor: carried block is drawn by game/mob_blocks.rs, not from this pose
EntityRenderDispatcher.getRenderer skip:other renderer lookup; Horizon dispatches by mob kind in game.rs::Game::entity_geometry and game/creatures.rs::Game::mob_geometry
EntityRenderDispatcher.getAvatarRenderer port crates/horizon-client/src/mobs/render.rs::body slim or wide arm model by skin
@@ -33,7 +33,7 @@ EvokerFangsRenderer.submit port crates/horizon-client/src/game/fangs.rs::Game::f
EvokerFangsRenderer.extractRenderState inline crates/horizon-client/src/game/fangs.rs::Game::fangs_geometry yaw and bite progress read there
ExperienceOrbRenderer.submit inline crates/horizon-client/src/game.rs::Game::entity_geometry orb billboards with the pulsing colour, in the entity pass
ExperienceOrbRenderer.vertex inline crates/horizon-client/src/game.rs::Game::entity_geometry vertex built inline
FallingBlockRenderer.shouldRender missing minor: a falling block inside an identical block (vanilla hides it that tick) is still drawn; no such check in game.rs or falling_block.rs
FallingBlockRenderer.shouldRender port crates/horizon-client/src/game.rs::falling_block_visible a falling block whose own state is the state of the cell its feet are in is not drawn, so a NoGravity block or one that has just landed shows no model; the entity draw skips it and a unit test covers both cases
FallingBlockRenderer.submit inline crates/horizon-client/src/game.rs::Game::entity_geometry falling block pass with its baked block model
FallingBlockRenderer.extractRenderState inline crates/horizon-client/src/game.rs::Game::entity_geometry position, state, biome tint and light read there
FireworkEntityRenderer.extractRenderState port crates/horizon-client/src/game/fireworks.rs::Game::rocket_geometry angled rocket keeps its direction
@@ -65,7 +65,7 @@ HumanoidMobRenderer.getEquipmentIfRenderable port crates/horizon-client/src/mobs
IllagerRenderer.extractRenderState port crates/horizon-client/src/mobs/illager.rs::holding_crossbow crossbow charge and aggressive pose
IllusionerRenderer.extractRenderState port crates/horizon-client/src/mobs/illusioner.rs::offsets mirror-image offsets; casting flag
IronGolemRenderer.extractRenderState port crates/horizon-client/src/mobs/golem.rs::client_tick gap: player: the offered poppy is not drawn in the golem hand and cracks from damage are not drawn
IronGolemRenderer.setupRotations port crates/horizon-client/src/mobs/render.rs::base_transform_at (IronGolem branch) the 6.5-degree triangle wave over a 13-block walk phase
IronGolemRenderer.setupRotations port crates/horizon-client/src/mobs/render.rs::base_transform_at the 6.5-degree triangle wave over a 13-block walk phase
ItemEntityRenderer.extractRenderState port crates/horizon-client/src/game/item_draw.rs::Game::dropped_item_geometry bob offset, spin and spread
ItemEntityRenderer.renderMultipleFromCount port crates/horizon-client/src/item_render.rs::cluster_poses copies of a stack laid out by depth
ItemFrameRenderer.submit port crates/horizon-client/src/game/frames.rs::Game::frame_geometry frame model, item or map
@@ -96,7 +96,7 @@ PaintingRenderer.vertex inline crates/horizon-client/src/game/paintings.rs::Game
PandaRenderer.extractRenderState port crates/horizon-client/src/mobs/body_anim.rs::panda sit, lie, sneeze and roll states
ParrotRenderer.extractRenderState port crates/horizon-client/src/mobs/body_anim.rs::parrot pose and flap angle
PhantomRenderer.extractRenderState port crates/horizon-client/src/mobs/phantom.rs::anim gap: player: phantom size is not read, so phantoms never grow
PhantomRenderer.scale port crates/horizon-client/src/mobs/render.rs::species_scale (Species::Phantom) 1 + 0.15 * size, with the model's 1.3125 offset
PhantomRenderer.scale port crates/horizon-client/src/mobs/render.rs::species_scale 1 + 0.15 * size, with the model's 1.3125 offset
PhantomRenderer.setupRotations port crates/horizon-client/src/mobs/phantom.rs::anim gap: minor: body pitch (xRot) is not applied; wings and tail are
PigRenderer.bakeModels skip:data model baking; Horizon loads model data from files
PigRenderer.submit port crates/horizon-client/src/mobs/render.rs::body cold or normal model by variant
@@ -107,7 +107,7 @@ PiglinRenderer.extractRenderState port crates/horizon-client/src/mobs/anim.rs::h
PiglinRenderer.isShaking port crates/horizon-client/src/mobs/render.rs::shaking converting piglin shakes
PolarBearRenderer.extractRenderState port crates/horizon-client/src/mobs/body_anim.rs::polar_bear standing scale
PufferfishRenderer.submit port crates/horizon-client/src/mobs/render.rs::body small, medium or big model by size
PufferfishRenderer.setupRotations port crates/horizon-client/src/mobs/render.rs::pufferfish_bob / pufferfish_bob_y the 0.08-block bob
PufferfishRenderer.setupRotations port crates/horizon-client/src/mobs/render.rs::pufferfish_bob the 0.08-block bob
RabbitRenderer.extractRenderState port crates/horizon-client/src/mobs/body_anim.rs::rabbit hop and idle head-tilt animations; Toast name in mobs/render.rs::body
RavagerRenderer.extractRenderState port crates/horizon-client/src/mobs/ravager.rs::anim stun, bite and roar clocks
SalmonRenderer.setupRotations port crates/horizon-client/src/mobs/aquatic.rs::rotations the 4.3-amplitude swim wiggle and the flop
@@ -132,7 +132,7 @@ ThrownItemRenderer.submit port crates/horizon-client/src/game/creatures.rs::Game
ThrownItemRenderer.extractRenderState inline crates/horizon-client/src/game/creatures.rs::Game::mob_geometry item sprite read there
ThrownTridentRenderer.submit port crates/horizon-client/src/game/trident.rs::Game::trident_geometry gap: minor: foil glint is not drawn on a thrown trident
ThrownTridentRenderer.extractRenderState inline crates/horizon-client/src/game/trident.rs::Game::trident_geometry yaw, pitch and foil read there
TippableArrowRenderer.extractRenderState port crates/horizon-client/src/projectiles.rs brewing::potion_color gives a tipped arrow its potion colour
TippableArrowRenderer.extractRenderState port crates/horizon-client/src/projectiles.rs::Projectile::arrow_effect_color brewing::potion_color gives a tipped arrow its potion colour
TntMinecartRenderer.submitMinecartContents port crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry TNT block swell and white flash
TntMinecartRenderer.submitWhiteSolidBlock inline crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry white overlay built inline
TntMinecartRenderer.extractRenderState inline crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry fuse read from the cart
1 DolphinRenderer.extractRenderState port crates/horizon-client/src/game/mob_blocks.rs::Game::held_item_geometry gap: minor: isMoving (dolphin motion flag) is not read
9 EnderDragonRenderer.submitRays port crates/horizon-client/src/game/dragon.rs::Game::dragon_rays
10 EnderDragonRenderer.submitCrystalBeams port crates/horizon-client/src/game/dragon.rs::Game::crystal_beam
11 EnderDragonRenderer.extractRenderState inline crates/horizon-client/src/game/dragon.rs::Game::dragon_geometry flap, death fade, hurt overlay, nearest-crystal beam and egg distance read there
12 EndermanRenderer.getRenderOffset port crates/horizon-client/src/mobs/render.rs crates/horizon-client/src/mobs/render.rs::enderman_render_offset EndermanRenderer.getRenderOffset's random side offset for a creepy enderman
13 EndermanRenderer.extractRenderState inline crates/horizon-client/src/mobs/enderman.rs::anim gap: minor: carried block is drawn by game/mob_blocks.rs, not from this pose
14 EntityRenderDispatcher.getRenderer skip:other renderer lookup; Horizon dispatches by mob kind in game.rs::Game::entity_geometry and game/creatures.rs::Game::mob_geometry
15 EntityRenderDispatcher.getAvatarRenderer port crates/horizon-client/src/mobs/render.rs::body slim or wide arm model by skin
33 EvokerFangsRenderer.extractRenderState inline crates/horizon-client/src/game/fangs.rs::Game::fangs_geometry yaw and bite progress read there
34 ExperienceOrbRenderer.submit inline crates/horizon-client/src/game.rs::Game::entity_geometry orb billboards with the pulsing colour, in the entity pass
35 ExperienceOrbRenderer.vertex inline crates/horizon-client/src/game.rs::Game::entity_geometry vertex built inline
36 FallingBlockRenderer.shouldRender missing port crates/horizon-client/src/game.rs::falling_block_visible minor: a falling block inside an identical block (vanilla hides it that tick) is still drawn; no such check in game.rs or falling_block.rs a falling block whose own state is the state of the cell its feet are in is not drawn, so a NoGravity block or one that has just landed shows no model; the entity draw skips it and a unit test covers both cases
37 FallingBlockRenderer.submit inline crates/horizon-client/src/game.rs::Game::entity_geometry falling block pass with its baked block model
38 FallingBlockRenderer.extractRenderState inline crates/horizon-client/src/game.rs::Game::entity_geometry position, state, biome tint and light read there
39 FireworkEntityRenderer.extractRenderState port crates/horizon-client/src/game/fireworks.rs::Game::rocket_geometry angled rocket keeps its direction
65 IllagerRenderer.extractRenderState port crates/horizon-client/src/mobs/illager.rs::holding_crossbow crossbow charge and aggressive pose
66 IllusionerRenderer.extractRenderState port crates/horizon-client/src/mobs/illusioner.rs::offsets mirror-image offsets; casting flag
67 IronGolemRenderer.extractRenderState port crates/horizon-client/src/mobs/golem.rs::client_tick gap: player: the offered poppy is not drawn in the golem hand and cracks from damage are not drawn
68 IronGolemRenderer.setupRotations port crates/horizon-client/src/mobs/render.rs::base_transform_at (IronGolem branch) crates/horizon-client/src/mobs/render.rs::base_transform_at the 6.5-degree triangle wave over a 13-block walk phase
69 ItemEntityRenderer.extractRenderState port crates/horizon-client/src/game/item_draw.rs::Game::dropped_item_geometry bob offset, spin and spread
70 ItemEntityRenderer.renderMultipleFromCount port crates/horizon-client/src/item_render.rs::cluster_poses copies of a stack laid out by depth
71 ItemFrameRenderer.submit port crates/horizon-client/src/game/frames.rs::Game::frame_geometry frame model, item or map
96 PandaRenderer.extractRenderState port crates/horizon-client/src/mobs/body_anim.rs::panda sit, lie, sneeze and roll states
97 ParrotRenderer.extractRenderState port crates/horizon-client/src/mobs/body_anim.rs::parrot pose and flap angle
98 PhantomRenderer.extractRenderState port crates/horizon-client/src/mobs/phantom.rs::anim gap: player: phantom size is not read, so phantoms never grow
99 PhantomRenderer.scale port crates/horizon-client/src/mobs/render.rs::species_scale (Species::Phantom) crates/horizon-client/src/mobs/render.rs::species_scale 1 + 0.15 * size, with the model's 1.3125 offset
100 PhantomRenderer.setupRotations port crates/horizon-client/src/mobs/phantom.rs::anim gap: minor: body pitch (xRot) is not applied; wings and tail are
101 PigRenderer.bakeModels skip:data model baking; Horizon loads model data from files
102 PigRenderer.submit port crates/horizon-client/src/mobs/render.rs::body cold or normal model by variant
107 PiglinRenderer.isShaking port crates/horizon-client/src/mobs/render.rs::shaking converting piglin shakes
108 PolarBearRenderer.extractRenderState port crates/horizon-client/src/mobs/body_anim.rs::polar_bear standing scale
109 PufferfishRenderer.submit port crates/horizon-client/src/mobs/render.rs::body small, medium or big model by size
110 PufferfishRenderer.setupRotations port crates/horizon-client/src/mobs/render.rs::pufferfish_bob / pufferfish_bob_y crates/horizon-client/src/mobs/render.rs::pufferfish_bob the 0.08-block bob
111 RabbitRenderer.extractRenderState port crates/horizon-client/src/mobs/body_anim.rs::rabbit hop and idle head-tilt animations; Toast name in mobs/render.rs::body
112 RavagerRenderer.extractRenderState port crates/horizon-client/src/mobs/ravager.rs::anim stun, bite and roar clocks
113 SalmonRenderer.setupRotations port crates/horizon-client/src/mobs/aquatic.rs::rotations the 4.3-amplitude swim wiggle and the flop
132 ThrownItemRenderer.extractRenderState inline crates/horizon-client/src/game/creatures.rs::Game::mob_geometry item sprite read there
133 ThrownTridentRenderer.submit port crates/horizon-client/src/game/trident.rs::Game::trident_geometry gap: minor: foil glint is not drawn on a thrown trident
134 ThrownTridentRenderer.extractRenderState inline crates/horizon-client/src/game/trident.rs::Game::trident_geometry yaw, pitch and foil read there
135 TippableArrowRenderer.extractRenderState port crates/horizon-client/src/projectiles.rs crates/horizon-client/src/projectiles.rs::Projectile::arrow_effect_color brewing::potion_color gives a tipped arrow its potion colour
136 TntMinecartRenderer.submitMinecartContents port crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry TNT block swell and white flash
137 TntMinecartRenderer.submitWhiteSolidBlock inline crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry white overlay built inline
138 TntMinecartRenderer.extractRenderState inline crates/horizon-client/src/game/minecarts.rs::Game::cart_geometry fuse read from the cart
+1 -1
View File
@@ -101,7 +101,7 @@ SpawnerRenderer.submit port crates/horizon-client/src/game/spawner.rs::Game::spa
StandingSignRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: minor: the text filtering option is not read
StandingSignRenderer.createTransformations inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one front and back text transforms per attachment
TestInstanceRenderer.extractRenderState missing player: creative players placing a test instance block see no beacon beam, box or error markers
TestInstanceRenderer.submit missing player: creative players placing a test instance block see no beacon beam or box
TestInstanceRenderer.submit port crates/horizon-client/src/game/test_instances.rs::Game::test_instance_marker_geometry the per-block loop drawing the error markers and their billboards; the beam sections come from Game::test_instance_beam and the box from Game::bounding_box_gizmos, both drawn per frame
TestInstanceRenderer.submitErrorMarker port crates/horizon-client/src/gizmos.rs::error_marker_shape the red box and its text, drawn by Game::test_instance_marker_geometry
TestInstanceRenderer.shouldRenderOffScreen missing minor: culling policy of a renderer Horizon lacks
TestInstanceRenderer.shouldRender missing minor: culling policy of a renderer Horizon lacks
Can't render this file because it contains an unexpected character in line 13 and column 112.
+16 -16
View File
@@ -5,7 +5,7 @@ CloudRenderer.isEastEmpty inline crates/horizon-client-render/src/clouds.rs::Mes
CloudRenderer.isSouthEmpty inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
CloudRenderer.isWestEmpty inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
CloudRenderer.render port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame gap: minor: one cloud pipeline; fancy vs flat is chosen by the mesh fast flag, not by pipeline
CloudRenderer.renderOit port crates/horizon-client-render/src/gfx/oit.rs the clouds draw through the OIT pipeline there, with their own depth beside the world's
CloudRenderer.renderOit port crates/horizon-client-render/src/gfx/oit.rs::Oit::render_clouds the clouds draw through the OIT pipeline there, with their own depth beside the world's
CloudRenderer.buildMesh port crates/horizon-client-render/src/clouds.rs::Mesh::prepare cell ring walk via cells(radius), nearest first
CloudRenderer.tryBuildCell inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare skip empty cells, then build the cell's faces
CloudRenderer.encodeFace inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare 3-byte face records replaced by vertex quads
@@ -40,7 +40,7 @@ FaceInfo.Extent.select skip:data corner selection; Horizon's unit-cube corner t
FaceInfo.VertexInfo.select skip:data corner selection; Horizon's unit-cube corner table PORTAL_FACES in block_entity_render.rs has the same order
FirstPersonHandsAndItemsRenderer.calculateMapTilt inline crates/horizon-client/src/game/maps.rs::Game::two_handed_map_geometry tilt computed inline with the calculateMapTilt comment
FirstPersonHandsAndItemsRenderer.renderPlayerHand port crates/horizon-client/src/game.rs::Game::hand_geometry empty main hand's arm model with the player skin
FirstPersonHandsAndItemsRenderer.renderMapHand port crates/horizon-client/src/first_person.rs::map_arm_poses (MapHold::TwoHanded) both arms behind a two-handed map, at the jar's rotations and offsets
FirstPersonHandsAndItemsRenderer.renderMapHand port crates/horizon-client/src/first_person.rs::map_arm_poses both arms behind a two-handed map, at the jar's rotations and offsets
FirstPersonHandsAndItemsRenderer.renderOneHandedMap port crates/horizon-client/src/first_person.rs::one_handed_map_pose gap: player: the arm holding a map is not drawn; the map hand branch skips the arm
FirstPersonHandsAndItemsRenderer.renderTwoHandedMap port crates/horizon-client/src/game/maps.rs::Game::two_handed_map_geometry gap: player: the arms that hold a two-handed map are not drawn
FirstPersonHandsAndItemsRenderer.renderMap port crates/horizon-client/src/game/maps.rs::Game::held_map map background with its 7-pixel border, then the map contents
@@ -57,17 +57,17 @@ GameRenderer.processBlurEffect port crates/horizon-client-render/src/menu_blur.r
GameRenderer.preloadUiShader skip:other GPU pipeline precompile at load; Horizon builds its pipelines in Renderer::new
GameRenderer.update port crates/horizon-client-render/src/camera.rs::AvatarBob::update_bob gap: minor: the requested post-effect list (from /postEffect) is not built
GameRenderer.extract skip:other render-state copy for the vanilla render thread; Horizon renders from game state
GameRenderer.preparePostEffects port crates/horizon-client-render/src/gfx.rs::post_effects (post_chain::PostEffects, built at gfx.rs:8424) vanilla post chains are made on first use from the request the game builds
GameRenderer.applyPostEffects port crates/horizon-client-render/src/gfx.rs::post_effects the same chain runs after the world pass
GameRenderer.tryTakeScreenshotIfNeeded port crates/horizon-client/src/game.rs::tick_world_icon (called at game.rs:17852) a local world without an icon requests a 64x64 ScreenshotRequest for saves/<folder>/icon.png once the streamer, uploads and backlog are idle and more than 10 sections are drawn
GameRenderer.preparePostEffects port crates/horizon-client-render/src/post_chain.rs::PostEffects::apply vanilla post chains are made on first use from the request the game builds
GameRenderer.applyPostEffects port crates/horizon-client-render/src/post_chain.rs::PostEffects::apply the same chain runs after the world pass
GameRenderer.tryTakeScreenshotIfNeeded port crates/horizon-client/src/game.rs::Game::tick_world_icon a local world without an icon requests a 64x64 ScreenshotRequest for saves/<folder>/icon.png once the streamer, uploads and backlog are idle and more than 10 sections are drawn
GameRenderer.takeAutoScreenshot inline crates/horizon-client/src/game.rs::tick_world_icon the same retry, once a second until the file exists; the path comes from game/integrated.rs:485
GameRenderer.integrate3DHudDepth inline crates/horizon-client-render/src/gfx.rs::post_effects runs with a post chain applied, which is vanilla's consistentDepthRequired condition
GameRenderer.integrate3DHudDepth inline crates/horizon-client-render/src/post_chain.rs::PostEffects::apply runs with a post chain applied, which is vanilla's consistentDepthRequired condition
GameRenderer.extractWindow skip:other render-state copy of the window size; Horizon reads the window directly
GameRenderer.extractOptions skip:other render-state copy of options; Horizon reads settings directly
GameRenderer.extractCamera skip:other render-state copy of camera and fog; Horizon computes them in camera.rs and fog.rs
GameRenderer.resetData inline crates/horizon-client/src/game.rs::Game::finish_close_world boss overlay reset on world close; camera and map-texture resets not verified here
GameRenderer.projectPointToScreen skip:other no callers in vanilla 26.3
GameRenderer.useImprovedTransparency port crates/horizon-client-render/src/gfx.rs::improved_transparency / oit: Option<oit::Oit> the option builds the OIT pass
GameRenderer.useImprovedTransparency port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame the option builds the OIT pass
GlobalSettingsUniform.update skip:other global uniform (window size, time, gamma, glint) written per pass; Horizon sets its own uniforms
GpuWarnlistManager.getAllWarnings missing minor: no GPU driver warnlist; Horizon does not check the driver against known-bad lists
GpuWarnlistManager.compilePatterns missing minor: no GPU driver warnlist patterns
@@ -82,9 +82,9 @@ LevelRenderer.addMainPass port crates/horizon-client-render/src/gfx.rs::Renderer
LevelRenderer.executeAlwaysOnTop port crates/horizon-client/src/game.rs::Game::render always-on-top debug lines and crosshair drawn over the world
LevelRenderer.executeSolid port crates/horizon-client-render/src/gfx.rs::Renderer::draw_sections solid section draws in arena page order
LevelRenderer.prepareTranslucents port crates/horizon-client-render/src/gfx/translucency.rs::Sorter::sort translucent quads sorted from the camera each frame
LevelRenderer.executeOit port crates/horizon-client-render/src/gfx/oit.rs + gfx::mark_oit (gfx/entity_batch.rs:31) translucent geometry is marked and resolved by the OIT pass
LevelRenderer.executeDepthBoundsCull inline crates/horizon-client-render/src/gfx/oit.rs part of the same pass
LevelRenderer.executeOitWaterMask port crates/horizon-client-render/src/gfx.rs::WATER_MASK_OVERLAY the water mask overlay of the same pass
LevelRenderer.executeOit port crates/horizon-client-render/src/gfx/oit.rs::Oit::run translucent geometry is marked and resolved by the OIT pass
LevelRenderer.executeDepthBoundsCull inline crates/horizon-client-render/src/gfx/oit.rs::Oit::execute_depth_bounds_cull part of the same pass
LevelRenderer.executeOitWaterMask port crates/horizon-client-render/src/gfx/oit.rs::Oit::execute_water_mask the water mask overlay of the same pass
LevelRenderer.extractSectionDrawGroups port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame section draws grouped per layer and arena page from the visible sections
LevelRenderer.prepareChunkRenders port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame per-layer draw list built for the frame, reversed for translucent layers
LevelRenderer.performTransientBlockRemovals inline crates/horizon-client/src/game/transient_blocks.rs::add_transient_block transient blocks are tracked and dropped by that list
@@ -93,7 +93,7 @@ LevelRenderer.compileSections port crates/horizon-client-render/src/streaming.rs
LevelRenderer.submitEntities port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame entity models drawn with their batches
LevelRenderer.submitBlockEntities port crates/horizon-client-render/src/gfx.rs::Renderer::block_entities block entity list submitted for drawing
LevelRenderer.submitBlockDestroyAnimation port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame crumbling overlay drawn on the block being broken
LevelRenderer.submitTransientBlocks port crates/horizon-client/src/game/remote.rs: C::AddTransientBlock => add_transient_block the packet is handled and the block is drawn
LevelRenderer.submitTransientBlocks port crates/horizon-client/src/game/transient_blocks.rs::Game::transient_block_geometry the packet is handled and the block is drawn
LevelRenderer.submitHitOutline port crates/horizon-client/src/game.rs::Game::outline_boxes outline of the block under the crosshair
LevelRenderer.close skip:other GPU resource teardown
LevelRenderer.invalidateCompiledGeometry port crates/horizon-client-render/src/gfx.rs::Renderer::clear_sections all section meshes dropped and rebuilt when settings are applied
@@ -134,11 +134,11 @@ PlayerSkinRenderCache.RenderInfo.textureView skip:other GPU texture view cache;
PlayerSkinRenderCache.RenderInfo.glyphRenderTypes skip:other font render types for name tags; Horizon's text uses its own glyph pipeline
PlayerSkinRenderCache.RenderInfo.equals skip:java value equality for a cache key
PlayerSkinRenderCache.RenderInfo.hashCode skip:java hash for a cache key
PostChain.load missing minor: no data-driven post chain loader; only the blur and entity outline chains exist, hard-coded
PostChain.getReferencedExternalTargets skip:other config validation for data-driven post chains
PostChain.createPass missing minor: no data-driven post pass creation; the outline and blur passes are fixed
PostChain.addToFrame port crates/horizon-client-render/src/gfx/entity_outline.rs::EntityOutline::draw gap: minor: runs the entity outline chain only; other chains are not built
PostChain.process port crates/horizon-client-render/src/menu_blur.rs::MenuBlur::apply gap: minor: runs the menu blur only; other chains are not run
PostChain.load port crates/horizon-client-render/src/post_chain.rs::PostEffects::load reads the chain's JSON, refuses a target other than the frame ("Referenced external targets are not available in this context"), builds its internal targets and passes; None when the file is absent
PostChain.getReferencedExternalTargets inline crates/horizon-client-render/src/post_chain.rs::PostEffects::load the external targets a chain names, checked against the frame the only external target Horizon has
PostChain.createPass port crates/horizon-client-render/src/post_chain.rs::PostEffects::translated_pass the pass's GLSL translated, its targets and images bound, its uniforms packed, and a pass reading the target it writes refused
PostChain.addToFrame port crates/horizon-client-render/src/gfx/entity_outline.rs::EntityOutline::draw gap: minor: this is the silhouette pass; a chain's own passes run in post_chain.rs::PostEffects::apply, which builds whatever chain id the frame's request names
PostChain.process port crates/horizon-client-render/src/menu_blur.rs::MenuBlur::apply gap: minor: this is the menu's own two-pass blur; the chain runner is post_chain.rs::PostEffects::apply
PostChain.getOrCreatePersistentTarget skip:other GPU persistent target cache
PostChain.close skip:other GPU resource teardown
PostChain.TargetBundle.getOrThrow skip:other frame-graph target lookup; Horizon has no frame graph
Can't render this file because it contains an unexpected character in line 137 and column 150.
+2 -2
View File
@@ -12,8 +12,8 @@ ChatComponent.scrollChat port crates/horizon-client-render/src/chat.rs::Chat::sc
ChatComponent.getHeight port crates/horizon-client-render/src/chat.rs::ChatOptions::height_px focused or unfocused chat height in px
ChatComponent.defaultUnfocusedPct inline crates/horizon-client-render/src/chat.rs::ChatOptions::default the default 70/160 is stored as a constant, not computed from the height formula
ChatComponent.getLineHeight port crates/horizon-client-render/src/chat.rs::ChatOptions::line_height 9 * (line spacing + 1)
ChatComponent.saveAsDraft missing player: Save Chat Drafts does nothing (options_ui.rs notes no drafts yet); text left in the box is not kept for the next open
ChatComponent.createScreen port crates/horizon-client/src/game/chat.rs::Game::open_chat gap: player: no draft is restored; the chat opens with the initial text its caller passes
ChatComponent.saveAsDraft port crates/horizon-client/src/game/chat.rs::Game::keep_chat_draft the text left in the box becomes the draft unless the screen was closed on purpose without Save Chat Drafts or the text was sent
ChatComponent.createScreen port crates/horizon-client/src/game/chat.rs::Game::open_chat_method T or / opens the chat with the draft kept last time, a command draft only for /, else with its prefix, and records whether the box starts on a draft
ChatComponent.preserveCurrentChatScreen missing minor: unsure: Horizon keeps no chat screen across a dimension change; game/nether_portal.rs:577 only leaves Chat alone during a portal effect, the change path was not checked
ChatComponent.storeState inline crates/horizon-client/src/game/remote.rs::Game::poll_remote no snapshot: the Reconfigured arm only resets boss bars and registries, so the chat survives a reconfigure without a copy
ChatComponent.restoreState inline crates/horizon-client/src/game/remote.rs::Game::poll_remote no restore: the chat is never cleared on Reconfigured, so there is nothing to put back
1 BossHealthOverlay.extractBar port crates/horizon-client-render/src/hud_overlays.rs::boss_overlay background bar, then progress at the lerped width; the notch overlay only when not PROGRESS
12 ChatComponent.getHeight port crates/horizon-client-render/src/chat.rs::ChatOptions::height_px focused or unfocused chat height in px
13 ChatComponent.defaultUnfocusedPct inline crates/horizon-client-render/src/chat.rs::ChatOptions::default the default 70/160 is stored as a constant, not computed from the height formula
14 ChatComponent.getLineHeight port crates/horizon-client-render/src/chat.rs::ChatOptions::line_height 9 * (line spacing + 1)
15 ChatComponent.saveAsDraft missing port crates/horizon-client/src/game/chat.rs::Game::keep_chat_draft player: Save Chat Drafts does nothing (options_ui.rs notes no drafts yet); text left in the box is not kept for the next open the text left in the box becomes the draft unless the screen was closed on purpose without Save Chat Drafts or the text was sent
16 ChatComponent.createScreen port crates/horizon-client/src/game/chat.rs::Game::open_chat crates/horizon-client/src/game/chat.rs::Game::open_chat_method gap: player: no draft is restored; the chat opens with the initial text its caller passes T or / opens the chat with the draft kept last time, a command draft only for /, else with its prefix, and records whether the box starts on a draft
17 ChatComponent.preserveCurrentChatScreen missing minor: unsure: Horizon keeps no chat screen across a dimension change; game/nether_portal.rs:577 only leaves Chat alone during a portal effect, the change path was not checked
18 ChatComponent.storeState inline crates/horizon-client/src/game/remote.rs::Game::poll_remote no snapshot: the Reconfigured arm only resets boss bars and registries, so the chat survives a reconfigure without a copy
19 ChatComponent.restoreState inline crates/horizon-client/src/game/remote.rs::Game::poll_remote no restore: the chat is never cleared on Reconfigured, so there is nothing to put back
+3 -3
View File
@@ -32,11 +32,11 @@ TabNavigationBar.narrationPriority inline crates/horizon-client/src/screen_narra
TabNavigationBar.updateWidgetNarration port crates/horizon-client/src/screen_narrator.rs::narrate the tabs list's narration: gui.narrate.tab of the tab under the mouse else the open one, plus narration.tab_navigation.usage while the row has focus
TabNavigationBar.narrateListElementPosition inline crates/horizon-client/src/screen_narrator.rs::narrate narrator.position.tab with the tab's index and the tab count, in the same branch
TabNavigationBar.extractWidgetRenderState port crates/horizon-client/src/title/create_world.rs::Title::draw_create tab row drawn in draw_create
TabNavigationBar.selectTab port crates/horizon-client/src/title.rs::Title::click gap: minor: only clicks pick a tab; no select by index (Ctrl+number, init)
TabNavigationBar.selectTab port crates/horizon-client/src/title.rs::Title::select_create_tab the tab set by index, the focus cleared, from a click on the row and from Ctrl+number
TabNavigationBar.setTabActiveState inline crates/horizon-client/src/game/stats_screen.rs::Game::stats_tab_active the Items and Mobs tabs are active only while their list has rows: an inactive tab does not click (stats_screen.rs:582) and its label draws inactive (stats_screen.rs:317-337)
TabNavigationBar.setTabTooltip missing minor: the tab row has no per-tab tooltip
TabNavigationBar.keyPressed missing minor: Ctrl+1 to 9 and Ctrl+Tab do not switch create-world tabs
TabNavigationBar.getNextTabIndex missing minor: index math for Ctrl+number and Ctrl+Tab, which Horizon lacks
TabNavigationBar.keyPressed port crates/horizon-client/src/title.rs::Title::tab_bar_key Ctrl+1 to Ctrl+0 and Ctrl+Tab (back with Shift) switch the Create World tabs, a digit past the last clamping to it; the rules and experiments screens keep the keys first
TabNavigationBar.getNextTabIndex port crates/horizon-client/src/title/create_world.rs::next_tab_index the tab Ctrl plus a digit asks for, Ctrl+1 the first and Ctrl+0 the tenth, or one step for Tab, wrapping; the caller clamps to the tabs there are
TabNavigationBar.currentTabIndex skip:other accessor; Horizon keeps the open tab in options.tab
TabNavigationBar.currentTabButton skip:other accessor; Horizon draws the tabs from options.tab
TabNavigationBar.Builder.addTab skip:java builder
1 ContainerEventHandler.getChildAt port crates/horizon-client-render/src/pack_screen.rs::PackScreen::button_at gap: minor: says whether a button is under the pointer; list rows are hit-tested in PackScreen::click
32 TabNavigationBar.updateWidgetNarration port crates/horizon-client/src/screen_narrator.rs::narrate the tabs list's narration: gui.narrate.tab of the tab under the mouse else the open one, plus narration.tab_navigation.usage while the row has focus
33 TabNavigationBar.narrateListElementPosition inline crates/horizon-client/src/screen_narrator.rs::narrate narrator.position.tab with the tab's index and the tab count, in the same branch
34 TabNavigationBar.extractWidgetRenderState port crates/horizon-client/src/title/create_world.rs::Title::draw_create tab row drawn in draw_create
35 TabNavigationBar.selectTab port crates/horizon-client/src/title.rs::Title::click crates/horizon-client/src/title.rs::Title::select_create_tab gap: minor: only clicks pick a tab; no select by index (Ctrl+number, init) the tab set by index, the focus cleared, from a click on the row and from Ctrl+number
36 TabNavigationBar.setTabActiveState inline crates/horizon-client/src/game/stats_screen.rs::Game::stats_tab_active the Items and Mobs tabs are active only while their list has rows: an inactive tab does not click (stats_screen.rs:582) and its label draws inactive (stats_screen.rs:317-337)
37 TabNavigationBar.setTabTooltip missing minor: the tab row has no per-tab tooltip
38 TabNavigationBar.keyPressed missing port crates/horizon-client/src/title.rs::Title::tab_bar_key minor: Ctrl+1 to 9 and Ctrl+Tab do not switch create-world tabs Ctrl+1 to Ctrl+0 and Ctrl+Tab (back with Shift) switch the Create World tabs, a digit past the last clamping to it; the rules and experiments screens keep the keys first
39 TabNavigationBar.getNextTabIndex missing port crates/horizon-client/src/title/create_world.rs::next_tab_index minor: index math for Ctrl+number and Ctrl+Tab, which Horizon lacks the tab Ctrl plus a digit asks for, Ctrl+1 the first and Ctrl+0 the tenth, or one step for Tab, wrapping; the caller clamps to the tabs there are
40 TabNavigationBar.currentTabIndex skip:other accessor; Horizon keeps the open tab in options.tab
41 TabNavigationBar.currentTabButton skip:other accessor; Horizon draws the tabs from options.tab
42 TabNavigationBar.Builder.addTab skip:java builder
+2 -2
View File
@@ -37,13 +37,13 @@ ServerSelectionList.updateOnlineServers inline crates/horizon-client/src/multipl
ServerSelectionList.updateNetworkServers inline crates/horizon-client/src/multiplayer.rs::rows LAN games are read each frame in rows; one found since the last call narrates multiplayer.lan.server_found with its getServerNarration (Screen::lan_found_narrations)
ServerSelectionList.LANHeader.extractContent port crates/horizon-client/src/multiplayer.rs::Screen::draw scanning label and loading dots drawn in the list
ServerSelectionList.NetworkServerEntry.extractContent port crates/horizon-client/src/multiplayer.rs::Screen::draw gap: minor: the address is always shown, Hide Server Address (hideServerAddress) is not read; LAN World title, MOTD and address
ServerSelectionList.NetworkServerEntry.keyPressed missing minor: no keyboard join on the server list (Enter)
ServerSelectionList.NetworkServerEntry.keyPressed port crates/horizon-client/src/multiplayer.rs::Screen::confirm Enter joins the selected LAN game: the index past the saved servers picks it out of the scanner and connects to its address
ServerSelectionList.NetworkServerEntry.join port crates/horizon-client/src/multiplayer.rs::Screen::click LAN game joined by a click on its row (Row::Lan) or a second click on the selected row
ServerSelectionList.NetworkServerEntry.matches skip:java selection is kept by index, so no entry identity match is needed
ServerSelectionList.OnlineServerEntry.extractContent port crates/horizon-client/src/multiplayer.rs::Screen::draw player: name, MOTD, ping bars, player count, hover join and move sprites; a server on another protocol shows its version in red in place of the player count and the count carries the playerList tooltip
ServerSelectionList.OnlineServerEntry.refreshStatus port crates/horizon-client/src/multiplayer.rs::ping_tooltip ping tooltip: ms, no connection or pinging; the player count's playerList tooltip is player_list_tooltip; the bar choice is in Screen::draw
ServerSelectionList.OnlineServerEntry.extractIcon port crates/horizon-client/src/multiplayer.rs::draw_server_icon server icon at 32x32
ServerSelectionList.OnlineServerEntry.keyPressed missing minor: no keyboard path: Enter does not join and Shift+Up/Down does not move a server
ServerSelectionList.OnlineServerEntry.keyPressed port crates/horizon-client/src/multiplayer.rs::Screen::confirm Enter joins the selected server as a click on it does; Shift+Up/Down swaps it with its neighbour and saves the list in Screen::arrow
ServerSelectionList.OnlineServerEntry.swap port crates/horizon-client/src/multiplayer.rs::Screen::click icon arrow click swaps the server up or down and saves
ServerSelectionList.OnlineServerEntry.getNarration port crates/horizon-client/src/multiplayer.rs::Screen::server_narration the entry's name then its status, joined for the screen narrator
ServerSelectionList.OnlineServerEntry.matches skip:java selection is kept by index, so no entry identity match is needed
1 ClientBundleTooltip.extractEmptyBundleTooltip port crates/horizon-client/src/game/stations.rs::Game::draw_bundle_tooltip empty branch: description, bar and label drawn from the layout
37 ServerSelectionList.updateNetworkServers inline crates/horizon-client/src/multiplayer.rs::rows LAN games are read each frame in rows; one found since the last call narrates multiplayer.lan.server_found with its getServerNarration (Screen::lan_found_narrations)
38 ServerSelectionList.LANHeader.extractContent port crates/horizon-client/src/multiplayer.rs::Screen::draw scanning label and loading dots drawn in the list
39 ServerSelectionList.NetworkServerEntry.extractContent port crates/horizon-client/src/multiplayer.rs::Screen::draw gap: minor: the address is always shown, Hide Server Address (hideServerAddress) is not read; LAN World title, MOTD and address
40 ServerSelectionList.NetworkServerEntry.keyPressed missing port crates/horizon-client/src/multiplayer.rs::Screen::confirm minor: no keyboard join on the server list (Enter) Enter joins the selected LAN game: the index past the saved servers picks it out of the scanner and connects to its address
41 ServerSelectionList.NetworkServerEntry.join port crates/horizon-client/src/multiplayer.rs::Screen::click LAN game joined by a click on its row (Row::Lan) or a second click on the selected row
42 ServerSelectionList.NetworkServerEntry.matches skip:java selection is kept by index, so no entry identity match is needed
43 ServerSelectionList.OnlineServerEntry.extractContent port crates/horizon-client/src/multiplayer.rs::Screen::draw player: name, MOTD, ping bars, player count, hover join and move sprites; a server on another protocol shows its version in red in place of the player count and the count carries the playerList tooltip
44 ServerSelectionList.OnlineServerEntry.refreshStatus port crates/horizon-client/src/multiplayer.rs::ping_tooltip ping tooltip: ms, no connection or pinging; the player count's playerList tooltip is player_list_tooltip; the bar choice is in Screen::draw
45 ServerSelectionList.OnlineServerEntry.extractIcon port crates/horizon-client/src/multiplayer.rs::draw_server_icon server icon at 32x32
46 ServerSelectionList.OnlineServerEntry.keyPressed missing port crates/horizon-client/src/multiplayer.rs::Screen::confirm minor: no keyboard path: Enter does not join and Shift+Up/Down does not move a server Enter joins the selected server as a click on it does; Shift+Up/Down swaps it with its neighbour and saves the list in Screen::arrow
47 ServerSelectionList.OnlineServerEntry.swap port crates/horizon-client/src/multiplayer.rs::Screen::click icon arrow click swaps the server up or down and saves
48 ServerSelectionList.OnlineServerEntry.getNarration port crates/horizon-client/src/multiplayer.rs::Screen::server_narration the entry's name then its status, joined for the screen narrator
49 ServerSelectionList.OnlineServerEntry.matches skip:java selection is kept by index, so no entry identity match is needed
+1 -1
View File
@@ -130,7 +130,7 @@ ServerPlayer.getIpAddress skip:server for ban-ip and the dedicated server log
ServerPlayer.updateOptions port crates/horizon-client/src/lan/hub.rs::Hub::process_inbox the ClientInformation arm: view distance, main hand, model parts and chat visibility are kept
ServerPlayer.clientInformation skip:java getter
ServerPlayer.sendServerStatus port crates/horizon-client/src/lan/hub.rs::Hub::place_new_player ServerData with the motd and icon at join
ServerPlayer.updateInvisibilityStatus inline crates/horizon-client/src/game/lan.rs::Game::lan_host_state
ServerPlayer.updateInvisibilityStatus inline crates/horizon-client/src/lan/hub.rs::guest_body a spectator is always invisible, else the invisibility effect decides; guest_body feeds the shared flags sent by ServerEntity.sendDirtyEntityData (shared_flags)
ServerPlayer.drop port crates/horizon-client/src/game.rs::Game::player_drop item_dropped and drop statistics, thrower set
ServerPlayer.calculateGameModeForNewPlayer port crates/horizon-client/src/game/lan.rs::Game::lan_guest_joined default or forced game mode for a new or force-gamemode guest
ServerPlayer.storeGameTypes port crates/horizon-client/src/save.rs::Level::player_tag both are saved: playerGameType, and previousPlayerGameType (absent when unset, as storeNullable is), read back into previous_game_mode
1 ServerBossEvent.setProgress inline crates/horizon-client/src/game/bossbars.rs::Game::diff_boss_events UpdateProgress goes out when a viewer's last-sent progress differs
130 ServerPlayer.updateOptions port crates/horizon-client/src/lan/hub.rs::Hub::process_inbox the ClientInformation arm: view distance, main hand, model parts and chat visibility are kept
131 ServerPlayer.clientInformation skip:java getter
132 ServerPlayer.sendServerStatus port crates/horizon-client/src/lan/hub.rs::Hub::place_new_player ServerData with the motd and icon at join
133 ServerPlayer.updateInvisibilityStatus inline crates/horizon-client/src/game/lan.rs::Game::lan_host_state crates/horizon-client/src/lan/hub.rs::guest_body a spectator is always invisible, else the invisibility effect decides; guest_body feeds the shared flags sent by ServerEntity.sendDirtyEntityData (shared_flags)
134 ServerPlayer.drop port crates/horizon-client/src/game.rs::Game::player_drop item_dropped and drop statistics, thrower set
135 ServerPlayer.calculateGameModeForNewPlayer port crates/horizon-client/src/game/lan.rs::Game::lan_guest_joined default or forced game mode for a new or force-gamemode guest
136 ServerPlayer.storeGameTypes port crates/horizon-client/src/save.rs::Level::player_tag both are saved: playerGameType, and previousPlayerGameType (absent when unset, as storeNullable is), read back into previous_game_mode
+6 -6
View File
@@ -71,19 +71,19 @@ DolphinJumpGoal.surfaceIsClear inline crates/horizon-client/src/mobs/dolphin.rs:
DolphinJumpGoal.canContinueToUse port crates/horizon-client/src/mobs/dolphin.rs::Jump::can_continue
DoorInteractGoal.start inline crates/horizon-client/src/mobs/doors.rs::RaiderOpenDoor::start passed and the door direction only feed canContinueToUse, which both subclasses override, so they are not kept
DoorInteractGoal.tick inline crates/horizon-client/src/mobs/doors.rs::BreakDoor::tick the passed-the-door dot test is not kept: BreakDoor and RaiderOpenDoor both override can_continue
FollowFlockLeaderGoal port crates/horizon-client/src/mobs/aquatic.rs::FollowFlockLeader tick for tick with vanilla in mob_ai cod/salmon/tropical_fish_school; recruits in vanilla getEntitiesOfClass order (Mob.section)
FollowMobGoal port crates/horizon-client/src/mobs/goals.rs::FollowMob the mutual-look branch is left out: only parrots have the goal
FollowPlayerRiddenEntityGoal port crates/horizon-client/src/mobs/dolphin.rs::FollowPlayerRidden only the player the mob sees (the nearest) is considered; tick for tick with vanilla in mob_ai dolphin_boat_*
FollowFlockLeaderGoal home crates/horizon-client/src/mobs/aquatic.rs FollowFlockLeader in that file, tick for tick with vanilla in mob_ai cod/salmon/tropical_fish_school; recruits in vanilla getEntitiesOfClass order (Mob.section)
FollowMobGoal home crates/horizon-client/src/mobs/goals.rs FollowMob in that file; the mutual-look branch is left out: only parrots have the goal
FollowPlayerRiddenEntityGoal home crates/horizon-client/src/mobs/dolphin.rs FollowPlayerRidden in that file; only the player the mob sees (the nearest) is considered; tick for tick with vanilla in mob_ai dolphin_boat_*
GoalSelector.removeAllGoals port crates/horizon-client/src/mobs/goals.rs::GoalSelector::clear gap: minor: clear() takes no predicate and does not stop running goals
GoalSelector.removeGoal missing minor: no per-goal removal; callers (cat and ocelot trust, zombie break door, skeleton weapon swap, skeleton horse trap) fold the condition into can_use
GoalSelector.goalContainsAnyFlags inline crates/horizon-client/src/mobs/goals.rs::GoalSelector::tick flags & disabled
GoalSelector.goalCanBeReplacedForAllFlags inline crates/horizon-client/src/mobs/goals.rs::GoalSelector::tick the replaceable check over the locked slots
GoalSelector.tickRunningGoals port crates/horizon-client/src/mobs/goals.rs::GoalSelector::tick_running
GolemRandomStrollInVillageGoal inline crates/horizon-client/src/mobs/golem.rs::register_iron a named Stroll with a gate and a position; tick for tick with vanilla in mob_ai iron_golem_village_*
GolemRandomStrollInVillageGoal home crates/horizon-client/src/mobs/golem.rs a Stroll with interval 240 and village_stroll_position that register_iron adds at 4; tick for tick with vanilla in mob_ai iron_golem_village_*
MeleeAttackGoal.checkAndPerformAttack inline crates/horizon-client/src/mobs/goals.rs::MeleeAttack::tick reset cooldown, swing, do_hurt_target
MeleeAttackGoal.canPerformAttack inline crates/horizon-client/src/mobs/goals.rs::MeleeAttack::tick cooldown over, within melee range, line of sight
MoveBackToVillageGoal inline crates/horizon-client/src/mobs/golem.rs::register_iron a named Stroll with a gate and a position; tick for tick with vanilla in mob_ai iron_golem_village_*
MoveThroughVillageGoal inline crates/horizon-client/src/mobs/undead.rs::MoveThroughVillage zombies register it at priority 6 (the raider variant is raider.rs::MoveThroughVillage); no tick parity check yet
MoveBackToVillageGoal home crates/horizon-client/src/mobs/golem.rs the back Stroll register_iron adds at 2: interval 10, the outside_village gate and back_to_village_position; tick for tick with vanilla in mob_ai iron_golem_village_*
MoveThroughVillageGoal home crates/horizon-client/src/mobs/undead.rs MoveThroughVillage in that file: zombies add it at 6; the raider's nested Raider$RaiderMoveThroughVillageGoal is the separate MoveThroughVillage in raider.rs; no tick parity check yet
MoveToBlockGoal.moveMobToBlock inline crates/horizon-client/src/mobs/goals.rs::MoveToBlock::start nav_move_to the block centre one up
MoveTowardsTargetGoal.start inline crates/horizon-client/src/mobs/golem.rs::MoveTowardsTarget::can_use gap: minor: paths straight to the target entity in can_use, not to a random position towards it chosen in canUse
OpenDoorGoal.canContinueToUse port crates/horizon-client/src/mobs/doors.rs::RaiderOpenDoor::can_continue the raider goal does not close the door after, so it never continues
1 UseBonemeal.checkExtraStartConditions inline crates/horizon-client/src/mobs/villager_brain.rs::B::try_start every 10 ticks and not within 160 of the last session, with bone meal in the inventory and a growing crop in the 3x3x3
71 DolphinJumpGoal.canContinueToUse port crates/horizon-client/src/mobs/dolphin.rs::Jump::can_continue
72 DoorInteractGoal.start inline crates/horizon-client/src/mobs/doors.rs::RaiderOpenDoor::start passed and the door direction only feed canContinueToUse, which both subclasses override, so they are not kept
73 DoorInteractGoal.tick inline crates/horizon-client/src/mobs/doors.rs::BreakDoor::tick the passed-the-door dot test is not kept: BreakDoor and RaiderOpenDoor both override can_continue
74 FollowFlockLeaderGoal port home crates/horizon-client/src/mobs/aquatic.rs::FollowFlockLeader crates/horizon-client/src/mobs/aquatic.rs tick for tick with vanilla in mob_ai cod/salmon/tropical_fish_school; recruits in vanilla getEntitiesOfClass order (Mob.section) FollowFlockLeader in that file, tick for tick with vanilla in mob_ai cod/salmon/tropical_fish_school; recruits in vanilla getEntitiesOfClass order (Mob.section)
75 FollowMobGoal port home crates/horizon-client/src/mobs/goals.rs::FollowMob crates/horizon-client/src/mobs/goals.rs the mutual-look branch is left out: only parrots have the goal FollowMob in that file; the mutual-look branch is left out: only parrots have the goal
76 FollowPlayerRiddenEntityGoal port home crates/horizon-client/src/mobs/dolphin.rs::FollowPlayerRidden crates/horizon-client/src/mobs/dolphin.rs only the player the mob sees (the nearest) is considered; tick for tick with vanilla in mob_ai dolphin_boat_* FollowPlayerRidden in that file; only the player the mob sees (the nearest) is considered; tick for tick with vanilla in mob_ai dolphin_boat_*
77 GoalSelector.removeAllGoals port crates/horizon-client/src/mobs/goals.rs::GoalSelector::clear gap: minor: clear() takes no predicate and does not stop running goals
78 GoalSelector.removeGoal missing minor: no per-goal removal; callers (cat and ocelot trust, zombie break door, skeleton weapon swap, skeleton horse trap) fold the condition into can_use
79 GoalSelector.goalContainsAnyFlags inline crates/horizon-client/src/mobs/goals.rs::GoalSelector::tick flags & disabled
80 GoalSelector.goalCanBeReplacedForAllFlags inline crates/horizon-client/src/mobs/goals.rs::GoalSelector::tick the replaceable check over the locked slots
81 GoalSelector.tickRunningGoals port crates/horizon-client/src/mobs/goals.rs::GoalSelector::tick_running
82 GolemRandomStrollInVillageGoal inline home crates/horizon-client/src/mobs/golem.rs::register_iron crates/horizon-client/src/mobs/golem.rs a named Stroll with a gate and a position; tick for tick with vanilla in mob_ai iron_golem_village_* a Stroll with interval 240 and village_stroll_position that register_iron adds at 4; tick for tick with vanilla in mob_ai iron_golem_village_*
83 MeleeAttackGoal.checkAndPerformAttack inline crates/horizon-client/src/mobs/goals.rs::MeleeAttack::tick reset cooldown, swing, do_hurt_target
84 MeleeAttackGoal.canPerformAttack inline crates/horizon-client/src/mobs/goals.rs::MeleeAttack::tick cooldown over, within melee range, line of sight
85 MoveBackToVillageGoal inline home crates/horizon-client/src/mobs/golem.rs::register_iron crates/horizon-client/src/mobs/golem.rs a named Stroll with a gate and a position; tick for tick with vanilla in mob_ai iron_golem_village_* the back Stroll register_iron adds at 2: interval 10, the outside_village gate and back_to_village_position; tick for tick with vanilla in mob_ai iron_golem_village_*
86 MoveThroughVillageGoal inline home crates/horizon-client/src/mobs/undead.rs::MoveThroughVillage crates/horizon-client/src/mobs/undead.rs zombies register it at priority 6 (the raider variant is raider.rs::MoveThroughVillage); no tick parity check yet MoveThroughVillage in that file: zombies add it at 6; the raider's nested Raider$RaiderMoveThroughVillageGoal is the separate MoveThroughVillage in raider.rs; no tick parity check yet
87 MoveToBlockGoal.moveMobToBlock inline crates/horizon-client/src/mobs/goals.rs::MoveToBlock::start nav_move_to the block centre one up
88 MoveTowardsTargetGoal.start inline crates/horizon-client/src/mobs/golem.rs::MoveTowardsTarget::can_use gap: minor: paths straight to the target entity in can_use, not to a random position towards it chosen in canUse
89 OpenDoorGoal.canContinueToUse port crates/horizon-client/src/mobs/doors.rs::RaiderOpenDoor::can_continue the raider goal does not close the door after, so it never continues
+15 -15
View File
@@ -4,12 +4,12 @@ ItemFrame.recalculateBoundingBox inline crates/horizon-client/src/entities.rs::I
ItemFrame.shouldDamageDropItem inline crates/horizon-client/src/game/explosion.rs::Game::explosion_hurt_entities a blast breaks a frame in reach and drops its item (`explosion_breaks_frames`)
ItemFrame.canHurtWhenFixed port crates/horizon-client/src/game/frames.rs::attack_frame a fixed frame refuses a non-creative hit (attack_ticks reset, no break) and lets a creative player break it
ItemFrame.hurtClient inline crates/horizon-client/src/game/frames.rs::Game::attack_frame gap: minor: no separate client-side fixed-frame gate; the creative exception is missing
ItemFrame.removeFramedMap inline crates/horizon-client/src/game/maps.rs::Game::lan_map_updates frame map markers come from item_frames each tick, so a removed frame needs no step
ItemFrame.removeFramedMap port crates/horizon-client/src/game/maps.rs::Game::remove_framed_map the map the frame holds forgets the frame (`MapItemSavedData.removedFromFrame`: the decoration and the frames entry); drop_frame_items calls it when the frame breaks, and skips it only when the drops are off and a player caused the break, which is vanilla's null `causedBy`
ItemFrame.getSlot inline crates/horizon-client/src/game/commands/items.rs::Game::slot_get slots_named maps container.0 and contents on a frame to Slot::Frame; slot_get reads the frame's item and slot_set writes it
ItemFrame.onSyncedDataUpdated skip:other network data-sync hook; the item is read from the frame field on demand
ItemFrame.setRotation inline crates/horizon-client/src/game/frames.rs::Game::interact_frame the rotate-by-one step; the neighbour update is in frame_output_changed
ItemFrame.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::item_frame_from_nbt gap: minor: Facing defaults to 2 where vanilla defaults to DOWN
ItemFrame.getFrameItemStackWithData missing minor: a broken frame never carries its custom name; drop_frame_items builds a plain item_frame stack
ItemFrame.getFrameItemStackWithData missing `game/frames.rs::drop_frame_items` builds a plain `item_frame` stack, so a broken frame never carries its custom name
ItemFrame.getVisualRotationYInDegrees inline crates/horizon-client/src/game/frames.rs::Game::frame_geometry the 45 degree step per rotation step is drawn in frame_geometry
LeashFenceKnotEntity.recalculateBoundingBox port crates/horizon-client/src/mobs/leash.rs::knot_state position and box from the block position
LeashFenceKnotEntity.getOrCreateKnot inline crates/horizon-client/src/game/leads.rs::Game::interact_knot knots are not entities in game; the knot is used when a lead is tied
@@ -35,15 +35,15 @@ ItemEntity.hurtClient skip:other client-side hurt prediction; items are not att
ItemEntity.hurtServer inline crates/horizon-client/src/game/explosion.rs::Game::explosion_hurt_entities gap: minor: explosions destroy items at damage 5 without subtracting health; fire and lava hurt are in environment_tick
ItemEntity.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::item_entity_to_nbt
ItemEntity.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::item_entity_from_nbt a missing Item returns None, which matches the discard
ItemEntity.getName missing minor: a dropped item's name (custom or item name) is not used anywhere in Horizon
ItemEntity.getName missing a dropped item's name (custom or the item's own) is not read anywhere
ItemEntity.teleport inline crates/horizon-client/src/entities.rs::merge_with_neighbours the arrival merge is the normal per-tick merge, not a teleport step
ItemEntity.getVisualRotationYInDegrees inline crates/horizon-client/src/game/item_draw.rs::Game::dropped_item_geometry spin is applied in radians there; no degree conversion
ItemEntity.getSlot missing unsure: no slot 0 access for dropped items found in commands.rs
ItemEntity.getSlot missing a dropped item is not addressable as an entity slot: `game/commands.rs` has no item entity case
PrimedTnt.defineSynchedData skip:other fuse and block state are struct fields; spawn sets fuse 80; the block is always TNT
PrimedTnt.explode inline crates/horizon-client/src/game/tnt.rs::Game::tick_tnt the power is the tnt's own field (`explosion_power`, 4 by default); gap: minor: no used-portal damage calculator
PrimedTnt.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::tnt_to_nbt the fuse and the block state are saved
PrimedTnt.restoreFrom missing minor: PrimedTnt has no owner field, so the igniter is not kept through portal moves
PrimedTnt.teleport missing unsure: no used-portal flag on PrimedTnt; the portal damage calculator is not applied
PrimedTnt.restoreFrom missing `PrimedTnt` has no owner field, so the igniter is not kept through a dimension change
PrimedTnt.teleport missing `PrimedTnt` carries no used-portal flag and the portal damage calculator is not applied to it
Blaze.registerGoals port crates/horizon-client/src/mobs/nether.rs::register_blaze gap: minor: MoveTowardsRestriction goal (priority 5) is not registered
Blaze.createAttributes skip:data attribute defaults come from data tables
Blaze.isCharged inline crates/horizon-client/src/mobs/nether.rs::BlazeAttack::tick the charged state is m.extra.flag, set in the attack goal tick
@@ -88,7 +88,7 @@ Guardian.checkGuardianSpawnRules port crates/horizon-client/src/mobs/spawn_rules
Guardian.GuardianAttackGoal.canContinueToUse port crates/horizon-client/src/mobs/guardian.rs::GuardianAttack::can_continue
Guardian.GuardianAttackSelector.test inline crates/horizon-client/src/mobs/guardian.rs::register the selector is the player, squid and axolotl beyond 9 filters in register
Monster.updateNoActionTime port crates/horizon-client/src/mobs/ai.rs::server_ai_step the idle counter is counted up there and read by the despawn path in raids.rs
Monster.getProjectile missing unsure: no held-ammo choice for mobs found in mobs/*.rs
Monster.getProjectile missing no held-ammo choice in `mobs/*.rs`: a monster's shot takes its projectile kind from the species, not from the stack it holds
PatrollingMonster.addAdditionalSaveData port crates/horizon-client/src/mobs/raider.rs::write_nbt
PatrollingMonster.readAdditionalSaveData port crates/horizon-client/src/mobs/raider.rs::read_nbt
PatrollingMonster.checkPatrollingMonsterSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules the Pillager arm: block light 8 or less, then mob rules
@@ -103,12 +103,12 @@ Phantom.PhantomBodyRotationControl.clientTick inline crates/horizon-client/src/m
Phantom.PhantomMoveTargetGoal.touchingTarget inline crates/horizon-client/src/mobs/phantom.rs::CircleAroundAnchor::tick the squared distance under 4 check near the move target
Phantom.PhantomSweepAttackGoal.canContinueToUse port crates/horizon-client/src/mobs/phantom.rs::SweepAttack::can_continue the cat hiss and scare check
Ravager.registerGoals port crates/horizon-client/src/mobs/ravager.rs::register
Ravager.updateControlFlags missing unsure: no rider control-flag switch for ravagers in ravager.rs
Ravager.updateControlFlags missing the goal set has no control flags, so a mob driving a boat does not switch off the ravager's own move, jump and look goals
Ravager.createAttributes skip:data attribute defaults come from data tables
Ravager.addAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::mob_to_nbt AttackTick, StunTick and RoarTick are written there
Ravager.readAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::mob_from_nbt the same three tick values are read there
Ravager.stunEffect port crates/horizon-client/src/mobs/ravager.rs::ai_step the stun particle is emitted from ai_step
Ravager.hasLineOfSight missing unsure: no stunned or roaring line-of-sight cut-off in ravager.rs
Ravager.hasLineOfSight missing `ravager::immobile` knows the biting, stunned and roaring states, but no target goal is gated on it, so an immobile ravager still sees and takes targets
Ravager.applyRoarKnockbackClient inline crates/horizon-client/src/mobs/ravager.rs::roar the roar push is applied in roar
Ravager.strongKnockback inline crates/horizon-client/src/mobs/ravager.rs::roar the push of 4 along the direction is in roar
Ravager.addRoarParticleEffects inline crates/horizon-client/src/mobs/ravager.rs::roar the poof particles are emitted in roar
@@ -119,18 +119,18 @@ Shulker.addAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::mo
Shulker.findNewAttachment inline crates/horizon-client/src/mobs/shulker.rs::tick keeps its face or teleports in tick, through attachable and teleport
Shulker.makeBoundingBox port crates/horizon-client/src/mobs/shulker.rs::progress_aabb
Shulker.getDefaultDimensions inline crates/horizon-client/src/mobs/shulker.rs::progress_aabb gap: minor: the down-face height scale is not seen here
Shulker.startRiding missing unsure: no attach-face reset to DOWN when a shulker mounts a vehicle in shulker.rs
Shulker.stopRiding missing unsure: no body yaw reset after a shulker dismounts in shulker.rs
Shulker.move missing unsure: no shulker-box move type (teleport) found in the piston code
Shulker.startRiding missing no attach-face reset on boarding: `mobs/shulker.rs` sets `extra.variant3` from `Attachable` and the save only, so a shulker on a vehicle keeps the face it had
Shulker.stopRiding inline crates/horizon-client/src/mobs/shulker.rs::tick the body yaw is pinned to 0 on every tick (`shulker::tick`, and `client_tick` for a remote one), so a dismounted shulker shows no leftover body yaw without a reset of its own
Shulker.move missing the `SHULKER_BOX` mover type has no counterpart: `shulker::teleport` runs from the tick and the bullet hit only, so a shulker box pushed by a piston never takes its shulker along
Shulker.findAttachableSurface port crates/horizon-client/src/mobs/shulker.rs::attachable
Shulker.isPositionBlocked inline crates/horizon-client/src/mobs/shulker.rs::can_stay_at the blocked-position check is part of can_stay_at
Shulker.onSyncedDataUpdated skip:other network sync hook; the box is computed from the face on demand
Shulker.getRenderPosition missing unsure: no render slide after a shulker teleport in shulker.rs
Shulker.getRenderPosition missing no render slide: `client_tick` eases the lid and squares the body, and nothing interpolates the position across a teleport
Shulker.setVariant inline crates/horizon-client/src/mobs/shulker.rs::register colour comes from spawn data; NO_COLOR when unset
Shulker.getColor inline crates/horizon-client/src/mobs/shulker.rs::texture colour is read from extra.color for the texture
Shulker.get skip:other component getter; Horizon reads extra.color directly
Shulker.applyImplicitComponent missing minor: a shulker spawned with a SHULKER_COLOR component ignores it
Shulker.ShulkerDefenseAttackGoal.getTargetSearchArea missing minor: shulkers do not defend nearby shulkers; there is no defense target goal
Shulker.applyImplicitComponent missing a shulker spawned from a shulker box item ignores its colour: the implicit components are applied to a mob coming out of a bucket only (`entity_nbt::bucket_components`)
Shulker.ShulkerDefenseAttackGoal.getTargetSearchArea missing no defense target goal: `mobs/shulker.rs::register` gives the shulker `HurtByTarget` with `alert` (a hurt shulker wakes the targetless ones around it) and a player search box 4 deep along the attach axis, nothing that defends another shulker
Shulker.ShulkerNearestAttackGoal.getTargetSearchArea inline crates/horizon-client/src/mobs/shulker.rs::register the 4-deep search box is the when filter in register
Shulker.ShulkerPeekGoal.canContinueToUse port crates/horizon-client/src/mobs/shulker.rs::Peek::can_continue
Silverfish.registerGoals port crates/horizon-client/src/mobs/hostile.rs::register
1 ItemFrame.defineSynchedData skip:other entity data defaults; ItemFrame starts with item None and rotation 0 in place_item_frame
4 ItemFrame.shouldDamageDropItem inline crates/horizon-client/src/game/explosion.rs::Game::explosion_hurt_entities a blast breaks a frame in reach and drops its item (`explosion_breaks_frames`)
5 ItemFrame.canHurtWhenFixed port crates/horizon-client/src/game/frames.rs::attack_frame a fixed frame refuses a non-creative hit (attack_ticks reset, no break) and lets a creative player break it
6 ItemFrame.hurtClient inline crates/horizon-client/src/game/frames.rs::Game::attack_frame gap: minor: no separate client-side fixed-frame gate; the creative exception is missing
7 ItemFrame.removeFramedMap inline port crates/horizon-client/src/game/maps.rs::Game::lan_map_updates crates/horizon-client/src/game/maps.rs::Game::remove_framed_map frame map markers come from item_frames each tick, so a removed frame needs no step the map the frame holds forgets the frame (`MapItemSavedData.removedFromFrame`: the decoration and the frames entry); drop_frame_items calls it when the frame breaks, and skips it only when the drops are off and a player caused the break, which is vanilla's null `causedBy`
8 ItemFrame.getSlot inline crates/horizon-client/src/game/commands/items.rs::Game::slot_get slots_named maps container.0 and contents on a frame to Slot::Frame; slot_get reads the frame's item and slot_set writes it
9 ItemFrame.onSyncedDataUpdated skip:other network data-sync hook; the item is read from the frame field on demand
10 ItemFrame.setRotation inline crates/horizon-client/src/game/frames.rs::Game::interact_frame the rotate-by-one step; the neighbour update is in frame_output_changed
11 ItemFrame.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::item_frame_from_nbt gap: minor: Facing defaults to 2 where vanilla defaults to DOWN
12 ItemFrame.getFrameItemStackWithData missing minor: a broken frame never carries its custom name; drop_frame_items builds a plain item_frame stack `game/frames.rs::drop_frame_items` builds a plain `item_frame` stack, so a broken frame never carries its custom name
13 ItemFrame.getVisualRotationYInDegrees inline crates/horizon-client/src/game/frames.rs::Game::frame_geometry the 45 degree step per rotation step is drawn in frame_geometry
14 LeashFenceKnotEntity.recalculateBoundingBox port crates/horizon-client/src/mobs/leash.rs::knot_state position and box from the block position
15 LeashFenceKnotEntity.getOrCreateKnot inline crates/horizon-client/src/game/leads.rs::Game::interact_knot knots are not entities in game; the knot is used when a lead is tied
35 ItemEntity.hurtServer inline crates/horizon-client/src/game/explosion.rs::Game::explosion_hurt_entities gap: minor: explosions destroy items at damage 5 without subtracting health; fire and lava hurt are in environment_tick
36 ItemEntity.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::item_entity_to_nbt
37 ItemEntity.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::item_entity_from_nbt a missing Item returns None, which matches the discard
38 ItemEntity.getName missing minor: a dropped item's name (custom or item name) is not used anywhere in Horizon a dropped item's name (custom or the item's own) is not read anywhere
39 ItemEntity.teleport inline crates/horizon-client/src/entities.rs::merge_with_neighbours the arrival merge is the normal per-tick merge, not a teleport step
40 ItemEntity.getVisualRotationYInDegrees inline crates/horizon-client/src/game/item_draw.rs::Game::dropped_item_geometry spin is applied in radians there; no degree conversion
41 ItemEntity.getSlot missing unsure: no slot 0 access for dropped items found in commands.rs a dropped item is not addressable as an entity slot: `game/commands.rs` has no item entity case
42 PrimedTnt.defineSynchedData skip:other fuse and block state are struct fields; spawn sets fuse 80; the block is always TNT
43 PrimedTnt.explode inline crates/horizon-client/src/game/tnt.rs::Game::tick_tnt the power is the tnt's own field (`explosion_power`, 4 by default); gap: minor: no used-portal damage calculator
44 PrimedTnt.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::tnt_to_nbt the fuse and the block state are saved
45 PrimedTnt.restoreFrom missing minor: PrimedTnt has no owner field, so the igniter is not kept through portal moves `PrimedTnt` has no owner field, so the igniter is not kept through a dimension change
46 PrimedTnt.teleport missing unsure: no used-portal flag on PrimedTnt; the portal damage calculator is not applied `PrimedTnt` carries no used-portal flag and the portal damage calculator is not applied to it
47 Blaze.registerGoals port crates/horizon-client/src/mobs/nether.rs::register_blaze gap: minor: MoveTowardsRestriction goal (priority 5) is not registered
48 Blaze.createAttributes skip:data attribute defaults come from data tables
49 Blaze.isCharged inline crates/horizon-client/src/mobs/nether.rs::BlazeAttack::tick the charged state is m.extra.flag, set in the attack goal tick
88 Guardian.GuardianAttackGoal.canContinueToUse port crates/horizon-client/src/mobs/guardian.rs::GuardianAttack::can_continue
89 Guardian.GuardianAttackSelector.test inline crates/horizon-client/src/mobs/guardian.rs::register the selector is the player, squid and axolotl beyond 9 filters in register
90 Monster.updateNoActionTime port crates/horizon-client/src/mobs/ai.rs::server_ai_step the idle counter is counted up there and read by the despawn path in raids.rs
91 Monster.getProjectile missing unsure: no held-ammo choice for mobs found in mobs/*.rs no held-ammo choice in `mobs/*.rs`: a monster's shot takes its projectile kind from the species, not from the stack it holds
92 PatrollingMonster.addAdditionalSaveData port crates/horizon-client/src/mobs/raider.rs::write_nbt
93 PatrollingMonster.readAdditionalSaveData port crates/horizon-client/src/mobs/raider.rs::read_nbt
94 PatrollingMonster.checkPatrollingMonsterSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules the Pillager arm: block light 8 or less, then mob rules
103 Phantom.PhantomMoveTargetGoal.touchingTarget inline crates/horizon-client/src/mobs/phantom.rs::CircleAroundAnchor::tick the squared distance under 4 check near the move target
104 Phantom.PhantomSweepAttackGoal.canContinueToUse port crates/horizon-client/src/mobs/phantom.rs::SweepAttack::can_continue the cat hiss and scare check
105 Ravager.registerGoals port crates/horizon-client/src/mobs/ravager.rs::register
106 Ravager.updateControlFlags missing unsure: no rider control-flag switch for ravagers in ravager.rs the goal set has no control flags, so a mob driving a boat does not switch off the ravager's own move, jump and look goals
107 Ravager.createAttributes skip:data attribute defaults come from data tables
108 Ravager.addAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::mob_to_nbt AttackTick, StunTick and RoarTick are written there
109 Ravager.readAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::mob_from_nbt the same three tick values are read there
110 Ravager.stunEffect port crates/horizon-client/src/mobs/ravager.rs::ai_step the stun particle is emitted from ai_step
111 Ravager.hasLineOfSight missing unsure: no stunned or roaring line-of-sight cut-off in ravager.rs `ravager::immobile` knows the biting, stunned and roaring states, but no target goal is gated on it, so an immobile ravager still sees and takes targets
112 Ravager.applyRoarKnockbackClient inline crates/horizon-client/src/mobs/ravager.rs::roar the roar push is applied in roar
113 Ravager.strongKnockback inline crates/horizon-client/src/mobs/ravager.rs::roar the push of 4 along the direction is in roar
114 Ravager.addRoarParticleEffects inline crates/horizon-client/src/mobs/ravager.rs::roar the poof particles are emitted in roar
119 Shulker.findNewAttachment inline crates/horizon-client/src/mobs/shulker.rs::tick keeps its face or teleports in tick, through attachable and teleport
120 Shulker.makeBoundingBox port crates/horizon-client/src/mobs/shulker.rs::progress_aabb
121 Shulker.getDefaultDimensions inline crates/horizon-client/src/mobs/shulker.rs::progress_aabb gap: minor: the down-face height scale is not seen here
122 Shulker.startRiding missing unsure: no attach-face reset to DOWN when a shulker mounts a vehicle in shulker.rs no attach-face reset on boarding: `mobs/shulker.rs` sets `extra.variant3` from `Attachable` and the save only, so a shulker on a vehicle keeps the face it had
123 Shulker.stopRiding missing inline crates/horizon-client/src/mobs/shulker.rs::tick unsure: no body yaw reset after a shulker dismounts in shulker.rs the body yaw is pinned to 0 on every tick (`shulker::tick`, and `client_tick` for a remote one), so a dismounted shulker shows no leftover body yaw without a reset of its own
124 Shulker.move missing unsure: no shulker-box move type (teleport) found in the piston code the `SHULKER_BOX` mover type has no counterpart: `shulker::teleport` runs from the tick and the bullet hit only, so a shulker box pushed by a piston never takes its shulker along
125 Shulker.findAttachableSurface port crates/horizon-client/src/mobs/shulker.rs::attachable
126 Shulker.isPositionBlocked inline crates/horizon-client/src/mobs/shulker.rs::can_stay_at the blocked-position check is part of can_stay_at
127 Shulker.onSyncedDataUpdated skip:other network sync hook; the box is computed from the face on demand
128 Shulker.getRenderPosition missing unsure: no render slide after a shulker teleport in shulker.rs no render slide: `client_tick` eases the lid and squares the body, and nothing interpolates the position across a teleport
129 Shulker.setVariant inline crates/horizon-client/src/mobs/shulker.rs::register colour comes from spawn data; NO_COLOR when unset
130 Shulker.getColor inline crates/horizon-client/src/mobs/shulker.rs::texture colour is read from extra.color for the texture
131 Shulker.get skip:other component getter; Horizon reads extra.color directly
132 Shulker.applyImplicitComponent missing minor: a shulker spawned with a SHULKER_COLOR component ignores it a shulker spawned from a shulker box item ignores its colour: the implicit components are applied to a mob coming out of a bucket only (`entity_nbt::bucket_components`)
133 Shulker.ShulkerDefenseAttackGoal.getTargetSearchArea missing minor: shulkers do not defend nearby shulkers; there is no defense target goal no defense target goal: `mobs/shulker.rs::register` gives the shulker `HurtByTarget` with `alert` (a hurt shulker wakes the targetless ones around it) and a player search box 4 deep along the attach axis, nothing that defends another shulker
134 Shulker.ShulkerNearestAttackGoal.getTargetSearchArea inline crates/horizon-client/src/mobs/shulker.rs::register the 4-deep search box is the when filter in register
135 Shulker.ShulkerPeekGoal.canContinueToUse port crates/horizon-client/src/mobs/shulker.rs::Peek::can_continue
136 Silverfish.registerGoals port crates/horizon-client/src/mobs/hostile.rs::register
+14 -14
View File
@@ -11,18 +11,18 @@ Strider.getDismountLocationForPassenger port crates/horizon-client/src/game/moun
Strider.tickRidden port crates/horizon-client/src/mobs/equine.rs::ridden_step strider branch: rider yaw, half pitch, boost factor, cold slowdown
Strider.playStepSound port crates/horizon-client/src/mobs/ai.rs::step_sound lava or normal step sound
Strider.isBeingTempted inline crates/horizon-client/src/mobs/ai.rs::play_ambient the Tempt goal running check is folded into the ambient sound guard
Strider.canAddPassenger missing minor: a player can still board a saddled strider whose head is in lava; vanilla refuses (isEyeInFluid LAVA)
Strider.canAddPassenger missing no boarding rule of its own: `mobs/riders.rs::start_riding` tests the mount and the cooldown only, so a player can still board a saddled strider whose head is in lava where vanilla refuses (`isEyeInFluid` LAVA)
Strider.getLeashOffset port crates/horizon-client/src/mobs/leash.rs::leash_offset gap: minor: the strider 0.6 x eye height override is not applied; the default eye-height offset is used
Strider.StriderPathNavigation.createPathFinder port crates/horizon-client/src/mobs/nav.rs::Navigation::evaluator ground nav kind gets the walk evaluator, as WalkNodeEvaluator
Strider.StriderPathNavigation.isStableDestination port crates/horizon-client/src/mobs/nav.rs::Mob::stable_destination lava counts as stable for lava walkers
Vex.registerGoals port crates/horizon-client/src/mobs/vex.rs::register float, charge, random move, look goals, owner and player targets
Vex.restoreFrom missing minor: a vex restored after a dimension trip keeps no owner link; Horizon has no mob restoreFrom
Vex.restoreFrom missing no mob `restoreFrom`: a vex that changes dimension keeps no owner link
Vex.getVexFlag inline crates/horizon-client/src/mobs/vex.rs::Charge::can_continue the charging bit is the plain extra.flag; there is no flags byte
Vex.setVexFlag inline crates/horizon-client/src/mobs/vex.rs::Charge::start start sets extra.flag; stop and tick clear it
Vex.finalizeSpawn port crates/horizon-client/src/mobs/finalize.rs::Ctx::finalize Vex arm: populate (sword) then enchant
Vex.populateDefaultEquipmentSlots port crates/horizon-client/src/mobs/finalize.rs::Ctx::populate iron sword in hand with drop chance 0
Witch.registerGoals port crates/horizon-client/src/mobs/witch.rs::register gap: minor: raiders that hurt a witch are still targeted back (vanilla ignores them)
Witch.handleEntityEvent missing player: a witch shows purple magic puffs about once a minute while idle; not drawn here (the RNG draw is kept in ai_step)
Witch.handleEntityEvent port crates/horizon-client/src/game/remote_mob_particles.rs::Game::witch_magic_particles event 15: `nextInt(35) + 10` purple puffs half a block over the head, the count drawn again every pass as the Java loop condition does; `game/remote.rs:3949` hands it every witch entity event (the rest of the handler is LivingEntity's)
Witch.getDamageAfterMagicAbsorb port crates/horizon-client/src/mobs.rs::Mob::damage_with 85% less from #witch_resistant_to, nothing from its own hit
Zoglin.initCoreActivity inline crates/horizon-client/src/mobs/piglin.rs::register_zoglin gap: minor: look-at-target and move-to-target sinks are not separate; the goal set stands in for them
Zoglin.initIdleActivity inline crates/horizon-client/src/mobs/piglin.rs::register_zoglin gap: minor: idle is a stroll only; no look-target sometimes and no do-nothing pauses
@@ -33,7 +33,7 @@ Zoglin.createAttributes skip:data attribute defaults come from the data tables
Zoglin.doHurtTarget port crates/horizon-client/src/mobs/piglin.rs::hoglin_hurt_target headbutt event, zoglin attack sound, hurt and throw
Zoglin.hurtServer inline crates/horizon-client/src/mobs/piglin.rs::register_zoglin gap: minor: HurtByTarget sets the attacker as target with no 4-block check against a much nearer current target
Zoglin.setAttackTarget inline crates/horizon-client/src/mobs/piglin.rs::register_zoglin gap: minor: target has no 200-tick expiry; Horizon sets the target directly
Zoglin.updateActivity missing player: a zoglin that starts a fight does not play its angry grunt (zoglin.angry)
Zoglin.updateActivity port crates/horizon-client/src/mobs/piglin.rs::zoglin_step on entering the fight activity it plays `entity.zoglin.angry`; `mobs/ai.rs:1718` runs it for every zoglin after the brain
Zoglin.customServerAiStep inline crates/horizon-client/src/mobs/piglin.rs::ai_step gap: minor: the brain tick is not run; the zoglin goal set runs in the general AI loop
Zoglin.setBaby port crates/horizon-client/src/mobs/finalize.rs::set_baby Zoglin arm: non-ageable baby age and 0.5 attack damage
Zoglin.handleEntityEvent port crates/horizon-client/src/mobs/piglin.rs::client_event event 4 starts the headbutt; the attack sound comes from hoglin_hurt_target
@@ -42,8 +42,8 @@ Breeze.createAttributes skip:data attribute defaults come from the data tables
Breeze.makeBrain port crates/horizon-client/src/mobs/monster_brain.rs::init breezes get the fight default activity
Breeze.onSyncedDataUpdated port crates/horizon-client/src/mobs/breeze.rs::ai_step client pose change restarts the matching animation
Breeze.resetAnimations inline crates/horizon-client/src/mobs/breeze.rs::ai_step the stop calls of the pose-change branch
Breeze.emitJumpTrailParticles missing player: no block-dust trail behind a long jump (about 5 ticks of particles)
Breeze.emitGroundParticles missing player: no block-dust puffs at its feet while it stands, shoots, inhales or lands; Horizon only draws the random number
Breeze.emitJumpTrailParticles missing `mobs/breeze.rs` has no particle call (only `dangerous_block`), so no block dust follows a long jump
Breeze.emitGroundParticles missing same as `emitJumpTrailParticles`: no ground puffs at its feet while it stands, shoots, inhales or lands
Breeze.playWhirlSound port crates/horizon-client/src/mobs/breeze.rs::tick whirl sound on a random clock, as a local sound
Breeze.deflection port crates/horizon-client/src/game/creatures.rs::Game::tick_projectiles breeze turns projectiles back, except wind charges
Breeze.getHurtBy port crates/horizon-client/src/mobs/monster_brain.rs::hurt_by_entity the living source of the last hurt
@@ -68,19 +68,19 @@ Creaking.defineSynchedData skip:java SynchedEntityData builder; the synced valu
Creaking.createAttributes skip:data attribute defaults come from the data tables (mobs.rs attr lookup)
Creaking.setupAnimationStates port crates/horizon-client/src/mobs/creaking.rs::animate attack, invulnerability and tear-down animations
Creaking.creakingDeathEffects port crates/horizon-client/src/mobs/creaking_heart.rs::remove_protector gap: minor: no blameSourceForDamage or die(); health is set to 0 and the twitch sound plays
Creaking.canUsePortal missing minor: a heart-bound creaking can still go through a portal; game/nether_portal.rs mob_can_use_portal has no heart check
Creaking.canUsePortal missing `game/nether_portal.rs::mob_can_use_portal` tests alive, no mount, not ridden, the guest-ride rule and the wither; nothing about the heart, so a heart-bound creaking can still go through a portal
Creaking.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt home_pos is read back as the transient home
Creaking.checkEyeBlink inline crates/horizon-client/src/mobs/creaking.rs::animate eye flicker timing on the dying creaking
Creaking.knockback port crates/horizon-client/src/mobs.rs::Mob::knockback
Creaking.CreakingPathNavigation.createPathFinder missing minor: no home-distance node evaluator; paths do not block nodes more than 32 blocks from the heart
Creaking.HomeNodeEvaluator.getPathType missing minor: nodes more than 32 blocks from home are not marked blocked
Creaking.CreakingPathNavigation.createPathFinder missing no home-distance node evaluator: `m.extra.home` is read for damage, tearing down and the save, never by the pathfinder
Creaking.HomeNodeEvaluator.getPathType missing no node is marked blocked for being more than 32 blocks from the heart
CreakingAi.initCoreActivity port crates/horizon-client/src/mobs/monster_brain.rs::creaking_packages core swim, look-at-target and move sink
CreakingAi.initIdleActivity port crates/horizon-client/src/mobs/monster_brain.rs::creaking_packages idle: attack active creakings, look sometimes, stroll, look-walk or do nothing
CreakingAi.initFightActivity port crates/horizon-client/src/mobs/monster_brain.rs::creaking_packages fight: walk to target, melee on a 40 tick cooldown, stop when unreachable
AbstractCubeMob.registerGoals port crates/horizon-client/src/mobs/cube.rs::register float, random direction, keep jumping, attack goal and player and golem targets
AbstractCubeMob.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_to_nbt Size stored minus one, and wasOnGround
AbstractCubeMob.refreshDimensions skip:other bounds are read from the size on each use, so there is no separate refresh; the position is never reset
AbstractCubeMob.onSyncedDataUpdated missing unsure: remote.rs applies no size sync to cubes; local play sets the size directly
AbstractCubeMob.onSyncedDataUpdated missing no size sync from a spawn or a data update: `mobs/living.rs` takes a mob's dimensions from its own size, and `game/remote.rs` applies no cube size
AbstractCubeMob.setUpSplitCube port crates/horizon-client/src/game/creatures.rs::Game::mob_died gap: minor: children get half size and the same fixed offsets, but no random yaw
AbstractCubeMob.push inline crates/horizon-client/src/mobs/cube.rs::hurt_touching_golems an iron golem whose box overlaps a cube that can deal damage is hurt through dealDamage (MobEvent::CubeHurtMob, with the attack sound after a landed hit)
AbstractCubeMob.dealDamage inline crates/horizon-client/src/mobs/cube.rs::ai_step the player touch (CubeHurtPlayer) and a golem's overlap (CubeHurtMob, hurt_touching_golems): alive, teams allow damage, in melee range, line of sight, then mobAttack for getAttackDamage
@@ -136,12 +136,12 @@ HoglinAi.initIdleActivity inline crates/horizon-client/src/mobs/piglin.rs::regis
HoglinAi.initFightActivity inline crates/horizon-client/src/mobs/piglin.rs::register_hoglin MeleeAttack.create(40) for adults and create(15) for babies (`MeleeAttack::intervals(40, 15)`); the brain's own order has no tick parity here (the probe has no hoglin brain)
HoglinAi.initRetreatActivity port crates/horizon-client/src/mobs/piglin.rs::register_hoglin
HoglinAi.createIdleMovementBehaviors inline crates/horizon-client/src/mobs/piglin.rs::register_hoglin gap: minor: no look-target walk or do-nothing pauses; a plain stroll only
HoglinAi.updateActivity missing player: no grunt when a hoglin switches activity (angry or retreat); only the ambient roll plays
HoglinAi.updateActivity port crates/horizon-client/src/mobs/piglin.rs::update_activity takes the first valid activity and voices the one it changed to (`hoglin_sound`: angry in a fight, retreat when avoiding or converting, else ambient); the same fn covers PiglinAi and PiglinBruteAi
HoglinAi.onHitTarget inline crates/horizon-client/src/mobs/piglin.rs::hoglin_hurt_target gap: player: no retreat when piglins outnumber hoglins; the hit only attacks
HoglinAi.retreatFromNearestTarget missing player: no retreat from the nearest piglin or hoglin target (part of the missing retreat activity)
HoglinAi.setAvoidTarget missing player: no avoid target is set, so a hoglin never backs off after a piglin hit (retreat activity missing)
HoglinAi.retreatFromNearestTarget port crates/horizon-client/src/mobs/piglin.rs::retreat_from_nearest the nearest of the new target, the avoid target and the attack target retreats `who` (`BehaviorUtils.getNearestTarget`: strictly closer wins), then a hoglin sets an avoid target and a piglin also stops hunting
HoglinAi.setAvoidTarget port crates/horizon-client/src/mobs/piglin.rs::set_avoid_target forgets the attack target and the walk and avoids `who` for a 5 to 20 second retreat, sampled from the level random
HoglinAi.isPosNearNearestRepellent inline crates/horizon-client/src/mobs/piglin.rs::HoglinAvoidRepellent::can_use the 8-block repellent distance check
HoglinAi.piglinsOutnumberHoglins missing player: the count of piglins against hoglins that starts a retreat is not kept
HoglinAi.piglinsOutnumberHoglins port crates/horizon-client/src/mobs/piglin.rs::piglins_outnumber_hoglins an adult hoglin counts itself and sees more adult piglins than hoglins (`piglins > hoglins + 1`)
HoglinAi.maybeRetaliate port crates/horizon-client/src/mobs/piglin.rs::register_hoglin gap: minor: no check for hoglin attackers or the 4-block nearer-target rule; HurtByTarget alerts others
HoglinAi.setAttackTarget inline crates/horizon-client/src/mobs/piglin.rs::register_hoglin gap: minor: target has no 200-tick expiry and the breed target is not cleared
HoglinAi.setAttackTargetIfCloserThanCurrent inline crates/horizon-client/src/mobs/piglin.rs::register_hoglin NearestTarget keeps the closest target and skips pacified hoglins
1 Strider.checkStriderSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules strider arm: climbs lava upward, then needs air above
11 Strider.tickRidden port crates/horizon-client/src/mobs/equine.rs::ridden_step strider branch: rider yaw, half pitch, boost factor, cold slowdown
12 Strider.playStepSound port crates/horizon-client/src/mobs/ai.rs::step_sound lava or normal step sound
13 Strider.isBeingTempted inline crates/horizon-client/src/mobs/ai.rs::play_ambient the Tempt goal running check is folded into the ambient sound guard
14 Strider.canAddPassenger missing minor: a player can still board a saddled strider whose head is in lava; vanilla refuses (isEyeInFluid LAVA) no boarding rule of its own: `mobs/riders.rs::start_riding` tests the mount and the cooldown only, so a player can still board a saddled strider whose head is in lava where vanilla refuses (`isEyeInFluid` LAVA)
15 Strider.getLeashOffset port crates/horizon-client/src/mobs/leash.rs::leash_offset gap: minor: the strider 0.6 x eye height override is not applied; the default eye-height offset is used
16 Strider.StriderPathNavigation.createPathFinder port crates/horizon-client/src/mobs/nav.rs::Navigation::evaluator ground nav kind gets the walk evaluator, as WalkNodeEvaluator
17 Strider.StriderPathNavigation.isStableDestination port crates/horizon-client/src/mobs/nav.rs::Mob::stable_destination lava counts as stable for lava walkers
18 Vex.registerGoals port crates/horizon-client/src/mobs/vex.rs::register float, charge, random move, look goals, owner and player targets
19 Vex.restoreFrom missing minor: a vex restored after a dimension trip keeps no owner link; Horizon has no mob restoreFrom no mob `restoreFrom`: a vex that changes dimension keeps no owner link
20 Vex.getVexFlag inline crates/horizon-client/src/mobs/vex.rs::Charge::can_continue the charging bit is the plain extra.flag; there is no flags byte
21 Vex.setVexFlag inline crates/horizon-client/src/mobs/vex.rs::Charge::start start sets extra.flag; stop and tick clear it
22 Vex.finalizeSpawn port crates/horizon-client/src/mobs/finalize.rs::Ctx::finalize Vex arm: populate (sword) then enchant
23 Vex.populateDefaultEquipmentSlots port crates/horizon-client/src/mobs/finalize.rs::Ctx::populate iron sword in hand with drop chance 0
24 Witch.registerGoals port crates/horizon-client/src/mobs/witch.rs::register gap: minor: raiders that hurt a witch are still targeted back (vanilla ignores them)
25 Witch.handleEntityEvent missing port crates/horizon-client/src/game/remote_mob_particles.rs::Game::witch_magic_particles player: a witch shows purple magic puffs about once a minute while idle; not drawn here (the RNG draw is kept in ai_step) event 15: `nextInt(35) + 10` purple puffs half a block over the head, the count drawn again every pass as the Java loop condition does; `game/remote.rs:3949` hands it every witch entity event (the rest of the handler is LivingEntity's)
26 Witch.getDamageAfterMagicAbsorb port crates/horizon-client/src/mobs.rs::Mob::damage_with 85% less from #witch_resistant_to, nothing from its own hit
27 Zoglin.initCoreActivity inline crates/horizon-client/src/mobs/piglin.rs::register_zoglin gap: minor: look-at-target and move-to-target sinks are not separate; the goal set stands in for them
28 Zoglin.initIdleActivity inline crates/horizon-client/src/mobs/piglin.rs::register_zoglin gap: minor: idle is a stroll only; no look-target sometimes and no do-nothing pauses
33 Zoglin.doHurtTarget port crates/horizon-client/src/mobs/piglin.rs::hoglin_hurt_target headbutt event, zoglin attack sound, hurt and throw
34 Zoglin.hurtServer inline crates/horizon-client/src/mobs/piglin.rs::register_zoglin gap: minor: HurtByTarget sets the attacker as target with no 4-block check against a much nearer current target
35 Zoglin.setAttackTarget inline crates/horizon-client/src/mobs/piglin.rs::register_zoglin gap: minor: target has no 200-tick expiry; Horizon sets the target directly
36 Zoglin.updateActivity missing port crates/horizon-client/src/mobs/piglin.rs::zoglin_step player: a zoglin that starts a fight does not play its angry grunt (zoglin.angry) on entering the fight activity it plays `entity.zoglin.angry`; `mobs/ai.rs:1718` runs it for every zoglin after the brain
37 Zoglin.customServerAiStep inline crates/horizon-client/src/mobs/piglin.rs::ai_step gap: minor: the brain tick is not run; the zoglin goal set runs in the general AI loop
38 Zoglin.setBaby port crates/horizon-client/src/mobs/finalize.rs::set_baby Zoglin arm: non-ageable baby age and 0.5 attack damage
39 Zoglin.handleEntityEvent port crates/horizon-client/src/mobs/piglin.rs::client_event event 4 starts the headbutt; the attack sound comes from hoglin_hurt_target
42 Breeze.makeBrain port crates/horizon-client/src/mobs/monster_brain.rs::init breezes get the fight default activity
43 Breeze.onSyncedDataUpdated port crates/horizon-client/src/mobs/breeze.rs::ai_step client pose change restarts the matching animation
44 Breeze.resetAnimations inline crates/horizon-client/src/mobs/breeze.rs::ai_step the stop calls of the pose-change branch
45 Breeze.emitJumpTrailParticles missing player: no block-dust trail behind a long jump (about 5 ticks of particles) `mobs/breeze.rs` has no particle call (only `dangerous_block`), so no block dust follows a long jump
46 Breeze.emitGroundParticles missing player: no block-dust puffs at its feet while it stands, shoots, inhales or lands; Horizon only draws the random number same as `emitJumpTrailParticles`: no ground puffs at its feet while it stands, shoots, inhales or lands
47 Breeze.playWhirlSound port crates/horizon-client/src/mobs/breeze.rs::tick whirl sound on a random clock, as a local sound
48 Breeze.deflection port crates/horizon-client/src/game/creatures.rs::Game::tick_projectiles breeze turns projectiles back, except wind charges
49 Breeze.getHurtBy port crates/horizon-client/src/mobs/monster_brain.rs::hurt_by_entity the living source of the last hurt
68 Creaking.createAttributes skip:data attribute defaults come from the data tables (mobs.rs attr lookup)
69 Creaking.setupAnimationStates port crates/horizon-client/src/mobs/creaking.rs::animate attack, invulnerability and tear-down animations
70 Creaking.creakingDeathEffects port crates/horizon-client/src/mobs/creaking_heart.rs::remove_protector gap: minor: no blameSourceForDamage or die(); health is set to 0 and the twitch sound plays
71 Creaking.canUsePortal missing minor: a heart-bound creaking can still go through a portal; game/nether_portal.rs mob_can_use_portal has no heart check `game/nether_portal.rs::mob_can_use_portal` tests alive, no mount, not ridden, the guest-ride rule and the wither; nothing about the heart, so a heart-bound creaking can still go through a portal
72 Creaking.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt home_pos is read back as the transient home
73 Creaking.checkEyeBlink inline crates/horizon-client/src/mobs/creaking.rs::animate eye flicker timing on the dying creaking
74 Creaking.knockback port crates/horizon-client/src/mobs.rs::Mob::knockback
75 Creaking.CreakingPathNavigation.createPathFinder missing minor: no home-distance node evaluator; paths do not block nodes more than 32 blocks from the heart no home-distance node evaluator: `m.extra.home` is read for damage, tearing down and the save, never by the pathfinder
76 Creaking.HomeNodeEvaluator.getPathType missing minor: nodes more than 32 blocks from home are not marked blocked no node is marked blocked for being more than 32 blocks from the heart
77 CreakingAi.initCoreActivity port crates/horizon-client/src/mobs/monster_brain.rs::creaking_packages core swim, look-at-target and move sink
78 CreakingAi.initIdleActivity port crates/horizon-client/src/mobs/monster_brain.rs::creaking_packages idle: attack active creakings, look sometimes, stroll, look-walk or do nothing
79 CreakingAi.initFightActivity port crates/horizon-client/src/mobs/monster_brain.rs::creaking_packages fight: walk to target, melee on a 40 tick cooldown, stop when unreachable
80 AbstractCubeMob.registerGoals port crates/horizon-client/src/mobs/cube.rs::register float, random direction, keep jumping, attack goal and player and golem targets
81 AbstractCubeMob.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_to_nbt Size stored minus one, and wasOnGround
82 AbstractCubeMob.refreshDimensions skip:other bounds are read from the size on each use, so there is no separate refresh; the position is never reset
83 AbstractCubeMob.onSyncedDataUpdated missing unsure: remote.rs applies no size sync to cubes; local play sets the size directly no size sync from a spawn or a data update: `mobs/living.rs` takes a mob's dimensions from its own size, and `game/remote.rs` applies no cube size
84 AbstractCubeMob.setUpSplitCube port crates/horizon-client/src/game/creatures.rs::Game::mob_died gap: minor: children get half size and the same fixed offsets, but no random yaw
85 AbstractCubeMob.push inline crates/horizon-client/src/mobs/cube.rs::hurt_touching_golems an iron golem whose box overlaps a cube that can deal damage is hurt through dealDamage (MobEvent::CubeHurtMob, with the attack sound after a landed hit)
86 AbstractCubeMob.dealDamage inline crates/horizon-client/src/mobs/cube.rs::ai_step the player touch (CubeHurtPlayer) and a golem's overlap (CubeHurtMob, hurt_touching_golems): alive, teams allow damage, in melee range, line of sight, then mobAttack for getAttackDamage
136 HoglinAi.initFightActivity inline crates/horizon-client/src/mobs/piglin.rs::register_hoglin MeleeAttack.create(40) for adults and create(15) for babies (`MeleeAttack::intervals(40, 15)`); the brain's own order has no tick parity here (the probe has no hoglin brain)
137 HoglinAi.initRetreatActivity port crates/horizon-client/src/mobs/piglin.rs::register_hoglin
138 HoglinAi.createIdleMovementBehaviors inline crates/horizon-client/src/mobs/piglin.rs::register_hoglin gap: minor: no look-target walk or do-nothing pauses; a plain stroll only
139 HoglinAi.updateActivity missing port crates/horizon-client/src/mobs/piglin.rs::update_activity player: no grunt when a hoglin switches activity (angry or retreat); only the ambient roll plays takes the first valid activity and voices the one it changed to (`hoglin_sound`: angry in a fight, retreat when avoiding or converting, else ambient); the same fn covers PiglinAi and PiglinBruteAi
140 HoglinAi.onHitTarget inline crates/horizon-client/src/mobs/piglin.rs::hoglin_hurt_target gap: player: no retreat when piglins outnumber hoglins; the hit only attacks
141 HoglinAi.retreatFromNearestTarget missing port crates/horizon-client/src/mobs/piglin.rs::retreat_from_nearest player: no retreat from the nearest piglin or hoglin target (part of the missing retreat activity) the nearest of the new target, the avoid target and the attack target retreats `who` (`BehaviorUtils.getNearestTarget`: strictly closer wins), then a hoglin sets an avoid target and a piglin also stops hunting
142 HoglinAi.setAvoidTarget missing port crates/horizon-client/src/mobs/piglin.rs::set_avoid_target player: no avoid target is set, so a hoglin never backs off after a piglin hit (retreat activity missing) forgets the attack target and the walk and avoids `who` for a 5 to 20 second retreat, sampled from the level random
143 HoglinAi.isPosNearNearestRepellent inline crates/horizon-client/src/mobs/piglin.rs::HoglinAvoidRepellent::can_use the 8-block repellent distance check
144 HoglinAi.piglinsOutnumberHoglins missing port crates/horizon-client/src/mobs/piglin.rs::piglins_outnumber_hoglins player: the count of piglins against hoglins that starts a retreat is not kept an adult hoglin counts itself and sees more adult piglins than hoglins (`piglins > hoglins + 1`)
145 HoglinAi.maybeRetaliate port crates/horizon-client/src/mobs/piglin.rs::register_hoglin gap: minor: no check for hoglin attackers or the 4-block nearer-target rule; HurtByTarget alerts others
146 HoglinAi.setAttackTarget inline crates/horizon-client/src/mobs/piglin.rs::register_hoglin gap: minor: target has no 200-tick expiry and the breed target is not cleared
147 HoglinAi.setAttackTargetIfCloserThanCurrent inline crates/horizon-client/src/mobs/piglin.rs::register_hoglin NearestTarget keeps the closest target and skips pacified hoglins
+15 -15
View File
@@ -15,7 +15,7 @@ SpellcasterIllager.getCurrentSpell skip:other getter; Horizon reads extra.varia
SpellcasterIllager.SpellcasterUseSpellGoal.canContinueToUse port crates/horizon-client/src/mobs/illager.rs::UseSpell::can_continue target alive and warm-up left
Vindicator.registerGoals port crates/horizon-client/src/mobs/illager.rs::register_vindicator break door, open door, hold ground, melee, Johnny goal
Vindicator.createAttributes skip:data attribute defaults (speed 0.35, follow 12, health 24, attack 5) live in data tables
Vindicator.addAdditionalSaveData missing minor: the Johnny flag is not saved; Horizon's Johnny goal tests the custom name, so only a vindicator renamed after being named Johnny differs
Vindicator.addAdditionalSaveData missing no `Johnny` flag is written: `mobs/illager.rs:78` tests the custom name itself, so only a vindicator renamed after being named Johnny differs
Vindicator.finalizeSpawn port crates/horizon-client/src/mobs/finalize.rs::Ctx::finalize raider spawn, then equipment and enchantments; the door flag it sets each tick comes from vindicator_server_ai_step
Vindicator.populateDefaultEquipmentSlots port crates/horizon-client/src/mobs/finalize.rs::Ctx::populate iron axe unless the mob is in a raid
Vindicator.setCustomName inline crates/horizon-client/src/mobs/illager.rs::register_vindicator gap: minor: the Johnny goal reads the current custom name each tick; vanilla latches the flag once a name is Johnny
@@ -25,7 +25,7 @@ AbstractPiglin.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.
AbstractPiglin.customServerAiStep port crates/horizon-client/src/mobs/piglin.rs::ai_step gap: minor: zombification runs outside the Nether by the nether flag, not the PIGLINS_ZOMBIFY attribute
AbstractPiglin.finishConversion port crates/horizon-client/src/mobs/piglin.rs::ai_step spawns the zombified piglin with nausea and removes the old one
Piglin.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt gap: minor: CannotHunt is not read, so hunting ignores it; baby and inventory are read
Piglin.getSlot missing minor: item commands cannot reach a piglin's inventory slots (300 and up); 300 and up only address the open container
Piglin.getSlot missing item commands stop at the open container: a piglin's own inventory slots (300 and up) are not addressable
Piglin.defineSynchedData skip:java synched-data registration; Horizon keeps baby, crossbow and dancing state in Mob fields
Piglin.createAttributes skip:data attribute defaults (health 16, speed 0.35, attack 5) live in data tables
Piglin.checkPiglinSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules not spawning on a nether wart block
@@ -39,7 +39,7 @@ Piglin.hurtServer inline crates/horizon-client/src/mobs/piglin.rs::celebrate gap
Piglin.canUseNonMeleeWeapon port crates/horizon-client/src/game/piglins.rs::Game::piglin_wanted gap: minor: checks the preferred-weapons tag only, not the usable-crossbow tag or the kinetic weapon component
Piglin.holdInOffHand port crates/horizon-client/src/game/piglins.rs::Game::start_admiring gap: minor: does not drop an item already in the off hand and does not set persistence
Piglin.wantsToPickUp port crates/horizon-client/src/game/piglins.rs::Game::piglin_wants gap: minor: does not check the admiring-disabled wait after a hit by a player
Piglin.startRiding missing minor: baby piglins never mount a baby hoglin (see PiglinAi.babySometimesRideBabyHoglin)
Piglin.startRiding port crates/horizon-client/src/mobs/riders.rs::start_riding the boarding tests (not the vehicle it is on, not one it carries, the boarding cooldown, a free vehicle) plus the `Piglin` case: a baby piglin on a hoglin goes on top of the up-to-three piglins already there
Piglin.getTopPassenger port crates/horizon-client/src/mobs/riders.rs::top_passenger the top of the passenger chain, capped at four passengers on a hoglin
Piglin.getAmbientSound port crates/horizon-client/src/mobs/piglin.rs::piglin_ambient the sound of the activity: angry, retreat, admiring, celebrate, jealous, or the plain grunt
PiglinAi.initCoreActivity inline crates/horizon-client/src/mobs/piglin.rs::register_piglin gap: minor: no babyAvoidNemesis (babies do not flee a nemesis); avoidZombified and admiring stop are covered by goals
@@ -71,12 +71,12 @@ PiglinAi.getSoundForActivity port crates/horizon-client/src/mobs/piglin.rs::pigl
PiglinAi.isNearAvoidTarget inline crates/horizon-client/src/mobs/piglin.rs::piglin_sound the avoid target within 12 blocks picks the retreat grunt
PiglinAi.babySometimesRideBabyHoglin port crates/horizon-client/src/mobs/piglin.rs::baby_sometimes_ride_baby_hoglin the idle activity copies NEAREST_VISIBLE_BABY_HOGLIN into RIDE_TARGET for 10 to 30 seconds
PiglinAi.broadcastAngerTarget inline crates/horizon-client/src/mobs/goals.rs::HurtByTarget::start gap: minor: alerts same-kind mobs nearby; no closer-target check and no hoglin rule
PiglinAi.broadcastUniversalAnger missing minor: the universal_anger game rule is stored but piglins ignore it
PiglinAi.broadcastUniversalAnger missing the rule reaches the mob world (`creatures.rs:788` sets `universal_anger`) and the wolf's target goal reads it (`tame.rs:81`, `NeutralMob.isAngryAt`), but nothing in `mobs/piglin.rs`: the piglin anger broadcast is `broadcast_anger` (`broadcastAngerTarget`) only, so no piglin is angered for a rule that is on
PiglinAi.setAngerTarget port crates/horizon-client/src/game/piglins.rs::Game::anger_piglins gap: minor: only the player path (600-tick anger); a hoglin target does not set the hunt wait
PiglinAi.setAngerTargetToNearestTargetablePlayerIfFound missing minor: only for universal anger, which Horizon does not apply
PiglinAi.setAngerTargetToNearestTargetablePlayerIfFound missing part of `broadcastUniversalAnger`, which the piglin AI does not run; `tame.rs:81` is the one place the rule picks a player, for the wolf
PiglinAi.setAngerTargetIfCloserThanCurrent inline crates/horizon-client/src/mobs/goals.rs::HurtByTarget::start gap: minor: alerts nearby piglins but never keeps a closer target
PiglinAi.getAvoidTarget port crates/horizon-client/src/mobs/piglin.rs::avoid_target the avoid target while its memory is there
PiglinAi.getNearestVisibleTargetablePlayer missing minor: only used by universal anger, which Horizon does not apply
PiglinAi.getNearestVisibleTargetablePlayer missing only used by `broadcastUniversalAnger` and its anger target, neither of which the piglin AI runs
PiglinAi.broadcastRetreat port crates/horizon-client/src/mobs/piglin.rs::broadcast_retreat every adult piglin in range takes the retreat
PiglinAi.retreatFromNearestTarget port crates/horizon-client/src/mobs/piglin.rs::retreat_from_nearest RETREAT_DURATION of avoiding the nearest target
PiglinAi.wantsToStopFleeing port crates/horizon-client/src/mobs/piglin.rs::wants_to_stop_fleeing the avoid target is gone or nearby and no hoglin outnumbers the piglin
@@ -100,26 +100,26 @@ Bogged.spawnShearedMushrooms port crates/horizon-client/src/game/creatures.rs::G
WitherSkeleton.registerGoals port crates/horizon-client/src/mobs/undead.rs::register piglin and piglin brute target at priority 3, then the skeleton goals
WitherSkeleton.canHoldItem port crates/horizon-client/src/mobs/equip.rs::wants_to_pick_up the wither_skeleton_disliked_weapons tag is refused
WitherSkeleton.finalizeSpawn port crates/horizon-client/src/mobs/finalize.rs::Ctx::skeleton attack damage 4 for wither skeletons
WitherSkeleton.getArrow missing minor: arrows from a wither skeleton are not set on fire for 100 seconds
WitherSkeleton.getArrow missing an arrow shot by a wither skeleton is not set on fire for 100 seconds
Spider.registerGoals port crates/horizon-client/src/mobs/hostile.rs::register spider goals: leap at target and the spider melee that gives up in bright light
Spider.isClimbing skip:other getter; Horizon reads the mob's climbing field
Spider.setClimbing inline crates/horizon-client/src/mobs/ai.rs::ai_step climbing set from the horizontal collision each tick
Spider.getVehicleAttachmentPoint port crates/horizon-client/src/mobs/riders.rs::passenger_attachment gap: minor: always the spider's fixed seat; vanilla uses a lower seat for riders narrower than the spider
Spider.SpiderEffectsGroupData.setRandomEffect port crates/horizon-client/src/mobs/finalize.rs::Ctx::spider hard-difficulty effect: speed, strength, regeneration or invisibility
AngerManagement.codec missing minor: warden anger is not saved; the anger tag is written empty, so a reloaded warden forgets its suspects
AngerManagement.createUuidAngerPairs missing minor: part of the anger save, which Horizon does not write
AngerManagement.convertFromUuids missing minor: part of the anger load, which Horizon does not read
AngerManagement.codec missing `entity_nbt.rs::persistent_anger` lists wolf, iron golem, polar bear, enderman and zombified piglin; a warden has none of it, so its anger is never written and never read
AngerManagement.createUuidAngerPairs missing part of the warden's anger save, which `entity_nbt.rs` does not write (see AngerManagement.codec)
AngerManagement.convertFromUuids missing part of the warden's anger load, which `entity_nbt.rs` does not read
AngerManagement.getTopSuspect port crates/horizon-client/src/mobs/warden.rs::AngerManagement::active_entity first suspect the filter accepts
AngerManagement.Sorter.compare port crates/horizon-client/src/mobs/warden.rs::AngerManagement::sort_and_update_highest angry first, then players, then the angriest
Warden.<init> port crates/horizon-client/src/mobs/warden.rs::register pathfinding maluses and can-float; the anger and vibration state start empty
Warden.getAddEntityPacket missing unsure: the spawn packet's emerging flag (data 1) has no Horizon counterpart that I found
Warden.recreateFromPacket missing unsure: no client-side emerging pose from a spawn packet that I found
Warden.getAddEntityPacket missing the emerging bit is extra entity data on the spawn packet; Horizon's add-entity payload carries no warden data, so a warden spawned from a packet is never emerging
Warden.recreateFromPacket missing the client half of the same bit: nothing in `game/remote.rs` applies warden data from a spawn, and `mobs/living.rs` shortens a warden only when its own `is_emerging` memory says so
Warden.createAttributes skip:data attribute defaults (health 500, speed 0.3, damage 30) live in data tables
Warden.handleEntityEvent inline crates/horizon-client/src/mobs/warden.rs::animate gap: minor: could not confirm the attack animation (event 4) is started here; tendril and sonic boom are
Warden.onSyncedDataUpdated inline crates/horizon-client/src/mobs/warden.rs::set_pose starts the emerge, dig, roar or sniff animation on pose change
Warden.updateDynamicGameEventListener port crates/horizon-client/src/mobs/warden.rs::listen vibrations reach the warden through listen; there is no registration step
Warden.addAdditionalSaveData missing minor: warden anger is not saved (the anger tag is written empty)
Warden.readAdditionalSaveData missing minor: warden anger is not read back, so a reloaded warden is calm
Warden.addAdditionalSaveData missing a warden's anger is not saved: `persistent_anger` leaves the warden out, so `anger_end_time` and `angry_at` are never written for it
Warden.readAdditionalSaveData missing a warden's anger is not read back, so a reloaded warden is calm
Warden.playListeningSound inline crates/horizon-client/src/mobs/warden.rs::increase_anger plays the listening sound for the anger level, unless roaring
WardenAi.initCoreActivity inline crates/horizon-client/src/mobs/warden_brain.rs::activities swim, look at target, look sink, move sink
WardenAi.initDiggingActivity inline crates/horizon-client/src/mobs/warden_brain.rs::activities force unmount and digging
@@ -133,7 +133,7 @@ WardenSpawnTracker.copyData inline crates/horizon-client-render/src/sculk.rs::Wa
Drowned.checkDrownedSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules water below, spawner or deep enough, dark enough, frequency roll
Drowned.isDeepEnoughToSpawn inline crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules below sea level minus 5
Drowned.populateDefaultEquipmentSlots port crates/horizon-client/src/mobs/finalize.rs::Ctx::populate 10 percent chance of a trident (10 in 16) or fishing rod
Drowned.canReplaceCurrentItem missing minor: no mob item pickup in Horizon, so the nautilus shell guard never applies
Drowned.canReplaceCurrentItem missing no mob item pickup, so the nautilus shell guard never applies
Drowned.rangedAttackUncertainty port crates/horizon-client/src/mobs/undead.rs::drowned_throw gap: minor: fixed uncertainty 6 (normal difficulty), not 14 minus 4 times the difficulty
Drowned.rideTick port crates/horizon-client/src/game/passengers.rs::position_riders a rider takes its vehicle's body yaw every tick
Drowned.wantsToPickUp port crates/horizon-client/src/mobs/equip.rs::wants_to_pick_up spears are refused, then the zombie checks
1 Evoker.EvokerWololoSpellGoal.canContinueToUse port crates/horizon-client/src/mobs/illager.rs::UseSpell::can_continue wololo branch: sheep target still set and warm-up left
15 SpellcasterIllager.SpellcasterUseSpellGoal.canContinueToUse port crates/horizon-client/src/mobs/illager.rs::UseSpell::can_continue target alive and warm-up left
16 Vindicator.registerGoals port crates/horizon-client/src/mobs/illager.rs::register_vindicator break door, open door, hold ground, melee, Johnny goal
17 Vindicator.createAttributes skip:data attribute defaults (speed 0.35, follow 12, health 24, attack 5) live in data tables
18 Vindicator.addAdditionalSaveData missing minor: the Johnny flag is not saved; Horizon's Johnny goal tests the custom name, so only a vindicator renamed after being named Johnny differs no `Johnny` flag is written: `mobs/illager.rs:78` tests the custom name itself, so only a vindicator renamed after being named Johnny differs
19 Vindicator.finalizeSpawn port crates/horizon-client/src/mobs/finalize.rs::Ctx::finalize raider spawn, then equipment and enchantments; the door flag it sets each tick comes from vindicator_server_ai_step
20 Vindicator.populateDefaultEquipmentSlots port crates/horizon-client/src/mobs/finalize.rs::Ctx::populate iron axe unless the mob is in a raid
21 Vindicator.setCustomName inline crates/horizon-client/src/mobs/illager.rs::register_vindicator gap: minor: the Johnny goal reads the current custom name each tick; vanilla latches the flag once a name is Johnny
25 AbstractPiglin.customServerAiStep port crates/horizon-client/src/mobs/piglin.rs::ai_step gap: minor: zombification runs outside the Nether by the nether flag, not the PIGLINS_ZOMBIFY attribute
26 AbstractPiglin.finishConversion port crates/horizon-client/src/mobs/piglin.rs::ai_step spawns the zombified piglin with nausea and removes the old one
27 Piglin.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt gap: minor: CannotHunt is not read, so hunting ignores it; baby and inventory are read
28 Piglin.getSlot missing minor: item commands cannot reach a piglin's inventory slots (300 and up); 300 and up only address the open container item commands stop at the open container: a piglin's own inventory slots (300 and up) are not addressable
29 Piglin.defineSynchedData skip:java synched-data registration; Horizon keeps baby, crossbow and dancing state in Mob fields
30 Piglin.createAttributes skip:data attribute defaults (health 16, speed 0.35, attack 5) live in data tables
31 Piglin.checkPiglinSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules not spawning on a nether wart block
39 Piglin.canUseNonMeleeWeapon port crates/horizon-client/src/game/piglins.rs::Game::piglin_wanted gap: minor: checks the preferred-weapons tag only, not the usable-crossbow tag or the kinetic weapon component
40 Piglin.holdInOffHand port crates/horizon-client/src/game/piglins.rs::Game::start_admiring gap: minor: does not drop an item already in the off hand and does not set persistence
41 Piglin.wantsToPickUp port crates/horizon-client/src/game/piglins.rs::Game::piglin_wants gap: minor: does not check the admiring-disabled wait after a hit by a player
42 Piglin.startRiding missing port crates/horizon-client/src/mobs/riders.rs::start_riding minor: baby piglins never mount a baby hoglin (see PiglinAi.babySometimesRideBabyHoglin) the boarding tests (not the vehicle it is on, not one it carries, the boarding cooldown, a free vehicle) plus the `Piglin` case: a baby piglin on a hoglin goes on top of the up-to-three piglins already there
43 Piglin.getTopPassenger port crates/horizon-client/src/mobs/riders.rs::top_passenger the top of the passenger chain, capped at four passengers on a hoglin
44 Piglin.getAmbientSound port crates/horizon-client/src/mobs/piglin.rs::piglin_ambient the sound of the activity: angry, retreat, admiring, celebrate, jealous, or the plain grunt
45 PiglinAi.initCoreActivity inline crates/horizon-client/src/mobs/piglin.rs::register_piglin gap: minor: no babyAvoidNemesis (babies do not flee a nemesis); avoidZombified and admiring stop are covered by goals
71 PiglinAi.isNearAvoidTarget inline crates/horizon-client/src/mobs/piglin.rs::piglin_sound the avoid target within 12 blocks picks the retreat grunt
72 PiglinAi.babySometimesRideBabyHoglin port crates/horizon-client/src/mobs/piglin.rs::baby_sometimes_ride_baby_hoglin the idle activity copies NEAREST_VISIBLE_BABY_HOGLIN into RIDE_TARGET for 10 to 30 seconds
73 PiglinAi.broadcastAngerTarget inline crates/horizon-client/src/mobs/goals.rs::HurtByTarget::start gap: minor: alerts same-kind mobs nearby; no closer-target check and no hoglin rule
74 PiglinAi.broadcastUniversalAnger missing minor: the universal_anger game rule is stored but piglins ignore it the rule reaches the mob world (`creatures.rs:788` sets `universal_anger`) and the wolf's target goal reads it (`tame.rs:81`, `NeutralMob.isAngryAt`), but nothing in `mobs/piglin.rs`: the piglin anger broadcast is `broadcast_anger` (`broadcastAngerTarget`) only, so no piglin is angered for a rule that is on
75 PiglinAi.setAngerTarget port crates/horizon-client/src/game/piglins.rs::Game::anger_piglins gap: minor: only the player path (600-tick anger); a hoglin target does not set the hunt wait
76 PiglinAi.setAngerTargetToNearestTargetablePlayerIfFound missing minor: only for universal anger, which Horizon does not apply part of `broadcastUniversalAnger`, which the piglin AI does not run; `tame.rs:81` is the one place the rule picks a player, for the wolf
77 PiglinAi.setAngerTargetIfCloserThanCurrent inline crates/horizon-client/src/mobs/goals.rs::HurtByTarget::start gap: minor: alerts nearby piglins but never keeps a closer target
78 PiglinAi.getAvoidTarget port crates/horizon-client/src/mobs/piglin.rs::avoid_target the avoid target while its memory is there
79 PiglinAi.getNearestVisibleTargetablePlayer missing minor: only used by universal anger, which Horizon does not apply only used by `broadcastUniversalAnger` and its anger target, neither of which the piglin AI runs
80 PiglinAi.broadcastRetreat port crates/horizon-client/src/mobs/piglin.rs::broadcast_retreat every adult piglin in range takes the retreat
81 PiglinAi.retreatFromNearestTarget port crates/horizon-client/src/mobs/piglin.rs::retreat_from_nearest RETREAT_DURATION of avoiding the nearest target
82 PiglinAi.wantsToStopFleeing port crates/horizon-client/src/mobs/piglin.rs::wants_to_stop_fleeing the avoid target is gone or nearby and no hoglin outnumbers the piglin
100 WitherSkeleton.registerGoals port crates/horizon-client/src/mobs/undead.rs::register piglin and piglin brute target at priority 3, then the skeleton goals
101 WitherSkeleton.canHoldItem port crates/horizon-client/src/mobs/equip.rs::wants_to_pick_up the wither_skeleton_disliked_weapons tag is refused
102 WitherSkeleton.finalizeSpawn port crates/horizon-client/src/mobs/finalize.rs::Ctx::skeleton attack damage 4 for wither skeletons
103 WitherSkeleton.getArrow missing minor: arrows from a wither skeleton are not set on fire for 100 seconds an arrow shot by a wither skeleton is not set on fire for 100 seconds
104 Spider.registerGoals port crates/horizon-client/src/mobs/hostile.rs::register spider goals: leap at target and the spider melee that gives up in bright light
105 Spider.isClimbing skip:other getter; Horizon reads the mob's climbing field
106 Spider.setClimbing inline crates/horizon-client/src/mobs/ai.rs::ai_step climbing set from the horizontal collision each tick
107 Spider.getVehicleAttachmentPoint port crates/horizon-client/src/mobs/riders.rs::passenger_attachment gap: minor: always the spider's fixed seat; vanilla uses a lower seat for riders narrower than the spider
108 Spider.SpiderEffectsGroupData.setRandomEffect port crates/horizon-client/src/mobs/finalize.rs::Ctx::spider hard-difficulty effect: speed, strength, regeneration or invisibility
109 AngerManagement.codec missing minor: warden anger is not saved; the anger tag is written empty, so a reloaded warden forgets its suspects `entity_nbt.rs::persistent_anger` lists wolf, iron golem, polar bear, enderman and zombified piglin; a warden has none of it, so its anger is never written and never read
110 AngerManagement.createUuidAngerPairs missing minor: part of the anger save, which Horizon does not write part of the warden's anger save, which `entity_nbt.rs` does not write (see AngerManagement.codec)
111 AngerManagement.convertFromUuids missing minor: part of the anger load, which Horizon does not read part of the warden's anger load, which `entity_nbt.rs` does not read
112 AngerManagement.getTopSuspect port crates/horizon-client/src/mobs/warden.rs::AngerManagement::active_entity first suspect the filter accepts
113 AngerManagement.Sorter.compare port crates/horizon-client/src/mobs/warden.rs::AngerManagement::sort_and_update_highest angry first, then players, then the angriest
114 Warden.<init> port crates/horizon-client/src/mobs/warden.rs::register pathfinding maluses and can-float; the anger and vibration state start empty
115 Warden.getAddEntityPacket missing unsure: the spawn packet's emerging flag (data 1) has no Horizon counterpart that I found the emerging bit is extra entity data on the spawn packet; Horizon's add-entity payload carries no warden data, so a warden spawned from a packet is never emerging
116 Warden.recreateFromPacket missing unsure: no client-side emerging pose from a spawn packet that I found the client half of the same bit: nothing in `game/remote.rs` applies warden data from a spawn, and `mobs/living.rs` shortens a warden only when its own `is_emerging` memory says so
117 Warden.createAttributes skip:data attribute defaults (health 500, speed 0.3, damage 30) live in data tables
118 Warden.handleEntityEvent inline crates/horizon-client/src/mobs/warden.rs::animate gap: minor: could not confirm the attack animation (event 4) is started here; tendril and sonic boom are
119 Warden.onSyncedDataUpdated inline crates/horizon-client/src/mobs/warden.rs::set_pose starts the emerge, dig, roar or sniff animation on pose change
120 Warden.updateDynamicGameEventListener port crates/horizon-client/src/mobs/warden.rs::listen vibrations reach the warden through listen; there is no registration step
121 Warden.addAdditionalSaveData missing minor: warden anger is not saved (the anger tag is written empty) a warden's anger is not saved: `persistent_anger` leaves the warden out, so `anger_end_time` and `angry_at` are never written for it
122 Warden.readAdditionalSaveData missing minor: warden anger is not read back, so a reloaded warden is calm a warden's anger is not read back, so a reloaded warden is calm
123 Warden.playListeningSound inline crates/horizon-client/src/mobs/warden.rs::increase_anger plays the listening sound for the anger level, unless roaring
124 WardenAi.initCoreActivity inline crates/horizon-client/src/mobs/warden_brain.rs::activities swim, look at target, look sink, move sink
125 WardenAi.initDiggingActivity inline crates/horizon-client/src/mobs/warden_brain.rs::activities force unmount and digging
133 Drowned.checkDrownedSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules water below, spawner or deep enough, dark enough, frequency roll
134 Drowned.isDeepEnoughToSpawn inline crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules below sea level minus 5
135 Drowned.populateDefaultEquipmentSlots port crates/horizon-client/src/mobs/finalize.rs::Ctx::populate 10 percent chance of a trident (10 in 16) or fishing rod
136 Drowned.canReplaceCurrentItem missing minor: no mob item pickup in Horizon, so the nautilus shell guard never applies no mob item pickup, so the nautilus shell guard never applies
137 Drowned.rangedAttackUncertainty port crates/horizon-client/src/mobs/undead.rs::drowned_throw gap: minor: fixed uncertainty 6 (normal difficulty), not 14 minus 4 times the difficulty
138 Drowned.rideTick port crates/horizon-client/src/game/passengers.rs::position_riders a rider takes its vehicle's body yaw every tick
139 Drowned.wantsToPickUp port crates/horizon-client/src/mobs/equip.rs::wants_to_pick_up spears are refused, then the zombie checks
+11 -11
View File
@@ -6,7 +6,7 @@ ZombieVillager.initializeZombieVillagerData port crates/horizon-client/src/mobs/
ZombieVillager.handleEntityEvent port crates/horizon-client/src/mobs/zombie_villager.rs::start_converting cure sound (event 16) is queued when the cure starts
ZombieVillager.setVillagerData inline crates/horizon-client/src/game/zombie_villagers.rs::carry_over gap: minor: the cure path copies type and profession there; no setter for the implicit component path
ZombieVillager.get skip:other DataComponents getter for the villager variant; Horizon has no entity component map
ZombieVillager.applyImplicitComponent missing minor: a zombie villager spawned from an item carrying a villager variant component keeps a random type; Horizon applies implicit components only to buckets (load_bucket_data)
ZombieVillager.applyImplicitComponent missing an implicit component is applied only to a mob coming out of a bucket (`entity_nbt::bucket_components`), so a zombie villager spawned from an item carrying a villager variant component keeps a random type
ZombifiedPiglin.createAttributes skip:data attribute defaults are entity-type data read in Mob::new (mobs.rs)
ZombifiedPiglin.maybePlayFirstAngerSound port crates/horizon-client/src/mobs/undead.rs::zombified_piglin_step first-anger counter and sound run in the step
ZombifiedPiglin.maybeAlertOthers port crates/horizon-client/src/mobs/undead.rs::zombified_piglin_step alert timer and line-of-sight check run in the step
@@ -39,8 +39,8 @@ Villager.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::re
Villager.getAmbientSound port crates/horizon-client/src/mobs/ai.rs::play_ambient gap: minor: the trade ambient sound is not played while trading
Villager.rewardTradeXp port crates/horizon-client/src/merchant.rs::Session::notify_trade gap: minor: no 10 second Regeneration on the villager at level-up
Villager.die port crates/horizon-client/src/game/villagers.rs::Game::villager_murdered gap: minor: releaseAllPois is not done on death
Villager.releaseAllPois missing unsure: releasePoi frees the bed, job site, potential job site and meeting point claims; Horizon has no claim store, only the block occupied property, so the effect is unclear
Villager.releasePoi missing unsure: same as releaseAllPois; the claim store is not clear in Horizon
Villager.releaseAllPois missing claims are per-mob fields (`extra.bed`, `extra.home` for the job site, `extra.meeting`) with no world-side point-of-interest store to release from; freeing a claim for another villager while this one lives has no counterpart
Villager.releasePoi missing same as `releaseAllPois`: a single claim is that mob's field, dropped with the mob
Villager.eatUntilFull port crates/horizon-client/src/mobs/villager.rs::Ext::eat_and_digest
Villager.shouldIncreaseLevel inline crates/horizon-client/src/merchant.rs::Session::notify_trade level-up check: can level up and xp at the maximum
Villager.increaseMerchantCareer inline crates/horizon-client/src/merchant.rs::Session::notify_trade level plus the new level trade set
@@ -53,7 +53,7 @@ Villager.updateTrades port crates/horizon-client/src/game/trading.rs::Game::ensu
Villager.maybeDecayGossip port crates/horizon-client/src/mobs/villager.rs::Ext::maybe_decay
Villager.golemSpawnConditionsMet port crates/horizon-client/src/mobs/villager.rs::Ext::wants_golem sleep memory check; the golem cooldown is an extra check here
Villager.get skip:other DataComponents getter for the villager variant; Horizon has no entity component map
Villager.applyImplicitComponent missing minor: a villager spawned from an item carrying a villager variant component keeps its biome type; Horizon applies implicit components only to buckets
Villager.applyImplicitComponent missing the same bucket-only path (`entity_nbt::bucket_components`), so a villager spawned from an item keeps the type its biome gives it
VillagerData.withType skip:java immutable record copy; Horizon keeps type and profession as Mob fields
VillagerData.withProfession skip:java immutable record copy; the profession change is villager::set_profession
VillagerProfession.bootstrap skip:data registry bootstrap of professions; Horizon reads them from data tables
@@ -87,9 +87,9 @@ Inventory.removeFromSelected port crates/horizon-world/src/player_inventory.rs::
Player.defineSynchedData skip:java synched data definitions; Horizon keeps the fields in game and player state
Player.turtleHelmetTick port crates/horizon-client/src/game/potions.rs::Game::turtle_helmet_tick
Player.isEquipped inline crates/horizon-client/src/game/potions.rs::Game::turtle_helmet_tick the head-slot check for the turtle helmet
Player.canPlayerFitWithinBlocksAndEntitiesWhen missing unsure: the pose fit check (no collision in the new pose box) was not found in Horizon
Player.canPlayerFitWithinBlocksAndEntitiesWhen port crates/horizon-client/src/pose.rs::MotionWorld::can_fit_pose the box of the pose's dimensions at the player's position, deflated by 1e-7, tested against the player's collision; `update_player_pose` asks it for the swimming, the desired and the crouching pose in that order, as `updatePlayerPose` does
Player.increaseScore skip:other the score is experience.total in Horizon (death screen); there is no separate score counter
Player.getWeaponItem missing unsure: the riptide spin-attack item (autoSpinAttackItemStack) was not found; Horizon keeps spin_attack parts only
Player.getWeaponItem missing the riptide spin attack keeps only `auto_spin_attack_ticks` and `auto_spin_attack_dmg`, not the stack it started with (`autoSpinAttackItemStack`), so there is no item to answer with mid-spin
Player.destroyVanishingCursedItems port crates/horizon-client/src/game.rs::Game::drop_inventory vanishing-curse stacks are removed from the death drops
Player.hasCorrectToolForDrops port crates/horizon-client/src/game.rs::Game::tick_mining the correct-tool test for harvest drops
Player.readAdditionalSaveData port crates/horizon-client/src/save.rs::Level::read_player
@@ -100,12 +100,12 @@ Player.canFallAtLeast inline crates/horizon-world/src/edge_backoff.rs::back_off_
Player.deflectProjectile port crates/horizon-client/src/game/deflect.rs::Game::attack_redirectable
Player.baseDamageScaleFactor inline crates/horizon-client/src/game/mace.rs::Game::sweep_damage
Player.isEffectiveAi skip:other client-side AI gate; Horizon has no effective-AI check in its loop
Player.hasContainerOpen missing unsure: the test for an open container menu (not the inventory menu) was not located
Player.hasContainerOpen missing no test for an open container menu: screens are `self.screen`, and nothing distinguishes a container menu from the inventory menu
Player.startSleepInBed port crates/horizon-client/src/game.rs::Game::try_sleep
Player.isSleepingLongEnough port crates/horizon-client/src/game/lan_actions.rs::sleep_counts 100 ticks in bed is what counts a sleeper as deep for SleepStatus.update
Player.updateSwimming port crates/horizon-client/src/motion.rs::MotionWorld::swimming
Player.getXpNeededForNextLevel port crates/horizon-client/src/entities.rs::Experience::needed
Player.hasEnoughFoodToDoExhaustiveManoeuvres missing unsure: the food-or-flying test for exhaustive moves was not found in motion.rs
Player.hasEnoughFoodToDoExhaustiveManoeuvres missing no food-or-flying gate anywhere in `motion.rs`: the exhaustion of a jump or a move is added without asking the food level
Player.mayUseItemAt port crates/horizon-client/src/game.rs::Game::use_hand the may-build test before use-on in adventure mode
Player.isSwimming port crates/horizon-client/src/motion.rs::MotionWorld::swimming
Player.decorateDisplayNameComponent port crates/horizon-client/src/game/commands.rs::player_display_name click suggests /tell, hover shows the player entity, plus the insertion
@@ -133,10 +133,10 @@ StackedContents.RecipePicker.getVisitedIngredientIndex skip:java bitset index a
StackedContents.RecipePicker.getVisitiedItemIndex skip:java bitset index arithmetic; plain bool vectors in Horizon
StackedContents.RecipePicker.clearAllVisited port crates/horizon-client/src/menu/place.rs::Picker::clear_visited
StackedItemContents.getBiggestCraftableStack port crates/horizon-client/src/menu/place.rs::Stacked::try_pick_all the largest craftable count, found by a bounded search
EvokerFangs.readAdditionalSaveData missing minor: fangs are not saved; a save in mid-bite drops them
EvokerFangs.addAdditionalSaveData missing minor: fangs are not saved; the warmup and owner are not written
EvokerFangs.readAdditionalSaveData missing fangs are not saved: `entity_nbt.rs` has no evoker fangs writer, so a save in mid-bite drops them
EvokerFangs.addAdditionalSaveData missing no writer for the warmup delay or the owner either
EvokerFangs.getAnimationProgress port crates/horizon-client/src/game/fangs.rs::Game::fangs_geometry the bite progress that positions the fang model
EyeOfEnder.setItem skip:ui the item sprite a flying eye shows; Horizon draws the eye itself
EyeOfEnder.shouldRenderAtSqrDistance skip:ui render distance culling only
EyeOfEnder.spawnParticles port crates/horizon-client/src/game/end.rs::Game::eye_break_particles the portal crumbs drawn in toward the eye
EyeOfEnder.readAdditionalSaveData missing minor: flying eyes are not saved; a save mid-flight loses them
EyeOfEnder.readAdditionalSaveData missing a flying eye of ender is not saved: `entity_nbt.rs` has no writer for it (nor `saved_with_chunk` a kind for it), so a save mid-flight loses it
1 ZombieVillager.defineSynchedData skip:java SynchedEntityData definitions; Horizon keeps the fields on Mob and sets them at spawn
6 ZombieVillager.handleEntityEvent port crates/horizon-client/src/mobs/zombie_villager.rs::start_converting cure sound (event 16) is queued when the cure starts
7 ZombieVillager.setVillagerData inline crates/horizon-client/src/game/zombie_villagers.rs::carry_over gap: minor: the cure path copies type and profession there; no setter for the implicit component path
8 ZombieVillager.get skip:other DataComponents getter for the villager variant; Horizon has no entity component map
9 ZombieVillager.applyImplicitComponent missing minor: a zombie villager spawned from an item carrying a villager variant component keeps a random type; Horizon applies implicit components only to buckets (load_bucket_data) an implicit component is applied only to a mob coming out of a bucket (`entity_nbt::bucket_components`), so a zombie villager spawned from an item carrying a villager variant component keeps a random type
10 ZombifiedPiglin.createAttributes skip:data attribute defaults are entity-type data read in Mob::new (mobs.rs)
11 ZombifiedPiglin.maybePlayFirstAngerSound port crates/horizon-client/src/mobs/undead.rs::zombified_piglin_step first-anger counter and sound run in the step
12 ZombifiedPiglin.maybeAlertOthers port crates/horizon-client/src/mobs/undead.rs::zombified_piglin_step alert timer and line-of-sight check run in the step
39 Villager.getAmbientSound port crates/horizon-client/src/mobs/ai.rs::play_ambient gap: minor: the trade ambient sound is not played while trading
40 Villager.rewardTradeXp port crates/horizon-client/src/merchant.rs::Session::notify_trade gap: minor: no 10 second Regeneration on the villager at level-up
41 Villager.die port crates/horizon-client/src/game/villagers.rs::Game::villager_murdered gap: minor: releaseAllPois is not done on death
42 Villager.releaseAllPois missing unsure: releasePoi frees the bed, job site, potential job site and meeting point claims; Horizon has no claim store, only the block occupied property, so the effect is unclear claims are per-mob fields (`extra.bed`, `extra.home` for the job site, `extra.meeting`) with no world-side point-of-interest store to release from; freeing a claim for another villager while this one lives has no counterpart
43 Villager.releasePoi missing unsure: same as releaseAllPois; the claim store is not clear in Horizon same as `releaseAllPois`: a single claim is that mob's field, dropped with the mob
44 Villager.eatUntilFull port crates/horizon-client/src/mobs/villager.rs::Ext::eat_and_digest
45 Villager.shouldIncreaseLevel inline crates/horizon-client/src/merchant.rs::Session::notify_trade level-up check: can level up and xp at the maximum
46 Villager.increaseMerchantCareer inline crates/horizon-client/src/merchant.rs::Session::notify_trade level plus the new level trade set
53 Villager.maybeDecayGossip port crates/horizon-client/src/mobs/villager.rs::Ext::maybe_decay
54 Villager.golemSpawnConditionsMet port crates/horizon-client/src/mobs/villager.rs::Ext::wants_golem sleep memory check; the golem cooldown is an extra check here
55 Villager.get skip:other DataComponents getter for the villager variant; Horizon has no entity component map
56 Villager.applyImplicitComponent missing minor: a villager spawned from an item carrying a villager variant component keeps its biome type; Horizon applies implicit components only to buckets the same bucket-only path (`entity_nbt::bucket_components`), so a villager spawned from an item keeps the type its biome gives it
57 VillagerData.withType skip:java immutable record copy; Horizon keeps type and profession as Mob fields
58 VillagerData.withProfession skip:java immutable record copy; the profession change is villager::set_profession
59 VillagerProfession.bootstrap skip:data registry bootstrap of professions; Horizon reads them from data tables
87 Player.defineSynchedData skip:java synched data definitions; Horizon keeps the fields in game and player state
88 Player.turtleHelmetTick port crates/horizon-client/src/game/potions.rs::Game::turtle_helmet_tick
89 Player.isEquipped inline crates/horizon-client/src/game/potions.rs::Game::turtle_helmet_tick the head-slot check for the turtle helmet
90 Player.canPlayerFitWithinBlocksAndEntitiesWhen missing port crates/horizon-client/src/pose.rs::MotionWorld::can_fit_pose unsure: the pose fit check (no collision in the new pose box) was not found in Horizon the box of the pose's dimensions at the player's position, deflated by 1e-7, tested against the player's collision; `update_player_pose` asks it for the swimming, the desired and the crouching pose in that order, as `updatePlayerPose` does
91 Player.increaseScore skip:other the score is experience.total in Horizon (death screen); there is no separate score counter
92 Player.getWeaponItem missing unsure: the riptide spin-attack item (autoSpinAttackItemStack) was not found; Horizon keeps spin_attack parts only the riptide spin attack keeps only `auto_spin_attack_ticks` and `auto_spin_attack_dmg`, not the stack it started with (`autoSpinAttackItemStack`), so there is no item to answer with mid-spin
93 Player.destroyVanishingCursedItems port crates/horizon-client/src/game.rs::Game::drop_inventory vanishing-curse stacks are removed from the death drops
94 Player.hasCorrectToolForDrops port crates/horizon-client/src/game.rs::Game::tick_mining the correct-tool test for harvest drops
95 Player.readAdditionalSaveData port crates/horizon-client/src/save.rs::Level::read_player
100 Player.deflectProjectile port crates/horizon-client/src/game/deflect.rs::Game::attack_redirectable
101 Player.baseDamageScaleFactor inline crates/horizon-client/src/game/mace.rs::Game::sweep_damage
102 Player.isEffectiveAi skip:other client-side AI gate; Horizon has no effective-AI check in its loop
103 Player.hasContainerOpen missing unsure: the test for an open container menu (not the inventory menu) was not located no test for an open container menu: screens are `self.screen`, and nothing distinguishes a container menu from the inventory menu
104 Player.startSleepInBed port crates/horizon-client/src/game.rs::Game::try_sleep
105 Player.isSleepingLongEnough port crates/horizon-client/src/game/lan_actions.rs::sleep_counts 100 ticks in bed is what counts a sleeper as deep for SleepStatus.update
106 Player.updateSwimming port crates/horizon-client/src/motion.rs::MotionWorld::swimming
107 Player.getXpNeededForNextLevel port crates/horizon-client/src/entities.rs::Experience::needed
108 Player.hasEnoughFoodToDoExhaustiveManoeuvres missing unsure: the food-or-flying test for exhaustive moves was not found in motion.rs no food-or-flying gate anywhere in `motion.rs`: the exhaustion of a jump or a move is added without asking the food level
109 Player.mayUseItemAt port crates/horizon-client/src/game.rs::Game::use_hand the may-build test before use-on in adventure mode
110 Player.isSwimming port crates/horizon-client/src/motion.rs::MotionWorld::swimming
111 Player.decorateDisplayNameComponent port crates/horizon-client/src/game/commands.rs::player_display_name click suggests /tell, hover shows the player entity, plus the insertion
133 StackedContents.RecipePicker.getVisitiedItemIndex skip:java bitset index arithmetic; plain bool vectors in Horizon
134 StackedContents.RecipePicker.clearAllVisited port crates/horizon-client/src/menu/place.rs::Picker::clear_visited
135 StackedItemContents.getBiggestCraftableStack port crates/horizon-client/src/menu/place.rs::Stacked::try_pick_all the largest craftable count, found by a bounded search
136 EvokerFangs.readAdditionalSaveData missing minor: fangs are not saved; a save in mid-bite drops them fangs are not saved: `entity_nbt.rs` has no evoker fangs writer, so a save in mid-bite drops them
137 EvokerFangs.addAdditionalSaveData missing minor: fangs are not saved; the warmup and owner are not written no writer for the warmup delay or the owner either
138 EvokerFangs.getAnimationProgress port crates/horizon-client/src/game/fangs.rs::Game::fangs_geometry the bite progress that positions the fang model
139 EyeOfEnder.setItem skip:ui the item sprite a flying eye shows; Horizon draws the eye itself
140 EyeOfEnder.shouldRenderAtSqrDistance skip:ui render distance culling only
141 EyeOfEnder.spawnParticles port crates/horizon-client/src/game/end.rs::Game::eye_break_particles the portal crumbs drawn in toward the eye
142 EyeOfEnder.readAdditionalSaveData missing minor: flying eyes are not saved; a save mid-flight loses them a flying eye of ender is not saved: `entity_nbt.rs` has no writer for it (nor `saved_with_chunk` a kind for it), so a save mid-flight loses it
+12 -12
View File
@@ -20,16 +20,16 @@ FishingHook.handleEntityEvent inline crates/horizon-client/src/game/fishing.rs::
FishingHook.getPlayerOwner skip:other owner is implicit: the host's hook is self.hook and a guest's is in that guest's play state
FishingHook.getAddEntityPacket port crates/horizon-client/src/game/fishing.rs::Hook::track add-entity payload: the owner entity id is the bobber's data field
LlamaSpit.onHitEntity port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit spit hit: 1.0 damage with the spit source to a player (mob_hits_player) or a mob
Projectile.applyImplicitComponent missing minor: a projectile given a can_break component by its item keeps no break predicate; Horizon applies implicit components only to buckets
Projectile.applyImplicitComponent missing an implicit component is applied only to a mob coming out of a bucket (`entity_nbt::bucket_components`, `game/creatures.rs:5058`), so a projectile spawned from an item with a `can_break` component keeps no break predicate
Projectile.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::put_projectile Owner and LeftOwner only when set, HasBeenShot once it has ticked (a save before the first tick keeps what it loaded)
Projectile.ownedBy inline crates/horizon-client/src/projectiles.rs::Projectile::tick the owner is skipped in the target loop until the projectile has left it
Projectile.readAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::throwable_from_nbt no shared reader: every kind's own reader takes Owner, LeftOwner and NoGravity (this one, `fireball_from_nbt`, `arrow_from_nbt`); HasBeenShot is read for arrows only, and no projectile keeps a `can_break` predicate
Projectile.restoreFrom missing minor: the owner is not carried over a dimension change; Horizon has no projectile dimension change
Projectile.restoreFrom missing no projectile `restoreFrom`: the owner is not carried over a dimension change
Projectile.isOutsideOwnerCollisionRange inline crates/horizon-client/src/projectiles.rs::Projectile::tick gap: minor: left_owner tests the owner's box inflated 1.0 at the position only; vanilla sweeps along the motion and checks the owner's passengers too
Projectile.getMovementToShoot port crates/horizon-client/src/projectiles.rs::Projectile::shoot direction plus triangle spread 0.0172275 times uncertainty, scaled by power
Projectile.onAboveBubbleColumn port crates/horizon-client/src/arrows.rs::Ticker::bubble_column gap: minor: arrows only; thrown ender pearls get their push in creatures.rs tick_projectiles and other projectiles get none
Projectile.onInsideBubbleColumn port crates/horizon-client/src/arrows.rs::Ticker::bubble_column gap: minor: arrows only; thrown ender pearls get their push in creatures.rs tick_projectiles and other projectiles get none
Projectile.canBreakBlockInAdventureMode missing minor: the can_break predicate and adventure-mode rules for projectile block breaking are not modelled
Projectile.canBreakBlockInAdventureMode missing a projectile's block breaking ignores the `can_break` predicate and the adventure-mode rules
Projectile.spawnProjectileFromRotation port crates/horizon-client/src/projectiles.rs::Projectile::shoot_from_rotation gap: minor: the aim and inherited velocity only; the add-to-world step is the caller's
Projectile.spawnProjectile inline crates/horizon-client/src/game/creatures.rs::Game::throw_item each shooter adds its own projectile (throw_item, shoot_crossbow, shoot_projectile_item, the bow in game.rs); throw_item is the player throw path
Projectile.applyOnProjectileSpawned port crates/horizon-client/src/game/enchant_effects.rs::Game::bow_projectile_ignite gap: minor: Flame ignite only; Piercing and Punch go through Projectile::set_bonus
@@ -43,14 +43,14 @@ ProjectileUtil.getHitResultOnViewVector port crates/horizon-client/src/game/pick
ProjectileUtil.getHitResult port crates/horizon-client/src/projectiles.rs::Projectile::tick block clip first, then the nearest entity box on the segment; same core as the move-vector sweep
ProjectileUtil.rotateTowardsMovement port crates/horizon-client/src/projectiles.rs::Projectile::update_rotation gap: minor: rotation is set at once, vanilla lerps it by rotationSpeed
ProjectileUtil.getMobArrow port crates/horizon-client/src/mobs/undead.rs::perform_ranged_attack gap: minor: builds the skeleton-family arrow with base damage from power; illager crossbow arrows are built in mobs/illager.rs::shoot_crossbow
ShulkerBullet.addAdditionalSaveData missing minor: the final target and move direction are not saved; Horizon does not save flying projectiles
ShulkerBullet.readAdditionalSaveData missing minor: the final target and move direction are not read back; Horizon does not save flying projectiles
ShulkerBullet.canHitEntity missing minor: no-physics entities are not skipped by the bullet's target search; Projectile::tick checks owner and boxes only
ShulkerBullet.addAdditionalSaveData missing `entity_nbt.rs::saved_with_chunk` leaves `ShulkerBullet` out of the kinds a chunk writes, so a bullet in flight is dropped with the save; the final target and the move direction have no writer
ShulkerBullet.readAdditionalSaveData missing the same gap on the load side: nothing reads a bullet's final target or move direction back
ShulkerBullet.canHitEntity missing the bullet's target search (`Projectile::tick` on the client, `projectiles` in `game/creatures.rs`) tests the owner and the boxes only; a no-physics entity is not skipped
ShulkerBullet.onHitBlock port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit shulker bullet block hit: hit sound and two explosion particles at the bullet
ShulkerBullet.destroy inline crates/horizon-client/src/game/creatures.rs::Game::tick_projectiles the bullet is dropped from the flight list on any hit; the game event is not sent
ShulkerBullet.hurtServer port crates/horizon-client/src/game/deflect.rs::Game::hit_shulker_bullet
ThrowableProjectile.applyInertia port crates/horizon-client/src/projectiles.rs::Projectile::tick default arm: gravity, then drag 0.8 in water and 0.99 in air; gap: minor: no bubble particles in water
ThrowableProjectile.handleFirstTickBubbleColumn missing minor: a thrown item that spawns inside a bubble column is not pushed on its first tick
ThrowableProjectile.handleFirstTickBubbleColumn missing a thrown item spawned inside a bubble column is not pushed on its first tick: `game/creatures.rs` has bubble-column particles and the pearl's own column hooks, nothing for a throwable's first tick
AbstractArrow.<init> port crates/horizon-client/src/arrows.rs::ArrowState::new arrow defaults: pickup, pickup item, shake, pierce; the owner and drawn ammo come from set_owner and set_item
AbstractArrow.defineSynchedData skip:java flags, pierce level and in-ground sync fields; Horizon keeps them on ArrowState and Projectile
AbstractArrow.lerpMotion skip:other client interpolation of a server motion packet; Horizon's remote arrows take the server position each update
@@ -68,7 +68,7 @@ AbstractArrow.tryPickup port crates/horizon-client/src/arrows.rs::ArrowState::pl
AbstractArrow.setFlag port crates/horizon-client/src/projectiles.rs::Projectile::set_critical gap: minor: only the critical flag; the no-physics flag is a plain field
AbstractArrow.setPickupItemStack port crates/horizon-client/src/arrows.rs::ArrowState::set_item an empty stack falls back to the default pickup item
AbstractArrow.isNoPhysics inline crates/horizon-client/src/arrows.rs::Ticker::tick no_physics is a field read by the tick
AbstractArrow.getSlot missing minor: the arrow's pickup stack is not reachable as an entity slot, so item commands on it do nothing
AbstractArrow.getSlot missing an arrow's pickup stack is not addressable as an entity slot, so item commands on an arrow do nothing
AbstractArrow.Pickup.byOrdinal port crates/horizon-client/src/arrows.rs::Pickup::from_byte out of range is DISALLOWED
Arrow.updateColor port crates/horizon-client/src/projectiles.rs::Projectile::arrow_effect_color the tipped arrow's opaque potion colour for its flight and hit particles (-1 for none)
Arrow.makeParticle port crates/horizon-client/src/game/remote_projectiles.rs::Game::arrow_makeparticle ticked in #3514; `Arrow.makeParticle` is cited at crates/horizon-client/src/game/remote_projectiles.rs:218
@@ -82,11 +82,11 @@ ThrownTrident.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.r
ThrownTrident.getLoyaltyFromItem port crates/horizon-client/src/game/trident.rs::Game::release_trident loyalty level read from the thrown stack when the trident is released
ThrownTrident.tickDespawn inline crates/horizon-client/src/arrows.rs::Ticker::tick gap: minor: the 1200-tick life limit applies to every trident; the loyalty exemption is not checked
AbstractHurtingProjectile.shouldRenderAtSqrDistance skip:ui distance culling for the fireball model; Horizon culls with its own range check
AbstractHurtingProjectile.canHitEntity missing minor: no-physics entities are not skipped by the fireball's target search
AbstractHurtingProjectile.canHitEntity missing same as `ShulkerBullet.canHitEntity`: the fireball's target search has no no-physics filter
WitherSkull.getBlockExplosionResistance port crates/horizon-client/src/game/explosion.rs::blast_resistance a dangerous skull's blast caps a breakable block's resistance at 0.8; `#wither_immune` blocks keep theirs
WitherSkull.onHit port crates/horizon-client/src/game/wither.rs::Game::wither_skull_hit skull hit damage, wither effect, then the blast at the impact point
AbstractWindCharge.makeBoundingBox port crates/horizon-client/src/projectiles.rs::ProjectileKind::size gap: minor: gives the width only; the box sits 0.15 below the position in vanilla
AbstractWindCharge.canHitEntity missing minor: no filter for end crystals or other wind charges found in the target list; unsure whether the caller excludes them
AbstractWindCharge.canHitEntity missing the wind charge's target list is `creatures.rs::wind_charge_hitboxes`, which collects the other live, unstuck wind charges as hittable boxes: nothing there drops a wind charge or an end crystal, so the filter `canHitEntity` makes is not applied
AbstractWindCharge.onHitBlock port crates/horizon-client/src/game/wind.rs::Game::wind_charge_hit a burst a quarter block out along the hit face
WindCharge.deflect port crates/horizon-client/src/game/wind.rs::reverse gap: minor: turns the charge at half speed; the noDeflectTicks delay is not modelled
WindCharge.shouldRenderAtSqrDistance skip:ui distance culling for the wind charge model; Horizon culls with its own range check
@@ -97,10 +97,10 @@ ThrownEnderpearl.getOwner skip:other owner is kept as a Who value and resolved
ThrownEnderpearl.findOwnerIncludingDeadPlayer skip:other owner lookup by UUID across dead players; Horizon keeps the owner id directly
ThrownEnderpearl.onHitEntity port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit a mob hit takes zero damage; the pearl still lands
ThrownEnderpearl.onHit port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit gap: minor: no 32 portal particles and no dimension change; the owner is teleported to where the pearl was, with 5 damage and a 5 percent endermite
ThrownEnderpearl.isAllowedToTeleportOwner missing minor: a pearl whose owner is dead or asleep when it lands still teleports the owner
ThrownEnderpearl.isAllowedToTeleportOwner missing `game/creatures.rs` teleports the owner on a pearl's hit without asking whether they are dead or sleeping
ThrownEnderpearl.playSound port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit teleport sound at the landing point
ThrownEnderpearl.teleport port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit moves the player to the pearl's previous position and clears velocity and fall distance
ThrownEnderpearl.canTeleport missing minor: a pearl from the End to the Overworld needs the owner's seen-credits flag before it may teleport
ThrownEnderpearl.canTeleport missing no seen-credits test: a pearl whose owner came from the End teleports them back to the Overworld regardless
ThrownEnderpearl.onInsideBlock port crates/horizon-client/src/game/end.rs::Game::pearl_owner_in_gateway a pearl that flies into an End gateway sends its owner through and fires enter_block
Raid.<init> port crates/horizon-client/src/raid.rs::Raid::new centre, difficulty-based group count, cooldown and active flag
Raid.getAllRaiders port crates/horizon-client/src/game/raids.rs::Game::raid_members the raiders of this raid, from the mobs carrying its id
1 FireworkRocketEntity.<init> port crates/horizon-client/src/game/fireworks.rs::Game::new_rocket launch velocity triangle and lifetime 10*(1+flight)+randoms; the owner and attached target are set by the callers
20 FishingHook.getPlayerOwner skip:other owner is implicit: the host's hook is self.hook and a guest's is in that guest's play state
21 FishingHook.getAddEntityPacket port crates/horizon-client/src/game/fishing.rs::Hook::track add-entity payload: the owner entity id is the bobber's data field
22 LlamaSpit.onHitEntity port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit spit hit: 1.0 damage with the spit source to a player (mob_hits_player) or a mob
23 Projectile.applyImplicitComponent missing minor: a projectile given a can_break component by its item keeps no break predicate; Horizon applies implicit components only to buckets an implicit component is applied only to a mob coming out of a bucket (`entity_nbt::bucket_components`, `game/creatures.rs:5058`), so a projectile spawned from an item with a `can_break` component keeps no break predicate
24 Projectile.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::put_projectile Owner and LeftOwner only when set, HasBeenShot once it has ticked (a save before the first tick keeps what it loaded)
25 Projectile.ownedBy inline crates/horizon-client/src/projectiles.rs::Projectile::tick the owner is skipped in the target loop until the projectile has left it
26 Projectile.readAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::throwable_from_nbt no shared reader: every kind's own reader takes Owner, LeftOwner and NoGravity (this one, `fireball_from_nbt`, `arrow_from_nbt`); HasBeenShot is read for arrows only, and no projectile keeps a `can_break` predicate
27 Projectile.restoreFrom missing minor: the owner is not carried over a dimension change; Horizon has no projectile dimension change no projectile `restoreFrom`: the owner is not carried over a dimension change
28 Projectile.isOutsideOwnerCollisionRange inline crates/horizon-client/src/projectiles.rs::Projectile::tick gap: minor: left_owner tests the owner's box inflated 1.0 at the position only; vanilla sweeps along the motion and checks the owner's passengers too
29 Projectile.getMovementToShoot port crates/horizon-client/src/projectiles.rs::Projectile::shoot direction plus triangle spread 0.0172275 times uncertainty, scaled by power
30 Projectile.onAboveBubbleColumn port crates/horizon-client/src/arrows.rs::Ticker::bubble_column gap: minor: arrows only; thrown ender pearls get their push in creatures.rs tick_projectiles and other projectiles get none
31 Projectile.onInsideBubbleColumn port crates/horizon-client/src/arrows.rs::Ticker::bubble_column gap: minor: arrows only; thrown ender pearls get their push in creatures.rs tick_projectiles and other projectiles get none
32 Projectile.canBreakBlockInAdventureMode missing minor: the can_break predicate and adventure-mode rules for projectile block breaking are not modelled a projectile's block breaking ignores the `can_break` predicate and the adventure-mode rules
33 Projectile.spawnProjectileFromRotation port crates/horizon-client/src/projectiles.rs::Projectile::shoot_from_rotation gap: minor: the aim and inherited velocity only; the add-to-world step is the caller's
34 Projectile.spawnProjectile inline crates/horizon-client/src/game/creatures.rs::Game::throw_item each shooter adds its own projectile (throw_item, shoot_crossbow, shoot_projectile_item, the bow in game.rs); throw_item is the player throw path
35 Projectile.applyOnProjectileSpawned port crates/horizon-client/src/game/enchant_effects.rs::Game::bow_projectile_ignite gap: minor: Flame ignite only; Piercing and Punch go through Projectile::set_bonus
43 ProjectileUtil.getHitResult port crates/horizon-client/src/projectiles.rs::Projectile::tick block clip first, then the nearest entity box on the segment; same core as the move-vector sweep
44 ProjectileUtil.rotateTowardsMovement port crates/horizon-client/src/projectiles.rs::Projectile::update_rotation gap: minor: rotation is set at once, vanilla lerps it by rotationSpeed
45 ProjectileUtil.getMobArrow port crates/horizon-client/src/mobs/undead.rs::perform_ranged_attack gap: minor: builds the skeleton-family arrow with base damage from power; illager crossbow arrows are built in mobs/illager.rs::shoot_crossbow
46 ShulkerBullet.addAdditionalSaveData missing minor: the final target and move direction are not saved; Horizon does not save flying projectiles `entity_nbt.rs::saved_with_chunk` leaves `ShulkerBullet` out of the kinds a chunk writes, so a bullet in flight is dropped with the save; the final target and the move direction have no writer
47 ShulkerBullet.readAdditionalSaveData missing minor: the final target and move direction are not read back; Horizon does not save flying projectiles the same gap on the load side: nothing reads a bullet's final target or move direction back
48 ShulkerBullet.canHitEntity missing minor: no-physics entities are not skipped by the bullet's target search; Projectile::tick checks owner and boxes only the bullet's target search (`Projectile::tick` on the client, `projectiles` in `game/creatures.rs`) tests the owner and the boxes only; a no-physics entity is not skipped
49 ShulkerBullet.onHitBlock port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit shulker bullet block hit: hit sound and two explosion particles at the bullet
50 ShulkerBullet.destroy inline crates/horizon-client/src/game/creatures.rs::Game::tick_projectiles the bullet is dropped from the flight list on any hit; the game event is not sent
51 ShulkerBullet.hurtServer port crates/horizon-client/src/game/deflect.rs::Game::hit_shulker_bullet
52 ThrowableProjectile.applyInertia port crates/horizon-client/src/projectiles.rs::Projectile::tick default arm: gravity, then drag 0.8 in water and 0.99 in air; gap: minor: no bubble particles in water
53 ThrowableProjectile.handleFirstTickBubbleColumn missing minor: a thrown item that spawns inside a bubble column is not pushed on its first tick a thrown item spawned inside a bubble column is not pushed on its first tick: `game/creatures.rs` has bubble-column particles and the pearl's own column hooks, nothing for a throwable's first tick
54 AbstractArrow.<init> port crates/horizon-client/src/arrows.rs::ArrowState::new arrow defaults: pickup, pickup item, shake, pierce; the owner and drawn ammo come from set_owner and set_item
55 AbstractArrow.defineSynchedData skip:java flags, pierce level and in-ground sync fields; Horizon keeps them on ArrowState and Projectile
56 AbstractArrow.lerpMotion skip:other client interpolation of a server motion packet; Horizon's remote arrows take the server position each update
68 AbstractArrow.setFlag port crates/horizon-client/src/projectiles.rs::Projectile::set_critical gap: minor: only the critical flag; the no-physics flag is a plain field
69 AbstractArrow.setPickupItemStack port crates/horizon-client/src/arrows.rs::ArrowState::set_item an empty stack falls back to the default pickup item
70 AbstractArrow.isNoPhysics inline crates/horizon-client/src/arrows.rs::Ticker::tick no_physics is a field read by the tick
71 AbstractArrow.getSlot missing minor: the arrow's pickup stack is not reachable as an entity slot, so item commands on it do nothing an arrow's pickup stack is not addressable as an entity slot, so item commands on an arrow do nothing
72 AbstractArrow.Pickup.byOrdinal port crates/horizon-client/src/arrows.rs::Pickup::from_byte out of range is DISALLOWED
73 Arrow.updateColor port crates/horizon-client/src/projectiles.rs::Projectile::arrow_effect_color the tipped arrow's opaque potion colour for its flight and hit particles (-1 for none)
74 Arrow.makeParticle port crates/horizon-client/src/game/remote_projectiles.rs::Game::arrow_makeparticle ticked in #3514; `Arrow.makeParticle` is cited at crates/horizon-client/src/game/remote_projectiles.rs:218
82 ThrownTrident.getLoyaltyFromItem port crates/horizon-client/src/game/trident.rs::Game::release_trident loyalty level read from the thrown stack when the trident is released
83 ThrownTrident.tickDespawn inline crates/horizon-client/src/arrows.rs::Ticker::tick gap: minor: the 1200-tick life limit applies to every trident; the loyalty exemption is not checked
84 AbstractHurtingProjectile.shouldRenderAtSqrDistance skip:ui distance culling for the fireball model; Horizon culls with its own range check
85 AbstractHurtingProjectile.canHitEntity missing minor: no-physics entities are not skipped by the fireball's target search same as `ShulkerBullet.canHitEntity`: the fireball's target search has no no-physics filter
86 WitherSkull.getBlockExplosionResistance port crates/horizon-client/src/game/explosion.rs::blast_resistance a dangerous skull's blast caps a breakable block's resistance at 0.8; `#wither_immune` blocks keep theirs
87 WitherSkull.onHit port crates/horizon-client/src/game/wither.rs::Game::wither_skull_hit skull hit damage, wither effect, then the blast at the impact point
88 AbstractWindCharge.makeBoundingBox port crates/horizon-client/src/projectiles.rs::ProjectileKind::size gap: minor: gives the width only; the box sits 0.15 below the position in vanilla
89 AbstractWindCharge.canHitEntity missing minor: no filter for end crystals or other wind charges found in the target list; unsure whether the caller excludes them the wind charge's target list is `creatures.rs::wind_charge_hitboxes`, which collects the other live, unstuck wind charges as hittable boxes: nothing there drops a wind charge or an end crystal, so the filter `canHitEntity` makes is not applied
90 AbstractWindCharge.onHitBlock port crates/horizon-client/src/game/wind.rs::Game::wind_charge_hit a burst a quarter block out along the hit face
91 WindCharge.deflect port crates/horizon-client/src/game/wind.rs::reverse gap: minor: turns the charge at half speed; the noDeflectTicks delay is not modelled
92 WindCharge.shouldRenderAtSqrDistance skip:ui distance culling for the wind charge model; Horizon culls with its own range check
97 ThrownEnderpearl.findOwnerIncludingDeadPlayer skip:other owner lookup by UUID across dead players; Horizon keeps the owner id directly
98 ThrownEnderpearl.onHitEntity port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit a mob hit takes zero damage; the pearl still lands
99 ThrownEnderpearl.onHit port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit gap: minor: no 32 portal particles and no dimension change; the owner is teleported to where the pearl was, with 5 damage and a 5 percent endermite
100 ThrownEnderpearl.isAllowedToTeleportOwner missing minor: a pearl whose owner is dead or asleep when it lands still teleports the owner `game/creatures.rs` teleports the owner on a pearl's hit without asking whether they are dead or sleeping
101 ThrownEnderpearl.playSound port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit teleport sound at the landing point
102 ThrownEnderpearl.teleport port crates/horizon-client/src/game/creatures.rs::Game::projectile_hit moves the player to the pearl's previous position and clears velocity and fall distance
103 ThrownEnderpearl.canTeleport missing minor: a pearl from the End to the Overworld needs the owner's seen-credits flag before it may teleport no seen-credits test: a pearl whose owner came from the End teleports them back to the Overworld regardless
104 ThrownEnderpearl.onInsideBlock port crates/horizon-client/src/game/end.rs::Game::pearl_owner_in_gateway a pearl that flies into an End gateway sends its owner through and fires enter_block
105 Raid.<init> port crates/horizon-client/src/raid.rs::Raid::new centre, difficulty-based group count, cooldown and active flag
106 Raid.getAllRaiders port crates/horizon-client/src/game/raids.rs::Game::raid_members the raiders of this raid, from the mobs carrying its id
+5 -5
View File
@@ -1,11 +1,11 @@
AbstractBoat.setInitialPos port crates/horizon-client/src/game/boats.rs::Boat::new spawn sets the position and its previous copy; velocity and yaw start at zero
AbstractBoat.defineSynchedData skip:java SynchedEntityData definitions; Horizon keeps paddle, hurt and input state on the Boat struct
AbstractBoat.getPassengerAttachmentPoint port crates/horizon-client/src/game/boats.rs::Boat::seat seat height and the two-seat offsets, +0.2 for an animal; chest boat's 0.15 is included
AbstractBoat.onAboveBubbleColumn missing player: a boat over a bubble column is not lifted or pushed down and no splash plays on it; Horizon has no bubble column for boats
AbstractBoat.onAboveBubbleColumn port crates/horizon-client/src/game/boats.rs::Game::boat_above_bubble_column a column whose surface the boat is over marks it above a column and starts the 60-tick bubble time, down or up; out of water, one roll in a hundred plays the swim splash sound and particle and posts the splash game event
AbstractBoat.animateHurt port crates/horizon-client/src/game/boats.rs::Game::hurt_boat sets the hurt wobble and time; gap: minor: uses the server damage x10, no separate client x11 wobble value
AbstractBoat.tickBubbleColumn missing player: a boat in a bubble column does not rise or sink and ejects no passengers from a down column; Horizon has no bubble time on boats
AbstractBoat.handleEntityEvent missing player: the bubble column effect event (71 or 72) never reaches a boat, same gap as the bubble column lift
AbstractBoat.handleBubbleColumnEffect missing player: a boat in an up or down bubble column keeps its speed; Horizon never changes its vertical velocity from a column
AbstractBoat.tickBubbleColumn port crates/horizon-client/src/game/boats.rs::Game::tick_boat_bubble_column the countdown (`Boat::tick_bubble_countdown`) running out throws the boat up or pulls it down after BUBBLE_TIME ticks, lets the riders out of a down column first and broadcasts event 71 or 72; the client's wobble is `Boat::tick_bubble_wobble`
AbstractBoat.handleEntityEvent port crates/horizon-client/src/game/remote_vehicles.rs::Game::remote_boat_event event 71 drags the boat down and 72 throws it up, on the boat the client drives; every other id is left to the super call
AbstractBoat.handleBubbleColumnEffect port crates/horizon-client/src/game/boats.rs::Game::handle_bubble_column_effect an up column sets vertical velocity 0.6 and a down column adds -0.7, only while the window simulates the boat's movement
AbstractBoat.setPaddleState inline crates/horizon-client/src/game/boats.rs::Game::tick_boat paddle flags are set from the rider's input inside the controlled branch
AbstractBoat.getLeashOffset port crates/horizon-client/src/mobs/leash.rs::Holder::boat_leash_offset 1.375 by 0.5625 for a boat or raft
AbstractBoat.getStatus port crates/horizon-client/src/game/boats.rs::Game::boat_status in air, in water, under water or on land
@@ -18,7 +18,7 @@ AbstractBoat.controlBoat inline crates/horizon-client/src/game/boats.rs::Game::t
AbstractBoat.getDismountLocationForPassenger port crates/horizon-client/src/game/boats.rs::Game::boat_dismount_target gap: minor: no dismount pose check; the floor and water tests are there
AbstractBoat.clampRotation inline crates/horizon-client/src/game/remote_vehicles.rs::Game::remote_ride the rider's yaw is clamped to 105 degrees off the boat; mobs get the head clamp in boat_passengers.rs seat_boat_mobs
AbstractBoat.calculatePassengerBodyYRot inline crates/horizon-client/src/game/boat_passengers.rs::Game::seat_boat_mobs animal turned 90 or 270 degrees when the boat is full
AbstractBoat.onPassengerTurned inline crates/horizon-client/src/game/boats.rs::Game::tick_riding the rider's turn is clamped each tick
AbstractBoat.onPassengerTurned inline crates/horizon-client/src/game/remote_vehicles.rs::Game::remote_ride the rider's turn is clamped each tick
AbstractBoat.remove inline crates/horizon-client/src/game/boats.rs::Game::hurt_boat destroyed boats eject mobs and drop the boat; gap: minor: no leash drop, since boats cannot be leashed here
AbstractBoat.checkFallDamage missing minor: boats keep no fall distance; boats take no fall damage either way
AbstractBoat.getPaddleState port crates/horizon-client/src/game/boats.rs::Boat::rowing_time paddle flag, set only while a rider drives
1 AbstractBoat.setInitialPos port crates/horizon-client/src/game/boats.rs::Boat::new spawn sets the position and its previous copy; velocity and yaw start at zero
2 AbstractBoat.defineSynchedData skip:java SynchedEntityData definitions; Horizon keeps paddle, hurt and input state on the Boat struct
3 AbstractBoat.getPassengerAttachmentPoint port crates/horizon-client/src/game/boats.rs::Boat::seat seat height and the two-seat offsets, +0.2 for an animal; chest boat's 0.15 is included
4 AbstractBoat.onAboveBubbleColumn missing port crates/horizon-client/src/game/boats.rs::Game::boat_above_bubble_column player: a boat over a bubble column is not lifted or pushed down and no splash plays on it; Horizon has no bubble column for boats a column whose surface the boat is over marks it above a column and starts the 60-tick bubble time, down or up; out of water, one roll in a hundred plays the swim splash sound and particle and posts the splash game event
5 AbstractBoat.animateHurt port crates/horizon-client/src/game/boats.rs::Game::hurt_boat sets the hurt wobble and time; gap: minor: uses the server damage x10, no separate client x11 wobble value
6 AbstractBoat.tickBubbleColumn missing port crates/horizon-client/src/game/boats.rs::Game::tick_boat_bubble_column player: a boat in a bubble column does not rise or sink and ejects no passengers from a down column; Horizon has no bubble time on boats the countdown (`Boat::tick_bubble_countdown`) running out throws the boat up or pulls it down after BUBBLE_TIME ticks, lets the riders out of a down column first and broadcasts event 71 or 72; the client's wobble is `Boat::tick_bubble_wobble`
7 AbstractBoat.handleEntityEvent missing port crates/horizon-client/src/game/remote_vehicles.rs::Game::remote_boat_event player: the bubble column effect event (71 or 72) never reaches a boat, same gap as the bubble column lift event 71 drags the boat down and 72 throws it up, on the boat the client drives; every other id is left to the super call
8 AbstractBoat.handleBubbleColumnEffect missing port crates/horizon-client/src/game/boats.rs::Game::handle_bubble_column_effect player: a boat in an up or down bubble column keeps its speed; Horizon never changes its vertical velocity from a column an up column sets vertical velocity 0.6 and a down column adds -0.7, only while the window simulates the boat's movement
9 AbstractBoat.setPaddleState inline crates/horizon-client/src/game/boats.rs::Game::tick_boat paddle flags are set from the rider's input inside the controlled branch
10 AbstractBoat.getLeashOffset port crates/horizon-client/src/mobs/leash.rs::Holder::boat_leash_offset 1.375 by 0.5625 for a boat or raft
11 AbstractBoat.getStatus port crates/horizon-client/src/game/boats.rs::Game::boat_status in air, in water, under water or on land
18 AbstractBoat.getDismountLocationForPassenger port crates/horizon-client/src/game/boats.rs::Game::boat_dismount_target gap: minor: no dismount pose check; the floor and water tests are there
19 AbstractBoat.clampRotation inline crates/horizon-client/src/game/remote_vehicles.rs::Game::remote_ride the rider's yaw is clamped to 105 degrees off the boat; mobs get the head clamp in boat_passengers.rs seat_boat_mobs
20 AbstractBoat.calculatePassengerBodyYRot inline crates/horizon-client/src/game/boat_passengers.rs::Game::seat_boat_mobs animal turned 90 or 270 degrees when the boat is full
21 AbstractBoat.onPassengerTurned inline crates/horizon-client/src/game/boats.rs::Game::tick_riding crates/horizon-client/src/game/remote_vehicles.rs::Game::remote_ride the rider's turn is clamped each tick
22 AbstractBoat.remove inline crates/horizon-client/src/game/boats.rs::Game::hurt_boat destroyed boats eject mobs and drop the boat; gap: minor: no leash drop, since boats cannot be leashed here
23 AbstractBoat.checkFallDamage missing minor: boats keep no fall distance; boats take no fall damage either way
24 AbstractBoat.getPaddleState port crates/horizon-client/src/game/boats.rs::Boat::rowing_time paddle flag, set only while a rider drives
+12 -12
View File
@@ -34,7 +34,7 @@ StonecutterMenu.removed port crates/horizon-client/src/game/stations.rs::Game::c
ArrowItem.asProjectile port crates/horizon-client/src/game/dispenser.rs::Game::shoot_projectile_item dispensed arrows are built in the arrow arm of the item shot
BlockItem.updateBlockEntityComponents port crates/horizon-client/src/block_entity_item.rs::place the placed block entity takes the item's components
BlockItem.canPlace port crates/horizon-world/src/level/place.rs::Sim::place the placement checks, including the survive check, run in Sim::place
BlockItem.onDestroyed missing unsure: a container item whose item entity is destroyed should spill its contents; no Horizon code does this
BlockItem.onDestroyed missing no `onDestroyed` on any item: an item entity of a shulker box or a bundle destroyed by fire or lava spills nothing
BoatItem.getBoat port crates/horizon-client/src/game/boats.rs::Game::place_boat boat placed at the hit point with the item's stack config
BucketItem.playEmptySound port crates/horizon-world/src/level/buckets.rs::Sim::empty_sound
BundleItem.getWeightSafe port crates/horizon-items/src/bundle.rs::Contents::weight_fraction float fraction; vanilla compares an exact Fraction
@@ -42,7 +42,7 @@ BundleItem.getBarWidth port crates/horizon-items/src/bundle.rs::Contents::bar
BundleItem.getBarColor port crates/horizon-items/src/bundle.rs::Contents::bar
BundleItem.removeOneItemFromBundle port crates/horizon-items/src/bundle.rs::Contents::remove_one
BundleItem.getTooltipImage port crates/horizon-items/src/bundle.rs::tooltip_layout gap: minor: the tooltip_display rule that hides the bundle contents is not checked here
BundleItem.onDestroyed missing unsure: a bundle whose item entity is destroyed should spill its contents; no Horizon code does this
BundleItem.onDestroyed missing same as `BlockItem.onDestroyed`: nothing spills a destroyed item entity's contents
BundleItem.playRemoveOneSound port crates/horizon-client/src/game.rs::Game::drop_bundle_content the menu queues the same sound in menu/bundle.rs
BundleItem.playInsertSound port crates/horizon-client/src/menu/bundle.rs::Menu::carried_bundle_click queues the insert or insert-fail sound
BundleItem.playDropContentsSound port crates/horizon-client/src/game.rs::Game::drop_bundle_content
@@ -54,8 +54,8 @@ CrossbowItem.createProjectile port crates/horizon-client/src/arrows.rs::Ticker::
CrossbowItem.getShotPitch inline crates/horizon-client/src/game/crossbow.rs::Game::shoot_crossbow the shot pitch per index is inline
CrossbowItem.getRandomShotPitch inline crates/horizon-client/src/game/crossbow.rs::Game::shoot_crossbow the random pitch formula is inline
CrossbowItem.getPowerForTime inline crates/horizon-client/src/game/crossbow.rs::Game::tick_crossbow the charge fraction capped at one is the tick check against the charge time
DyeColor.getMixedColor missing player: a baby sheep, wolf or cat from parents of two dye colours gets a mixed colour; Horizon has no breeding dye mix (only sheep_color for spawns)
DyeColor.findColorMixInRecipes missing player: part of the same breeding dye mix; the crafting recipe lookup for two dyes is not done for offspring
DyeColor.getMixedColor port crates/horizon-client/src/game/creatures.rs::Game::dye_mix the colour of the crafting recipe that mixes the two dyes, else one of the two picked at random (`next_boolean`); `creatures.rs:3153`, `:3176` and `:3222` give the offspring of a sheep, wolf or cat its colour with it
DyeColor.findColorMixInRecipes port crates/horizon-client/src/game/creatures.rs::Game::dye_color_mix_in_recipes the two dyes laid out as a 2x2 crafting grid through `Recipes::find`, and the result read back as a dye colour by its `_dye` suffix
FireworkRocketItem.createDispenseConfig port crates/horizon-client/src/game/dispenser.rs::Game::dispense_projectile rocket config: offset 0.5000099, uncertainty 1, power 0.5, event 1004
FireworkRocketItem.getEntityJustOutsideOfBlockPos port crates/horizon-client/src/game/dispenser.rs::Game::dispense_position the position just outside the dispenser on the facing side
HangingEntityItem.mayPlace port crates/horizon-client/src/game/paintings.rs::Game::place_painting paintings take no vertical face; an item frame (ItemFrameItem.mayPlace) may go on one and only needs the build height, which place_item_frame checks
@@ -84,7 +84,7 @@ Item.Properties.horseArmor skip:data registration builder
Item.Properties.nautilusArmor skip:data registration builder
Item.Properties.requiredFeatures skip:data registration builder
Item.Properties.delayedHolderComponent skip:data registration builder
Item.Properties.loweredMobVisibility missing unsure: the mob_visibility component is not read by Horizon's mob targeting
Item.Properties.loweredMobVisibility missing the `mob_visibility` component is parsed (it is in `item_model.rs`'s component table and the protocol's item hash), but no mob targeting reads it
Item.Properties.finalizeInitializer skip:data registration builder
Item.TooltipContext.of skip:java tooltip context factory; Horizon passes the level and player to the tooltip functions
ItemStack.validateStrict skip:java codec-side validation of components and stack size
@@ -97,7 +97,7 @@ ItemStack.isBroken inline crates/horizon-client/src/game.rs::Game::damage_held t
ItemStack.nextDamageWillBreak inline crates/horizon-client/src/game/trident.rs::Game::use_trident gap: minor: only the trident's check is ported here
ItemStack.processDurabilityChange port crates/horizon-client/src/game/enchant_effects.rs::Game::durability_loss the Unbreaking-adjusted loss
ItemStack.applyDamage port crates/horizon-client/src/game.rs::Game::damage_held gap: minor: no item durability advancement trigger fires
ItemStack.hurtAndConvertOnBreak missing unsure: a carrot or fungus on a stick that breaks should become a fishing rod; no conversion was found
ItemStack.hurtAndConvertOnBreak missing nothing converts a broken carrot on a stick or fungus on a stick into a fishing rod
ItemStack.hurtEnemy port crates/horizon-client/src/game/mace.rs::Game::weapon_wear the per-hit durability loss of a weapon
ItemStack.interactLivingEntity port crates/horizon-client/src/game.rs::Game::interact_entity equip-on-interact is in the mob paths (nautili.rs, ghasts.rs)
ItemStack.copy skip:java value copy
@@ -111,21 +111,21 @@ ItemStack.hashStackList skip:java hash for collections
ItemStack.onCraftedBy inline crates/horizon-client/src/game.rs::Game::click_inventory gap: minor: the crafted statistic is added only on the inventory grid craft path here
ItemStack.set skip:other component write; Horizon stores component data in typed fields
ItemStack.update skip:java functional update helper
ItemStack.applyComponentsAndValidate missing unsure: a validated component patch has no single Horizon counterpart; /item modify is a separate path
ItemStack.applyComponentsAndValidate missing a validated component patch has no single counterpart: `/item modify` goes through its own path
ItemStack.getStyledHoverName port crates/horizon-client/src/game/stations.rs::Game::stack_hover_name gap: minor: plain text; the rarity colour and italic style are not applied here
ItemStack.addToTooltip port crates/horizon-client/src/item_tooltip.rs::lines
ItemStack.addUnitComponentToTooltip inline crates/horizon-client/src/game/stations.rs::Game::stack_tooltip_lines unit lines such as unbreakable, gated by the tooltip display
ItemStack.canPlaceOnBlockInAdventureMode port crates/horizon-client/src/game/game_mode.rs::Game::can_place_on_in_adventure the can_place_on predicates gate a placement in adventure mode
ItemStack.canBreakBlockInAdventureMode missing player: in adventure mode a block can be broken only when the held item's can_break list allows it; Horizon does not read that component
ItemStack.getDamageSource missing unsure: a stack's damage_type component is not read when a weapon hits; no Horizon reader found
ItemStack.canBreakBlockInAdventureMode port crates/horizon-client/src/game/game_mode.rs::Game::adventure_predicate the main-hand stack's `can_break` predicate against the block at the position (one predicate or a list, block ids or tags, exact or ranged state properties; a predicate with `nbt` or `components` never matches), read by `game_mode.rs:200`; the same fn answers `can_place_on`
ItemStack.getDamageSource missing a stack's `damage_type` component is not read when a weapon hits: the attack builds its damage from the mob's own sources (`mobs::Damage`), and `damage_type` is only used for the enchantment damage tags
ItemStackTemplate.fromNonEmptyStack port crates/horizon-items/src/item_data.rs::template_tag stack to template tag
ItemStackTemplate.withCount skip:java record copy with a new count
ItemStackTemplate.apply port crates/horizon-assets/src/item_model.rs::stack_from_template
ItemUtils.onContainerDestroyed missing unsure: destroyed container items do not spill their contents; see BlockItem.onDestroyed
ItemUtils.onContainerDestroyed missing the shared spill of a destroyed container item has no counterpart; see `BlockItem.onDestroyed`
Items.registerBlock skip:data item registry bootstrap
Items.registerItem skip:data item registry bootstrap
JukeboxSong.hasFinished port crates/horizon-items/src/items.rs::Song::finished
JukeboxSongPlayer.setSongWithoutPlaying missing unsure: no load path restores a jukebox's song start tick
JukeboxSongPlayer.setSongWithoutPlaying inline crates/horizon-client/src/save.rs::decode_block_entity the load sets the record and `ticks_since_song_started` straight into the `Jukebox` block entity, which is the whole of `setSongWithoutPlaying`; the song carries on from that tick in `tick_jukebox`
JukeboxSongPlayer.play port crates/horizon-client/src/game/jukebox.rs::Game::start_song the song starts at the jukebox with its sound
JukeboxSongPlayer.shouldEmitJukeboxPlayingEvent inline crates/horizon-client/src/game/jukebox.rs::Game::tick_jukebox
JukeboxSongPlayer.spawnMusicParticles inline crates/horizon-client/src/game/jukebox.rs::Game::tick_jukebox the note particle is emitted in tick_jukebox
@@ -138,7 +138,7 @@ MapItem.createNewSavedData port crates/horizon-client/src/game/maps.rs::MapData:
MapItem.getCorrectStateForFluidBlock inline crates/horizon-client/src/game/maps.rs::Game::update_map the map colour pass
MapItem.inventoryTick port crates/horizon-client/src/game/maps.rs::Game::tick_maps
MapItem.lockMap port crates/horizon-client/src/menu.rs::Menu::map_crafted the lock branch of the post processing
MapItem.useOn missing player: using a map on a banner block should toggle that banner's marker on the map; Horizon has no banner toggle
MapItem.useOn port crates/horizon-client/src/game/maps.rs::Game::use_map_on_banner a map used on a block in the `minecraft:banners` tag toggles that banner's marker (`MapData::toggle_banner`); a failed toggle ends the use without a swing, and `game/remote.rs` predicts the same for its own map
MobBucketItem.emptyContents port crates/horizon-world/src/level/buckets.rs::Sim::bucket_empty the MobWater and Nothing contents
PotionItem.getDefaultInstance skip:data default stack data
PotionItem.useOn port crates/horizon-client/src/game/potions.rs::Game::use_water_bottle_on_block a water bottle poured on #convertible_to_mud makes mud and empties the bottle
1 LoomMenu.<init> port crates/horizon-client/src/menu.rs::Menu::new the loom's banner, dye, pattern and result slots are laid out for Kind::Loom in the constructor
34 ArrowItem.asProjectile port crates/horizon-client/src/game/dispenser.rs::Game::shoot_projectile_item dispensed arrows are built in the arrow arm of the item shot
35 BlockItem.updateBlockEntityComponents port crates/horizon-client/src/block_entity_item.rs::place the placed block entity takes the item's components
36 BlockItem.canPlace port crates/horizon-world/src/level/place.rs::Sim::place the placement checks, including the survive check, run in Sim::place
37 BlockItem.onDestroyed missing unsure: a container item whose item entity is destroyed should spill its contents; no Horizon code does this no `onDestroyed` on any item: an item entity of a shulker box or a bundle destroyed by fire or lava spills nothing
38 BoatItem.getBoat port crates/horizon-client/src/game/boats.rs::Game::place_boat boat placed at the hit point with the item's stack config
39 BucketItem.playEmptySound port crates/horizon-world/src/level/buckets.rs::Sim::empty_sound
40 BundleItem.getWeightSafe port crates/horizon-items/src/bundle.rs::Contents::weight_fraction float fraction; vanilla compares an exact Fraction
42 BundleItem.getBarColor port crates/horizon-items/src/bundle.rs::Contents::bar
43 BundleItem.removeOneItemFromBundle port crates/horizon-items/src/bundle.rs::Contents::remove_one
44 BundleItem.getTooltipImage port crates/horizon-items/src/bundle.rs::tooltip_layout gap: minor: the tooltip_display rule that hides the bundle contents is not checked here
45 BundleItem.onDestroyed missing unsure: a bundle whose item entity is destroyed should spill its contents; no Horizon code does this same as `BlockItem.onDestroyed`: nothing spills a destroyed item entity's contents
46 BundleItem.playRemoveOneSound port crates/horizon-client/src/game.rs::Game::drop_bundle_content the menu queues the same sound in menu/bundle.rs
47 BundleItem.playInsertSound port crates/horizon-client/src/menu/bundle.rs::Menu::carried_bundle_click queues the insert or insert-fail sound
48 BundleItem.playDropContentsSound port crates/horizon-client/src/game.rs::Game::drop_bundle_content
54 CrossbowItem.getShotPitch inline crates/horizon-client/src/game/crossbow.rs::Game::shoot_crossbow the shot pitch per index is inline
55 CrossbowItem.getRandomShotPitch inline crates/horizon-client/src/game/crossbow.rs::Game::shoot_crossbow the random pitch formula is inline
56 CrossbowItem.getPowerForTime inline crates/horizon-client/src/game/crossbow.rs::Game::tick_crossbow the charge fraction capped at one is the tick check against the charge time
57 DyeColor.getMixedColor missing port crates/horizon-client/src/game/creatures.rs::Game::dye_mix player: a baby sheep, wolf or cat from parents of two dye colours gets a mixed colour; Horizon has no breeding dye mix (only sheep_color for spawns) the colour of the crafting recipe that mixes the two dyes, else one of the two picked at random (`next_boolean`); `creatures.rs:3153`, `:3176` and `:3222` give the offspring of a sheep, wolf or cat its colour with it
58 DyeColor.findColorMixInRecipes missing port crates/horizon-client/src/game/creatures.rs::Game::dye_color_mix_in_recipes player: part of the same breeding dye mix; the crafting recipe lookup for two dyes is not done for offspring the two dyes laid out as a 2x2 crafting grid through `Recipes::find`, and the result read back as a dye colour by its `_dye` suffix
59 FireworkRocketItem.createDispenseConfig port crates/horizon-client/src/game/dispenser.rs::Game::dispense_projectile rocket config: offset 0.5000099, uncertainty 1, power 0.5, event 1004
60 FireworkRocketItem.getEntityJustOutsideOfBlockPos port crates/horizon-client/src/game/dispenser.rs::Game::dispense_position the position just outside the dispenser on the facing side
61 HangingEntityItem.mayPlace port crates/horizon-client/src/game/paintings.rs::Game::place_painting paintings take no vertical face; an item frame (ItemFrameItem.mayPlace) may go on one and only needs the build height, which place_item_frame checks
84 Item.Properties.nautilusArmor skip:data registration builder
85 Item.Properties.requiredFeatures skip:data registration builder
86 Item.Properties.delayedHolderComponent skip:data registration builder
87 Item.Properties.loweredMobVisibility missing unsure: the mob_visibility component is not read by Horizon's mob targeting the `mob_visibility` component is parsed (it is in `item_model.rs`'s component table and the protocol's item hash), but no mob targeting reads it
88 Item.Properties.finalizeInitializer skip:data registration builder
89 Item.TooltipContext.of skip:java tooltip context factory; Horizon passes the level and player to the tooltip functions
90 ItemStack.validateStrict skip:java codec-side validation of components and stack size
97 ItemStack.nextDamageWillBreak inline crates/horizon-client/src/game/trident.rs::Game::use_trident gap: minor: only the trident's check is ported here
98 ItemStack.processDurabilityChange port crates/horizon-client/src/game/enchant_effects.rs::Game::durability_loss the Unbreaking-adjusted loss
99 ItemStack.applyDamage port crates/horizon-client/src/game.rs::Game::damage_held gap: minor: no item durability advancement trigger fires
100 ItemStack.hurtAndConvertOnBreak missing unsure: a carrot or fungus on a stick that breaks should become a fishing rod; no conversion was found nothing converts a broken carrot on a stick or fungus on a stick into a fishing rod
101 ItemStack.hurtEnemy port crates/horizon-client/src/game/mace.rs::Game::weapon_wear the per-hit durability loss of a weapon
102 ItemStack.interactLivingEntity port crates/horizon-client/src/game.rs::Game::interact_entity equip-on-interact is in the mob paths (nautili.rs, ghasts.rs)
103 ItemStack.copy skip:java value copy
111 ItemStack.onCraftedBy inline crates/horizon-client/src/game.rs::Game::click_inventory gap: minor: the crafted statistic is added only on the inventory grid craft path here
112 ItemStack.set skip:other component write; Horizon stores component data in typed fields
113 ItemStack.update skip:java functional update helper
114 ItemStack.applyComponentsAndValidate missing unsure: a validated component patch has no single Horizon counterpart; /item modify is a separate path a validated component patch has no single counterpart: `/item modify` goes through its own path
115 ItemStack.getStyledHoverName port crates/horizon-client/src/game/stations.rs::Game::stack_hover_name gap: minor: plain text; the rarity colour and italic style are not applied here
116 ItemStack.addToTooltip port crates/horizon-client/src/item_tooltip.rs::lines
117 ItemStack.addUnitComponentToTooltip inline crates/horizon-client/src/game/stations.rs::Game::stack_tooltip_lines unit lines such as unbreakable, gated by the tooltip display
118 ItemStack.canPlaceOnBlockInAdventureMode port crates/horizon-client/src/game/game_mode.rs::Game::can_place_on_in_adventure the can_place_on predicates gate a placement in adventure mode
119 ItemStack.canBreakBlockInAdventureMode missing port crates/horizon-client/src/game/game_mode.rs::Game::adventure_predicate player: in adventure mode a block can be broken only when the held item's can_break list allows it; Horizon does not read that component the main-hand stack's `can_break` predicate against the block at the position (one predicate or a list, block ids or tags, exact or ranged state properties; a predicate with `nbt` or `components` never matches), read by `game_mode.rs:200`; the same fn answers `can_place_on`
120 ItemStack.getDamageSource missing unsure: a stack's damage_type component is not read when a weapon hits; no Horizon reader found a stack's `damage_type` component is not read when a weapon hits: the attack builds its damage from the mob's own sources (`mobs::Damage`), and `damage_type` is only used for the enchantment damage tags
121 ItemStackTemplate.fromNonEmptyStack port crates/horizon-items/src/item_data.rs::template_tag stack to template tag
122 ItemStackTemplate.withCount skip:java record copy with a new count
123 ItemStackTemplate.apply port crates/horizon-assets/src/item_model.rs::stack_from_template
124 ItemUtils.onContainerDestroyed missing unsure: destroyed container items do not spill their contents; see BlockItem.onDestroyed the shared spill of a destroyed container item has no counterpart; see `BlockItem.onDestroyed`
125 Items.registerBlock skip:data item registry bootstrap
126 Items.registerItem skip:data item registry bootstrap
127 JukeboxSong.hasFinished port crates/horizon-items/src/items.rs::Song::finished
128 JukeboxSongPlayer.setSongWithoutPlaying missing inline crates/horizon-client/src/save.rs::decode_block_entity unsure: no load path restores a jukebox's song start tick the load sets the record and `ticks_since_song_started` straight into the `Jukebox` block entity, which is the whole of `setSongWithoutPlaying`; the song carries on from that tick in `tick_jukebox`
129 JukeboxSongPlayer.play port crates/horizon-client/src/game/jukebox.rs::Game::start_song the song starts at the jukebox with its sound
130 JukeboxSongPlayer.shouldEmitJukeboxPlayingEvent inline crates/horizon-client/src/game/jukebox.rs::Game::tick_jukebox
131 JukeboxSongPlayer.spawnMusicParticles inline crates/horizon-client/src/game/jukebox.rs::Game::tick_jukebox the note particle is emitted in tick_jukebox
138 MapItem.getCorrectStateForFluidBlock inline crates/horizon-client/src/game/maps.rs::Game::update_map the map colour pass
139 MapItem.inventoryTick port crates/horizon-client/src/game/maps.rs::Game::tick_maps
140 MapItem.lockMap port crates/horizon-client/src/menu.rs::Menu::map_crafted the lock branch of the post processing
141 MapItem.useOn missing port crates/horizon-client/src/game/maps.rs::Game::use_map_on_banner player: using a map on a banner block should toggle that banner's marker on the map; Horizon has no banner toggle a map used on a block in the `minecraft:banners` tag toggles that banner's marker (`MapData::toggle_banner`); a failed toggle ends the use without a swing, and `game/remote.rs` predicts the same for its own map
142 MobBucketItem.emptyContents port crates/horizon-world/src/level/buckets.rs::Sim::bucket_empty the MobWater and Nothing contents
143 PotionItem.getDefaultInstance skip:data default stack data
144 PotionItem.useOn port crates/horizon-client/src/game/potions.rs::Game::use_water_bottle_on_block a water bottle poured on #convertible_to_mud makes mud and empties the bottle
+9 -9
View File
@@ -68,7 +68,7 @@ ItemContainerContents.itemCopies skip:other no callers in the vanilla source
ItemContainerContents.asMutable skip:other no callers in the vanilla source
ItemContainerContents.equals skip:java value equality; Horizon compares the item data directly
KineticWeapon.makeLocalHitSound port crates/horizon-client/src/game/spear.rs::Game::kinetic_hit_feedback the local hit sound, at most once per ten ticks per entity
KineticWeapon.computeDamageUseDuration missing unsure: the use duration (delay plus the damage window) is not computed in Horizon's use_spear or spear_tick; the windows are checked one by one
KineticWeapon.computeDamageUseDuration port crates/horizon-client/src/spear.rs::Kinetic::damage_use_duration the delay plus the damage window's duration, which `SpearUseGoal` and `SpearAttack` read as the spear's use duration
KineticWeapon.Condition.test port crates/horizon-client/src/spear.rs::Condition::test tick, attacker speed and relative speed against the scaled minimums
PiercingWeapon.makeHitSound port crates/horizon-client/src/game/spear.rs::Game::jab gap: minor: the hit sound plays at this player's feet only; vanilla plays it for everyone nearby
ResolvableProfile.createUnresolved port crates/horizon-client-render/src/skins.rs::parse_profile keeps a name or id as the unresolved key, resolved later
@@ -97,7 +97,7 @@ Ingredient.testOptionalIngredient port crates/horizon-client/src/crafting.rs::In
Ingredient.equals skip:java value equality
Ingredient.of port crates/horizon-client/src/crafting.rs::Ingredient::of ingredient from item ids
Ingredient.displayForSingleItem skip:other no callers in the vanilla source
Ingredient.getSingleItem missing unsure: returns the one item of a single-item ingredient; no Horizon function found in this pass
Ingredient.getSingleItem skip:data only `BrewingRecipeBuilder.defaultId` (datagen) asks for it; the brewing recipes Horizon reads come from the JSON tables
PlacementInfo.create port crates/horizon-client/src/crafting.rs::Placement::of placement for one ingredient, none when it is empty
PotionIngredient.test port crates/horizon-client-render/src/brewing.rs::PotionIngredient::test item id and the potion filter
PotionIngredient.of port crates/horizon-client-render/src/brewing.rs::PotionIngredient::parse built from the recipe JSON, with the potion filter
@@ -116,24 +116,24 @@ ShapedRecipePattern.unpack port crates/horizon-client/src/crafting.rs::Recipes::
ShapedRecipePattern.firstNonEmpty port crates/horizon-client/src/crafting.rs::shrink trims empty rows and columns at both ends
ShapedRecipePattern.lastNonEmpty port crates/horizon-client/src/crafting.rs::shrink trims empty rows and columns at both ends
ShapedRecipePattern.matches port crates/horizon-client/src/crafting.rs::Recipes::assemble_if_matches shaped arm: exact size, cell tests, mirrored match
SmithingTransformRecipe.display missing unsure: the recipe book slot displays for a smithing transform are not built in Horizon's recipe book; the smithing table uses StationRecipes::smithing_result
SmithingTrimRecipe.display missing unsure: the recipe book slot displays for a smithing trim are not built in Horizon's recipe book; the smithing table uses StationRecipes::smithing_result
SmithingTransformRecipe.display missing the book's entries are built from shaped, shapeless, furnace and special recipes only (Book::build), so a smithing transform gets its display count (1) and its station in RecipeIndex and no slot displays
SmithingTrimRecipe.display missing same as SmithingTransformRecipe.display: the table's own screens come from StationRecipes::smithing_result, the book builds no smithing entry
TransmuteRecipe.computeResultSize port crates/horizon-client/src/crafting.rs::Special::assemble result count plus the material count when it is added
TransmuteRecipe.matches port crates/horizon-client/src/crafting.rs::Special::matches Transmute arm: one input and the material list
TransmuteRecipe.assemble port crates/horizon-client/src/crafting.rs::Special::assemble Transmute arm: result with the input's components
TransmuteRecipe.resultDisplay port crates/horizon-client/src/recipe_book.rs::RecipeIndex::result the recipe's result id
TransmuteRecipe.createPlacementInfo port crates/horizon-client/src/crafting.rs::Recipes::placement the placement of the input and the materials
RecipeDisplay.isEnabled missing minor: no feature flag check, so a display for a disabled feature still shows
RecipeDisplay.isEnabled missing no feature flag check: RecipeIndex::load and Book::build skip the level's enabled_features, so a display of a disabled feature still shows
SlotDisplay.applyDemoTransformation port crates/horizon-client/src/stations.rs::StationRecipes::trim_demo gap: minor: only the trim demo draws (up to 256 tries); the dyed demo is not modelled
SlotDisplay.AnyFuel.resolve port crates/horizon-client/src/recipe_book.rs::ghost every item with a cooking fuel component, grouped by component value in registry order
SlotDisplay.Composite.resolve missing unsure: no composite display list found in Horizon's recipe book
SlotDisplay.Composite.resolve missing the synced display parses SlotDisplay::Composite (horizon-protocol/src/packets/recipes.rs) but nothing resolves it into stacks; Book::items_of reads an Ingredient's own item list
SlotDisplay.DyedSlotDemo.resolve port crates/horizon-client/src/crafting.rs::book_displays the targets cycle fastest and the list stops at 16 stacks
SlotDisplay.ItemSlotDisplay.resolve port crates/horizon-client/src/recipe_book.rs::Book::items_of the item of a single-item slot
SlotDisplay.ItemStackSlotDisplay.resolve port crates/horizon-client/src/recipe_book.rs::Book::items_of gap: minor: keeps item ids only; the stack's components are not carried into the display
SlotDisplay.OnlyWithComponent.resolve missing unsure: no display that filters by an item component found in Horizon's recipe book
SlotDisplay.OnlyWithComponent.resolve missing the book's displays hold item ids, not stacks (Book::items_of), so there is no component for a predicate to test
SlotDisplay.SmithingTrimDemoSlotDisplay.resolve port crates/horizon-client/src/stations.rs::StationRecipes::trim_demo a random base trimmed by a random material
SlotDisplay.WithAnyPotion.resolve port crates/horizon-client/src/crafting.rs::book_displays every potion resolved onto the source item
SlotDisplay.WithRemainder.resolve missing unsure: no remainder display found in Horizon's recipe book; remainders exist in Recipes::remainders for play
SlotDisplay.WithRemainder.isEnabled missing minor: no feature flag check for the remainder display
SlotDisplay.WithRemainder.resolve missing remainders are kept for play (Recipes::remainders), and the book's displays never apply them
SlotDisplay.WithRemainder.isEnabled missing no feature flag check: the recipe load and the book never read the level's enabled_features
BlockPlaceContext.at port crates/horizon-world/src/level/place.rs::Sim::placement_at the placement position and face from the clicked block
UseOnContext.isSecondaryUseActive port crates/horizon-client/src/game.rs::Game::use_hand the shift key sets the context's sneaking flag
1 SmithingTemplateItem.createArmorTrimTemplate port crates/horizon-client/src/game/stations.rs::smithing_slot_icons the empty template slot cycles the armour trim and netherite upgrade template icons, the base slot the helmet to boots and the additions slot the ingot to amethyst shard, as CyclingSlotBackground (30 ticks, a 4 tick fade)
68 ItemContainerContents.asMutable skip:other no callers in the vanilla source
69 ItemContainerContents.equals skip:java value equality; Horizon compares the item data directly
70 KineticWeapon.makeLocalHitSound port crates/horizon-client/src/game/spear.rs::Game::kinetic_hit_feedback the local hit sound, at most once per ten ticks per entity
71 KineticWeapon.computeDamageUseDuration missing port crates/horizon-client/src/spear.rs::Kinetic::damage_use_duration unsure: the use duration (delay plus the damage window) is not computed in Horizon's use_spear or spear_tick; the windows are checked one by one the delay plus the damage window's duration, which `SpearUseGoal` and `SpearAttack` read as the spear's use duration
72 KineticWeapon.Condition.test port crates/horizon-client/src/spear.rs::Condition::test tick, attacker speed and relative speed against the scaled minimums
73 PiercingWeapon.makeHitSound port crates/horizon-client/src/game/spear.rs::Game::jab gap: minor: the hit sound plays at this player's feet only; vanilla plays it for everyone nearby
74 ResolvableProfile.createUnresolved port crates/horizon-client-render/src/skins.rs::parse_profile keeps a name or id as the unresolved key, resolved later
97 Ingredient.equals skip:java value equality
98 Ingredient.of port crates/horizon-client/src/crafting.rs::Ingredient::of ingredient from item ids
99 Ingredient.displayForSingleItem skip:other no callers in the vanilla source
100 Ingredient.getSingleItem missing skip:data unsure: returns the one item of a single-item ingredient; no Horizon function found in this pass only `BrewingRecipeBuilder.defaultId` (datagen) asks for it; the brewing recipes Horizon reads come from the JSON tables
101 PlacementInfo.create port crates/horizon-client/src/crafting.rs::Placement::of placement for one ingredient, none when it is empty
102 PotionIngredient.test port crates/horizon-client-render/src/brewing.rs::PotionIngredient::test item id and the potion filter
103 PotionIngredient.of port crates/horizon-client-render/src/brewing.rs::PotionIngredient::parse built from the recipe JSON, with the potion filter
116 ShapedRecipePattern.firstNonEmpty port crates/horizon-client/src/crafting.rs::shrink trims empty rows and columns at both ends
117 ShapedRecipePattern.lastNonEmpty port crates/horizon-client/src/crafting.rs::shrink trims empty rows and columns at both ends
118 ShapedRecipePattern.matches port crates/horizon-client/src/crafting.rs::Recipes::assemble_if_matches shaped arm: exact size, cell tests, mirrored match
119 SmithingTransformRecipe.display missing unsure: the recipe book slot displays for a smithing transform are not built in Horizon's recipe book; the smithing table uses StationRecipes::smithing_result the book's entries are built from shaped, shapeless, furnace and special recipes only (Book::build), so a smithing transform gets its display count (1) and its station in RecipeIndex and no slot displays
120 SmithingTrimRecipe.display missing unsure: the recipe book slot displays for a smithing trim are not built in Horizon's recipe book; the smithing table uses StationRecipes::smithing_result same as SmithingTransformRecipe.display: the table's own screens come from StationRecipes::smithing_result, the book builds no smithing entry
121 TransmuteRecipe.computeResultSize port crates/horizon-client/src/crafting.rs::Special::assemble result count plus the material count when it is added
122 TransmuteRecipe.matches port crates/horizon-client/src/crafting.rs::Special::matches Transmute arm: one input and the material list
123 TransmuteRecipe.assemble port crates/horizon-client/src/crafting.rs::Special::assemble Transmute arm: result with the input's components
124 TransmuteRecipe.resultDisplay port crates/horizon-client/src/recipe_book.rs::RecipeIndex::result the recipe's result id
125 TransmuteRecipe.createPlacementInfo port crates/horizon-client/src/crafting.rs::Recipes::placement the placement of the input and the materials
126 RecipeDisplay.isEnabled missing minor: no feature flag check, so a display for a disabled feature still shows no feature flag check: RecipeIndex::load and Book::build skip the level's enabled_features, so a display of a disabled feature still shows
127 SlotDisplay.applyDemoTransformation port crates/horizon-client/src/stations.rs::StationRecipes::trim_demo gap: minor: only the trim demo draws (up to 256 tries); the dyed demo is not modelled
128 SlotDisplay.AnyFuel.resolve port crates/horizon-client/src/recipe_book.rs::ghost every item with a cooking fuel component, grouped by component value in registry order
129 SlotDisplay.Composite.resolve missing unsure: no composite display list found in Horizon's recipe book the synced display parses SlotDisplay::Composite (horizon-protocol/src/packets/recipes.rs) but nothing resolves it into stacks; Book::items_of reads an Ingredient's own item list
130 SlotDisplay.DyedSlotDemo.resolve port crates/horizon-client/src/crafting.rs::book_displays the targets cycle fastest and the list stops at 16 stacks
131 SlotDisplay.ItemSlotDisplay.resolve port crates/horizon-client/src/recipe_book.rs::Book::items_of the item of a single-item slot
132 SlotDisplay.ItemStackSlotDisplay.resolve port crates/horizon-client/src/recipe_book.rs::Book::items_of gap: minor: keeps item ids only; the stack's components are not carried into the display
133 SlotDisplay.OnlyWithComponent.resolve missing unsure: no display that filters by an item component found in Horizon's recipe book the book's displays hold item ids, not stacks (Book::items_of), so there is no component for a predicate to test
134 SlotDisplay.SmithingTrimDemoSlotDisplay.resolve port crates/horizon-client/src/stations.rs::StationRecipes::trim_demo a random base trimmed by a random material
135 SlotDisplay.WithAnyPotion.resolve port crates/horizon-client/src/crafting.rs::book_displays every potion resolved onto the source item
136 SlotDisplay.WithRemainder.resolve missing unsure: no remainder display found in Horizon's recipe book; remainders exist in Recipes::remainders for play remainders are kept for play (Recipes::remainders), and the book's displays never apply them
137 SlotDisplay.WithRemainder.isEnabled missing minor: no feature flag check for the remainder display no feature flag check: the recipe load and the book never read the level's enabled_features
138 BlockPlaceContext.at port crates/horizon-world/src/level/place.rs::Sim::placement_at the placement position and face from the clicked block
139 UseOnContext.isSecondaryUseActive port crates/horizon-client/src/game.rs::Game::use_hand the shift key sets the context's sneaking flag
+1 -1
View File
@@ -118,7 +118,7 @@ CollisionGetter.noBorderCollision port crates/horizon-client-render/src/collisio
CollisionGetter.getPreMoveCollisions inline crates/horizon-client-render/src/collisions.rs::SceneCollisions::resolve_with_context gap: minor: entity and block shapes are not returned as a list; the resolver takes them together
CollisionGetter.getBlockAndLiquidCollisions port crates/horizon-client-render/src/collisions.rs::no_collision_or_liquid gap: minor: a yes or no test, not the shape list
CollisionGetter.getBlockCollisionsFromContext port crates/horizon-client-render/src/collisions.rs::SceneCollisions::collision_block gap: minor: per-block shape; the box walk is not here
CollisionGetter.clipIncludingBorder missing unsure: no world-border clamp of a ray found in clip_ray
CollisionGetter.clipIncludingBorder port crates/horizon-client/src/projectiles.rs::clip_including_border a ray that starts inside the world border and ends outside it stops on the border at WorldBorder.clampVec3ToBound of that point, facing the nearest direction of travel; the projectile clip in creatures.rs calls it around its block clip
CollisionGetter.collidesWithSuffocatingBlock port crates/horizon-client-render/src/collisions.rs::SceneCollisions::is_suffocating
EntityGetter.getEntitiesOfClass skip:other class filter repeated at each caller over the entity lists; no shared lookup
EntityGetter.getPlayerByUUID skip:other uuid find repeated at each caller (e.g. allays.rs)
1 Enchantment.getSlotItems inline crates/horizon-world/src/loot.rs::LootData::enchantment_level slot filter and item lookup folded into the best-level loop
118 CollisionGetter.getPreMoveCollisions inline crates/horizon-client-render/src/collisions.rs::SceneCollisions::resolve_with_context gap: minor: entity and block shapes are not returned as a list; the resolver takes them together
119 CollisionGetter.getBlockAndLiquidCollisions port crates/horizon-client-render/src/collisions.rs::no_collision_or_liquid gap: minor: a yes or no test, not the shape list
120 CollisionGetter.getBlockCollisionsFromContext port crates/horizon-client-render/src/collisions.rs::SceneCollisions::collision_block gap: minor: per-block shape; the box walk is not here
121 CollisionGetter.clipIncludingBorder missing port crates/horizon-client/src/projectiles.rs::clip_including_border unsure: no world-border clamp of a ray found in clip_ray a ray that starts inside the world border and ends outside it stops on the border at WorldBorder.clampVec3ToBound of that point, facing the nearest direction of travel; the projectile clip in creatures.rs calls it around its block clip
122 CollisionGetter.collidesWithSuffocatingBlock port crates/horizon-client-render/src/collisions.rs::SceneCollisions::is_suffocating
123 EntityGetter.getEntitiesOfClass skip:other class filter repeated at each caller over the entity lists; no shared lookup
124 EntityGetter.getPlayerByUUID skip:other uuid find repeated at each caller (e.g. allays.rs)
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff. Show more