Merge remote-tracking branch 'origin/main' into fix/7078-pearl-chorus-border

This commit is contained in:
Akirabane committed 2026-11-02 12:37:00 +01:00
commit 71b32333ef
25 files changed
+775 -147

No files matched your search

+10 -1
View File
@@ -110,7 +110,16 @@ pub(crate) fn prepare_with_models(
.state(state)
.ok_or("missing gallery registry state")?;
let position = BlockPos::new((index % 4) as i32 * 4 - 7, 4, (index / 4) as i32 * 4 + 14);
let selected = catalog.collect(state, position)?;
// A state no definition binds (a pack's partial blockstate file
// without vanilla's below it) is left out, as the mesher skips it,
// instead of failing the whole pack.
let selected = match catalog.collect(state, position) {
Ok(selected) => selected,
Err(e) => {
log::warn!("Horizon: gallery block {name} has no model: {e}");
continue;
}
};
states.push(json!({"block":actual.name,"properties":actual.properties,"state_id":state.0,
"position":[position.x,position.y,position.z],
"models":selected.iter().map(|(v,_)| json!({"model":v.model,"rotations":v.rotations,"uv_lock":v.uv_lock})).collect::<Vec<_>>()}));
@@ -105,6 +105,35 @@ fn bake_resolved(
cube,
})
}
/// The definitions a blockstate file holds, bottom first: the layers of a
/// file the pack overlay stacked (`packs::BLOCKSTATE_LAYERS`), else the
/// file itself.
fn definition_layers(text: &str) -> std::result::Result<Vec<String>, String> {
if !text.contains(crate::packs::BLOCKSTATE_LAYERS) {
return Ok(vec![text.to_owned()]);
}
let value: Value = serde_json::from_str(text).map_err(|e| e.to_string())?;
match value.get(crate::packs::BLOCKSTATE_LAYERS) {
Some(Value::Array(layers)) => Ok(layers.iter().map(Value::to_string).collect()),
_ => Ok(vec![text.to_owned()]),
}
}
/// Each state's model from definitions bound bottom first: the topmost one
/// that binds the state, as (its layer, its selection). A higher pack's
/// definition replaces only the states it binds (`putAll` in
/// `loadBlockStateDefinitionStack`).
fn topmost(layers: Vec<Vec<Option<SelectedModels>>>) -> Vec<Option<(usize, SelectedModels)>> {
let mut picked: Vec<Option<(usize, SelectedModels)>> = Vec::new();
for (layer, bound) in layers.into_iter().enumerate() {
picked.resize(picked.len().max(bound.len()), None);
for (slot, selected) in picked.iter_mut().zip(bound) {
if let Some(selected) = selected {
*slot = Some((layer, selected));
}
}
}
picked
}
impl StateModels {
pub fn load(
root: &Path,
@@ -194,21 +223,17 @@ impl StateModels {
.collect::<Result<_>>()?;
let possible: Vec<Properties> = serde_json::from_value(facts["states"].clone())?;
// Vanilla's BlockStateModelLoader: a definition that does not
// read or bind is logged and its block gets no model; the other
// blocks still load.
let loaded = horizon_core::prepared_files::read_to_string(
// read or bind is logged and left out; with none left the block
// gets no model, and the other blocks still load. A file several
// packs provide binds each pack's definition on its own, bottom
// first (`loadBlockStateDefinitionStack`).
let layers = match horizon_core::prepared_files::read_to_string(
&root.join(format!("assets/{namespace}/blockstates/{path}.json")),
)
.map_err(|e| e.to_string())
.and_then(|text| BlockStateDefinition::from_json(&text).map_err(|e| e.to_string()))
.and_then(|definition| {
let bound = definition
.bind(&schema, &possible)
.map_err(|e| e.to_string())?;
Ok((definition, bound))
});
let (definition, bound) = match loaded {
Ok(loaded) => loaded,
.and_then(|text| definition_layers(&text))
{
Ok(layers) => layers,
Err(e) => {
log::warn!(
"Horizon: blockstate definition of {namespace}:{path} not loaded: {e}"
@@ -216,6 +241,26 @@ impl StateModels {
continue;
}
};
let mut loaded = Vec::with_capacity(layers.len());
for text in &layers {
let layer = BlockStateDefinition::from_json(text)
.map_err(|e| e.to_string())
.and_then(|definition| {
let bound = definition
.bind(&schema, &possible)
.map_err(|e| e.to_string())?;
Ok((definition, bound))
});
match layer {
Ok(layer) => loaded.push(layer),
Err(e) => log::warn!(
"Horizon: blockstate definition of {namespace}:{path} not loaded: {e}"
),
}
}
if loaded.is_empty() {
continue;
}
let native_order: Vec<_> = by_name
.get(name.as_str())
.map(Vec::as_slice)
@@ -233,32 +278,36 @@ impl StateModels {
"model state definitions disagree with the imported block registry".into(),
);
}
for ((id, _), selected) in native_order.into_iter().zip(bound) {
if let Some(selected) = selected {
states[id.0 as usize] = Some((choices.len(), selected));
let (definitions, bound): (Vec<_>, Vec<_>) = loaded.into_iter().unzip();
let first = choices.len();
for ((id, _), picked) in native_order.into_iter().zip(topmost(bound)) {
if let Some((layer, selected)) = picked {
states[id.0 as usize] = Some((first + layer, selected));
}
}
for choice in definition
.variants
.iter()
.flatten()
.map(|(_, choice)| choice)
.chain(
definition
.multipart
.iter()
.flatten()
.map(|selector| &selector.variant),
)
{
match choice {
VariantSet::Single(v) => {
variants.insert(key(v));
for definition in definitions {
for choice in definition
.variants
.iter()
.flatten()
.map(|(_, choice)| choice)
.chain(
definition
.multipart
.iter()
.flatten()
.map(|selector| &selector.variant),
)
{
match choice {
VariantSet::Single(v) => {
variants.insert(key(v));
}
VariantSet::Weighted(v) => variants.extend(v.iter().map(|(v, _)| key(v))),
}
VariantSet::Weighted(v) => variants.extend(v.iter().map(|(v, _)| key(v))),
}
choices.push(CompiledChoices::new(&definition)?);
}
choices.push(CompiledChoices::new(&definition)?);
}
let names: Vec<String> = variants.iter().map(|(name, _, _)| name.clone()).collect();
let library = match models.block_library(root, names.clone()) {
@@ -428,6 +477,44 @@ mod tests {
use super::*;
use crate::model_library::ParsedModels;
/// #7013: a pack's blockstate file that defines only some states (here
/// a fence gate's `powered=true` ones) keeps vanilla's model for the
/// rest, as vanilla binds each pack's definition and lets a higher one
/// replace only what it binds.
#[test]
fn a_partial_pack_blockstate_keeps_vanilla_for_the_other_states() {
let schema: PropertySchema = BTreeMap::from([(
"powered".to_owned(),
PropertyValues::Named(BTreeSet::from(["false".to_owned(), "true".to_owned()])),
)]);
let states: Vec<Properties> = ["false", "true"]
.iter()
.map(|p| BTreeMap::from([("powered".to_owned(), (*p).to_owned())]))
.collect();
let stacked = serde_json::json!({ crate::packs::BLOCKSTATE_LAYERS: [
{"variants":{"":{"model":"minecraft:block/oak_fence_gate"}}},
{"variants":{"powered=true":{"model":"minecraft:block/stone"}}}
]})
.to_string();
let layers = definition_layers(&stacked).unwrap();
assert_eq!(layers.len(), 2);
let bound = layers
.iter()
.map(|text| {
BlockStateDefinition::from_json(text)
.unwrap()
.bind(&schema, &states)
.unwrap()
})
.collect();
let picked = topmost(bound);
assert_eq!(picked[0], Some((0, SelectedModels::Variant(0))));
assert_eq!(picked[1], Some((1, SelectedModels::Variant(0))));
// A file that isn't stacked is one definition.
let plain = r#"{"variants":{"":{"model":"minecraft:block/stone"}}}"#;
assert_eq!(definition_layers(plain).unwrap(), vec![plain.to_owned()]);
}
struct Fixture(std::path::PathBuf);
impl Fixture {
fn write(&self, name: &str, json: &str) {
+45 -1
View File
@@ -841,7 +841,14 @@ enum Merge {
Font,
/// `SpriteSourceList.load`: sources from the bottom pack up.
Atlas,
/// `BlockStateModelLoader.loadBlockStateDefinitionStack`: every pack's
/// definition binds on its own, and a higher one replaces only the states
/// it binds. Kept as its layers ([`BLOCKSTATE_LAYERS`]) for the catalog.
BlockStates,
}
/// The key of a stacked blockstate file: its definitions, bottom first, each
/// as the pack wrote it.
pub const BLOCKSTATE_LAYERS: &str = "horizon:layers";
fn merge_kind(name: &str) -> Option<Merge> {
let (_, rest) = name.strip_prefix("assets/")?.split_once('/')?;
if rest == "sounds.json" {
@@ -852,6 +859,7 @@ fn merge_kind(name: &str) -> Option<Merge> {
"lang" => Some(Merge::Lang),
"font" => Some(Merge::Font),
"atlases" => Some(Merge::Atlas),
"blockstates" => Some(Merge::BlockStates),
_ => None,
}
}
@@ -859,7 +867,7 @@ fn merge_kind(name: &str) -> Option<Merge> {
/// does. Layers that do not parse are skipped, as vanilla logs and skips them.
fn merge(kind: Merge, layers: &[Vec<u8>]) -> Option<Vec<u8>> {
use serde_json::{Map, Value};
let parsed: Vec<Map<String, Value>> = layers
let mut parsed: Vec<Map<String, Value>> = layers
.iter()
// SoundManager skips a sounds.json Gson rejects, whole.
.filter(|b| kind != Merge::Sounds || crate::sound_events::is_valid_file(b))
@@ -902,6 +910,12 @@ fn merge(kind: Merge, layers: &[Vec<u8>]) -> Option<Vec<u8>> {
}
Merge::Font => list("providers", &mut parsed.iter().rev()),
Merge::Atlas => list("sources", &mut parsed.iter()),
// One definition is the file as it is.
Merge::BlockStates if parsed.len() < 2 => Value::Object(parsed.pop()?),
Merge::BlockStates => Value::Object(Map::from_iter([(
BLOCKSTATE_LAYERS.to_owned(),
Value::Array(parsed.into_iter().map(Value::Object).collect()),
)])),
};
serde_json::to_vec(&out).ok()
}
@@ -1405,6 +1419,36 @@ mod tests {
&[json(r#"{"sources":[1]}"#), json(r#"{"sources":[2]}"#)],
);
assert_eq!(parse(atlas.unwrap()), serde_json::json!({"sources":[1,2]}));
// BlockStateModelLoader: every definition kept, bottom first, for the
// catalog to bind one by one; one that does not parse is skipped.
let gate = merge(
Merge::BlockStates,
&[
json(r#"{"variants":{"":{"model":"v"}}}"#),
b"not json".to_vec(),
json(r#"{"variants":{"powered=true":{"model":"p"}}}"#),
],
);
assert_eq!(
parse(gate.unwrap()),
serde_json::json!({BLOCKSTATE_LAYERS:[
{"variants":{"":{"model":"v"}}},
{"variants":{"powered=true":{"model":"p"}}}
]})
);
// A single definition stays the file as it is.
let alone = merge(
Merge::BlockStates,
&[json(r#"{"variants":{"":{"model":"p"}}}"#)],
);
assert_eq!(
parse(alone.unwrap()),
serde_json::json!({"variants":{"":{"model":"p"}}})
);
assert_eq!(
merge_kind("assets/minecraft/blockstates/oak_fence_gate.json"),
Some(Merge::BlockStates)
);
assert_eq!(
merge_kind("assets/minecraft/lang/en_us.json"),
Some(Merge::Lang)
@@ -12,7 +12,8 @@ use std::{collections::BTreeSet, fs, io, path::Path, path::PathBuf};
// Version 3 could omit untouched vanilla directories on Windows (#1649, #1688).
// Version 5 read every vanilla file to stamp it on Windows.
// Version 6 stamps vanilla's files by their metadata on Windows too.
const VERSION: u32 = 6;
// Version 7 stacks blockstate files (every pack's definition, bottom first).
const VERSION: u32 = 7;
#[derive(Default, Serialize, Deserialize)]
pub(super) struct Dependencies {
+5
View File
@@ -7309,6 +7309,11 @@ impl Game<'_> {
horizon_client::spans::time("tick_item_frames", || self.tick_item_frames());
horizon_client::spans::time("tick_armor_stands", || self.tick_armor_stands())?;
horizon_client::spans::time("tick_cushions", || self.tick_cushions());
// ServerLevel.tick: the arrivals of this server tick are in the level
// (`addDuringTeleport`), and each vehicle handles its portal as its
// own tick opens.
self.land_portal_arrivals();
self.handle_vehicle_portals();
horizon_client::spans::time("tick_boats", || self.tick_boats());
horizon_client::spans::time("tick_carts", || self.tick_carts());
self.tick_vehicle_watch();
+11
View File
@@ -196,6 +196,11 @@ pub(super) trait Entity: EntityRules {
fn can_use_portal(&self, _game: &Game<'_>) -> bool {
true
}
/// A boat or minecart: the kinds whose portal pass opens the tick, before
/// they move (`AbstractMinecart.tick` handles its portal first).
fn is_vehicle(&self) -> bool {
false
}
/// Where it stood at the start of its tick, for the kinds that store it
/// (None: `portals.before` has it).
fn portal_from(&self) -> Option<Vec3> {
@@ -546,6 +551,9 @@ impl Entity for ExperienceOrb {
impl Entity for Boat {
stored_motion!();
fn is_vehicle(&self) -> bool {
true
}
/// `Boat.getEyeY`: the top of the 0.5625 box.
fn fluid_eye_y(&self) -> f64 {
self.position.y + super::boats::HEIGHT
@@ -579,6 +587,9 @@ impl Entity for Boat {
impl Entity for Minecart {
stored_motion!();
fn is_vehicle(&self) -> bool {
true
}
fn portal_size(&self) -> Option<(f32, f32)> {
Some((
super::minecarts::WIDTH as f32,
+17
View File
@@ -424,6 +424,7 @@ impl Game<'_> {
let observe_level =
running.is_some() && (self.serving.is_some() || self.options.dedicated.is_some());
let dims = self.levels_to_tick();
let order = dims.clone();
let n = dims.len();
let mut result = Ok(());
for (k, dim) in dims.into_iter().enumerate() {
@@ -466,6 +467,22 @@ impl Game<'_> {
self.native_level_tick_post(dim.id());
}
self.publish_server_debug(dim);
// An entity that crossed into a level that already ticked this
// server tick is in it when the tick ends (`addDuringTeleport`).
if running == Some(true) {
let mut moved = false;
for &earlier in &order[..k] {
if !self.arrival_chunks(earlier).is_empty()
&& self.enter_level(None, earlier).is_ok()
{
self.land_portal_arrivals();
moved = true;
}
}
if moved {
self.enter_level(None, dim)?;
}
}
}
self.level_pass = Pass::default();
if result.is_err() {
+130 -36
View File
@@ -140,6 +140,38 @@ pub(super) fn projectile_box(p: &horizon_client::projectiles::Projectile) -> (f3
(size, size)
}
}
/// `Entity.calculatePassengerTransition`: the riders under `tag`'s
/// `Passengers`, and theirs, keep their place on the vehicle: they move by
/// what it moved and turn by what it turned.
fn move_passengers(tag: &mut Tag, by: Vec3, turn: f32) {
let Tag::Compound(c) = tag else {
return;
};
let Some(Tag::List(riders)) = c.get_mut(&"Passengers".into()) else {
return;
};
for rider in riders {
if let Some(at) = horizon_client::entity_nbt::position(rider)
&& let Tag::Compound(r) = rider
{
r.insert(
"Pos".into(),
doubles([at.x + by.x, at.y + by.y, at.z + by.z]),
);
let [yaw, pitch] = horizon_client::entity_nbt::rotation(rider);
if let Tag::Compound(r) = rider {
r.insert(
"Rotation".into(),
Tag::List(vec![
Tag::Float((yaw + turn).to_bits()),
Tag::Float(pitch.to_bits()),
]),
);
}
}
move_passengers(rider, by, turn);
}
}
fn doubles(values: [f64; 3]) -> Tag {
Tag::List(values.iter().map(|v| Tag::Double(v.to_bits())).collect())
}
@@ -557,6 +589,15 @@ impl Game<'_> {
/// at the start of their tick: an item or mob that stayed in a portal
/// goes through (transition time 0) and waits for its dimension.
pub(super) fn handle_entity_portals(&mut self) {
self.handle_portals(false);
}
/// `Entity.handlePortal` of the boats and minecarts, which `tick_boats`
/// and `tick_carts` move: it runs before them, from the position they
/// ended the last tick at, as the start of a vehicle's own tick does.
pub(super) fn handle_vehicle_portals(&mut self) {
self.handle_portals(true);
}
fn handle_portals(&mut self, vehicles: bool) {
if self.remote.is_some() {
return;
}
@@ -564,13 +605,17 @@ impl Game<'_> {
let mut leaving: Vec<(u32, BlockPos)> = Vec::new();
// Positions before the entities move, and (only for an entity in a
// portal, or cooling down from one, which has a state here) whether it
// can use one: asking every entity on every tick is not worth it.
// can use one: asking every entity on every tick is not worth it. The
// vehicles' pass opens the tick and the other kinds' pass follows, so
// each walk takes only its own group.
let mut before = std::mem::take(&mut self.portals.before);
before.clear();
if vehicles {
before.clear();
}
let mut ids: Vec<(u32, bool)> = Vec::new();
let tracked = &self.portals.entities;
for_each_entity!(entity_lists_of!(self), entity => {
if entity.portal_size().is_some() {
if entity.portal_size().is_some() && entity.is_vehicle() == vehicles {
if entity.portal_from().is_none() {
before.insert(entity.id(), entity.position());
}
@@ -581,7 +626,12 @@ impl Game<'_> {
});
self.portals.before = before;
let present: std::collections::HashSet<u32> = ids.iter().map(|(id, _)| *id).collect();
self.portals.entities.retain(|id, _| present.contains(id));
// Each pass keeps the other group's states.
let (boats, carts) = (&self.boats, &self.carts);
self.portals.entities.retain(|id, _| {
let vehicle = boats.iter().any(|b| b.id == *id) || carts.iter().any(|c| c.id == *id);
present.contains(id) || vehicle != vehicles
});
for (id, allowed) in ids {
let Some(state) = self.portals.entities.get_mut(&id) else {
continue;
@@ -650,20 +700,20 @@ impl Game<'_> {
&& (!self.guest_rides(m.id) || self.carries_riders())
&& m.species() != Species::Wither
}
/// `canUsePortal(false)` of a boat or minecart: empty, or ridden by
/// server players only, whom it carries along.
/// `Entity.canUsePortal(false)` for a primed TNT: it carries no mob.
pub(super) fn tnt_can_use_portal(&self, id: u32) -> bool {
// Id 0: not tracked yet, nothing to key its portal state by.
id != 0 && !self.mobs.iter().any(|m| m.extra.mount == Some(id))
}
/// `canUsePortal(false)` of a boat or minecart: it is nobody's passenger;
/// the mobs riding it, and on a server the players, cross with it.
pub(super) fn vehicle_can_use_portal(&self, id: u32) -> bool {
if !self.vehicle_occupied(id) {
return true;
// The window's player is moved by its own trip, not by the vehicle's.
if self.riding == Some(id) {
return false;
}
self.carries_riders()
&& self.riding != Some(id)
&& !self.mobs.iter().any(|m| m.extra.mount == Some(id))
// Mob riders always go along; server players only on a server.
!self.ridden_by_guest(id) || self.carries_riders()
}
/// Whether a vehicle can take the server players riding it through a
/// portal: only a server moves players between its levels. Elsewhere a
@@ -681,27 +731,23 @@ impl Game<'_> {
here.into_iter().map(|(_, id, entry)| (id, entry)).collect()
}
/// The plan of vehicle `id` through the portal at `entry`: from its own
/// position and size (`getPortalDestination(level, vehicle)`).
/// position and size (`getPortalDestination(level, vehicle)`). A boat or
/// minecart, like a mob, lands in the middle of the exit portal's depth
/// (`AbstractBoat` and `AbstractMinecart.getRelativePortalPosition`).
pub(super) fn vehicle_plan(&self, id: u32, entry: BlockPos) -> Option<Plan> {
if let Some(m) = self.mobs.iter().find(|m| m.id == id) {
return Some(self.entity_plan(entry, m.position, m.width(), m.height(), true));
}
if let Some(b) = self.boats.iter().find(|b| b.id == id) {
let (width, height) = (super::boats::WIDTH as f32, super::boats::HEIGHT as f32);
return Some(self.entity_plan(entry, b.position, width, height, false));
return Some(self.entity_plan(entry, b.position, width, height, true));
}
let c = self.carts.iter().find(|c| c.id == id)?;
let (width, height) = (
super::minecarts::WIDTH as f32,
super::minecarts::HEIGHT as f32,
);
Some(self.entity_plan(entry, c.position, width, height, false))
}
/// Whether the player, a server player or a mob rides vehicle `id`.
fn vehicle_occupied(&self, id: u32) -> bool {
self.riding == Some(id)
|| self.ridden_by_guest(id)
|| self.mobs.iter().any(|m| m.extra.mount == Some(id))
Some(self.entity_plan(entry, c.position, width, height, true))
}
/// `NetherPortalBlock.entityInside` and `EndPortalBlock.entityInside`
/// for the items, mobs, empty boats and minecarts, and arrows that moved
@@ -929,18 +975,8 @@ impl Game<'_> {
c.remove(&"leash".into());
}
let mut gone = vec![id];
let mut k = 0;
while k < gone.len() {
let v = gone[k];
gone.extend(
self.mobs
.iter()
.filter(|m| m.extra.mount == Some(v))
.map(|m| m.id),
);
k += 1;
}
self.mobs.retain(|m| !gone.contains(&m.id));
gone.extend(self.remove_mob_riders(id));
self.mobs.retain(|m| m.id != id);
for g in gone {
self.detach_entity(g);
}
@@ -949,14 +985,14 @@ impl Game<'_> {
} else if let Some(i) = self.boats.iter().position(|b| b.id == id) {
let b = &self.boats[i];
let (width, height) = (super::boats::WIDTH as f32, super::boats::HEIGHT as f32);
let plan = self.entity_plan(entry, b.position, width, height, false);
let plan = self.entity_plan(entry, b.position, width, height, true);
if !self.may_enter(plan.dim) {
return;
}
self.release_vehicle_container(id);
let uuid = self.uuid_for(id);
let b = &self.boats[i];
let tag = horizon_client::entity_nbt::boat_to_nbt(
let mut tag = horizon_client::entity_nbt::boat_to_nbt(
&b.entity_type(),
b.position,
b.velocity,
@@ -965,6 +1001,11 @@ impl Game<'_> {
self.persist_raw(id),
b.chest.then_some(&b.items[..]),
);
// The mobs riding it cross with it and mount it again.
self.put_mob_passengers(&mut tag, id);
for rider in self.remove_mob_riders(id) {
self.detach_entity(rider);
}
self.boats.remove(i);
self.detach_entity(id);
self.portals.arrivals.push(Arrival { plan, tag });
@@ -975,13 +1016,27 @@ impl Game<'_> {
super::minecarts::WIDTH as f32,
super::minecarts::HEIGHT as f32,
);
let plan = self.entity_plan(entry, c.position, width, height, false);
let plan = self.entity_plan(entry, c.position, width, height, true);
if !self.may_enter(plan.dim) {
return;
}
self.release_vehicle_container(id);
let uuid = self.uuid_for(id);
let tag = self.carts[i].to_nbt(uuid, self.persist_raw(id));
let mut tag = self.carts[i].to_nbt(uuid, self.persist_raw(id));
// `PositionMoveRotation.of(this)` takes the cart's `getKnownMovement`:
// `OldMinecartBehavior` clamps it to 0.4 either way along x and z.
let m = horizon_client::entity_nbt::motion(&tag);
if let Tag::Compound(c) = &mut tag {
c.insert(
"Motion".into(),
doubles([m.x.clamp(-0.4, 0.4), m.y, m.z.clamp(-0.4, 0.4)]),
);
}
// The mobs riding it cross with it and mount it again.
self.put_mob_passengers(&mut tag, id);
for rider in self.remove_mob_riders(id) {
self.detach_entity(rider);
}
self.carts.remove(i);
self.detach_entity(id);
self.portals.arrivals.push(Arrival { plan, tag });
@@ -1026,6 +1081,26 @@ impl Game<'_> {
self.native_entity_portal(self.dim.id(), id, [entry.x, entry.y, entry.z]);
}
}
/// Removes the mobs riding `vehicle`, and those riding them, from the
/// level: `Entity.teleportCrossDimension` takes the whole tree with its
/// root. Their ids.
fn remove_mob_riders(&mut self, vehicle: u32) -> Vec<u32> {
let mut tree = vec![vehicle];
let mut k = 0;
while k < tree.len() {
let v = tree[k];
tree.extend(
self.mobs
.iter()
.filter(|m| m.extra.mount == Some(v))
.map(|m| m.id),
);
k += 1;
}
tree.remove(0);
self.mobs.retain(|m| !tree.contains(&m.id));
tree
}
/// The chunks of the arrivals still waiting to land in `dim`, for the
/// streaming `extra` areas that light them: vanilla's portal ticket
/// (`Entity.teleport` adds `TicketType.PORTAL` at the destination) keeps
@@ -1038,6 +1113,20 @@ impl Game<'_> {
.map(|a| (a.plan.approximate.x >> 4, a.plan.approximate.z >> 4))
.collect()
}
/// `ServerLevel.addDuringTeleport`: the entities that arrive in the level in
/// place join it now, before its entities tick, so a vehicle that crossed
/// from an earlier level this server tick moves in its new level the same
/// tick, and one that crossed from a later level stands there until the
/// next. Only the portal arrivals join; the chunks' entities wait.
pub(super) fn land_portal_arrivals(&mut self) {
if self.remote.is_some() || self.portals.arrivals.is_empty() {
return;
}
let waiting = std::mem::take(&mut self.world.loaded_entities);
self.resolve_arrivals();
self.spawn_loaded_entities();
self.world.loaded_entities.extend(waiting);
}
/// Entities waiting for the loaded dimension whose exit area is loaded:
/// their exit portal (found or built) or End destination, position,
/// turned yaw and motion, and portal cooldown; they join the world with the chunk entities.
@@ -1059,6 +1148,7 @@ impl Game<'_> {
self.portals.dirty = true;
let motion = horizon_client::entity_nbt::motion(&tag);
let [yaw, pitch] = horizon_client::entity_nbt::rotation(&tag);
let was_yaw = yaw;
let (at, motion, [yaw, pitch]) = if plan.end {
// Relative.DELTA: the motion is kept.
let at = self.end_portal_exit(&plan);
@@ -1075,6 +1165,10 @@ impl Game<'_> {
let (at, rotation) = self.arrival(&plan, exit);
(at, rotate_y(motion, -rotation), [yaw + rotation, pitch])
};
let yaw_turn = yaw - was_yaw;
if let Some(was) = horizon_client::entity_nbt::position(&tag) {
move_passengers(&mut tag, at.subtract(was), yaw_turn);
}
if let Tag::Compound(c) = &mut tag {
c.insert("Pos".into(), doubles([at.x, at.y, at.z]));
c.insert("Motion".into(), doubles([motion.x, motion.y, motion.z]));
+1 -1
View File
@@ -771,7 +771,7 @@ impl Game<'_> {
}
/// The mobs riding entity `id` under `tag`'s `Passengers`, after the
/// passengers it kept from loading.
fn put_mob_passengers(&mut self, tag: &mut Tag, id: u32) {
pub(super) fn put_mob_passengers(&mut self, tag: &mut Tag, id: u32) {
let riders: Vec<usize> = (0..self.mobs.len())
.filter(|&j| self.mobs[j].extra.mount == Some(id) && self.mobs[j].alive())
.collect();
+35
View File
@@ -556,3 +556,38 @@ mod item_tests {
assert!(m.extra.main_hand.is_none(), "the mouth is empty");
}
}
#[cfg(test)]
mod target_tests {
use super::*;
use crate::mobs::world::with_test_world;
use crate::mobs::{Species, sensor_scan_tests::kind};
use horizon_core::geometry::Vec3;
fn dolphin(baby: bool) -> Mob {
let mut kind = kind();
let k = std::sync::Arc::make_mut(&mut kind);
k.id = "minecraft:dolphin".into();
k.species = Species::Dolphin;
let mut m = Mob::new(1, kind, Vec3::ZERO, 0.0);
m.extra.age = if baby { -24000 } else { 0 };
m
}
/// `Dolphin.canAttack`: a baby takes nobody as its target, so a hit on it
/// is not answered; an adult does take the one that hurt it.
#[test]
fn a_baby_dolphin_spares_whatever_hurt_it() {
let ran = with_test_world(&[], |w, _| {
assert!(
dolphin(true).spares_target(w, Who::Mob(7)),
"a baby dolphin fights nobody"
);
assert!(
!dolphin(false).spares_target(w, Who::Mob(7)),
"an adult dolphin takes its attacker"
);
});
let _ = ran;
}
}
+5
View File
@@ -689,6 +689,11 @@ impl Mob {
/// `IronGolem.canAttack`'s refusal of creepers. The callers check that
/// the target is alive and can be seen as an enemy.
pub fn spares_target(&self, w: &MobWorld, who: Who) -> bool {
// Dolphin.canAttack: a baby dolphin attacks nobody, so whatever hurt it
// is never taken as its target.
if self.species() == Species::Dolphin && self.is_baby() {
return true;
}
match who {
Who::Player => self.spares_players(w),
Who::Mob(id) => {
+8 -13
View File
@@ -32,8 +32,7 @@ pub trait Hooks: Send + Sync {
let _ = event;
}
/// The world's phases, read-only; `error_text` carries the engine's
/// own open-failure text, empty otherwise. Server-side; the deprecated
/// in-frame path delivers no lifecycle events. A server loop or final
/// own open-failure text, empty otherwise. Server-side. A server loop or final
/// save failure fires `WorldCloseFailed` with the error text.
fn world_lifecycle(&self, host: &mut dyn Host, event: &mut WorldLifecycleEvent<'_>) {
let _ = host;
@@ -734,8 +733,7 @@ pub trait Hooks: Send + Sync {
/// as on a refused hit, except that a canceled hit on the ender dragon
/// still counts as landed (`EnderDragon.hurt` returns true whatever
/// `reallyHurt` did: the arrow is spent, the melee effects run). It runs
/// on the thread that ticks the world (the server thread; the window's
/// in-frame): state shared with render thread hooks needs synchronising.
/// on the thread that ticks the world (the server thread): state shared with render thread hooks needs synchronising.
/// No `self`: the game is in the middle of the hit. API 50.
///
/// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped.
@@ -1369,12 +1367,10 @@ pub trait Hooks: Send + Sync {
/// 0 runs it out on its first tick, as `native_living_entity_use_item_tick`
/// running it out, unless the item's own is 0 already), or set
/// `canceled` and the use doesn't start. Both sides, as
/// vanilla's: wherever the world runs (the server, single player
/// in-frame, a LAN host) for every player of the world, and on a
/// vanilla's: on the server for every player of the world, and on a
/// server's client for the uses it predicts. Answer the same on both,
/// and change the world only where this `Game` runs the world, not on a
/// server's client: no helper tells the two apart yet (`native_window`
/// is also true in-frame and on an in-frame LAN host). API 90.
/// and change the world only on the server (`native_window` is false
/// there), not on a server's client. API 90.
///
/// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped.
fn living_entity_use_item_start(
@@ -1796,7 +1792,7 @@ pub trait Hooks: Send + Sync {
/// ender dragon's wings, Punch arrows, explosions, wind charges, a mace smash's area, a
/// hoglin's throw), nor yet for the ones Horizon makes another way: a mob's melee push from
/// its attack knockback or a Knockback weapon, an armor stand's, the ender dragon's. It runs
/// on the thread that ticks the world (the server thread; the window's in-frame): state
/// on the thread that ticks the world (the server thread): state
/// shared with render thread hooks needs synchronising. No `self`: the game is in the middle
/// of the hit. API 125.
///
@@ -1869,9 +1865,8 @@ pub trait Hooks: Send + Sync {
/// the code. On a server's client a run-out use stays in use, frozen,
/// so letting go still tells the server. Both sides, as vanilla's
/// `updatingUsingItem`: answer the same on both, and change the world
/// only where this `Game` runs the world, not on a server's client (no
/// helper tells the two apart yet; `native_window` is also true
/// in-frame and on an in-frame LAN host). Runs every tick of every use:
/// only on the server (`native_window` is false there), not on a
/// server's client. Runs every tick of every use:
/// check the item first. API 131.
///
/// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped.
@@ -437,6 +437,18 @@ impl<L: LevelAccess + ?Sized> Sim<'_, L> {
}
// DecoratedPotBlock.useWithoutItem: the wobble always succeeds.
Class::DecoratedPot => Success,
// MovingPistonBlock.useWithoutItem: a moving piston whose block
// entity is gone (`Level.getBlockEntity` null) is removed by the
// click on a server; one still in flight passes it on.
Class::MovingPiston => {
if !self.level.is_client_side() && self.moving_piston(pos).is_none() {
self.remove_block(pos, false);
// CONSUME: the action is used, the arm does not swing.
Consume
} else {
Pass
}
}
// JukeboxBlock.useWithoutItem: ejecting a disc is SUCCESS (the
// insert is useItemOn). An empty jukebox passes.
Class::Jukebox if table.bool(state, p.has_record) => Success,
+37
View File
@@ -1945,6 +1945,43 @@ fn a_broken_moving_piston_drops_the_block_it_carries() {
);
}
/// `MovingPistonBlock.useWithoutItem`: a moving piston with no block entity (its
/// move is not in flight) is removed by the click, and one that is still moving
/// keeps its block and passes the click on.
#[test]
fn a_moving_piston_without_a_block_entity_is_removed_by_a_click() {
let mut w = world!();
let moving = w.id("moving_piston", &[("facing", "east"), ("type", "normal")]);
let orphan = BlockPos::new(0, 1, 0);
let in_flight = BlockPos::new(4, 1, 4);
let air = w.id("air", &[]);
for pos in [orphan, in_flight] {
w.put(pos, moving);
}
w.ticks.moving_pistons.push((
in_flight,
MovingPiston {
moved_state: w.id("sand", &[]),
direction: Direction::East,
extending: true,
source: false,
progress: 0.5,
progress_o: 0.4,
last_ticked: 0,
death_ticks: 0,
id: 0,
},
));
assert_eq!(w.use_block(orphan), InteractionResult::Consume);
assert_eq!(w.get(orphan), air, "the orphan moving piston is removed");
assert_eq!(w.use_block(in_flight), InteractionResult::Pass);
assert_eq!(
w.get(in_flight),
moving,
"a moving piston in flight keeps its block"
);
}
#[test]
fn dripstone_fills_cauldrons() {
let mut w = world!();
+13
View File
@@ -32,6 +32,14 @@ class PrepareOutputTests(unittest.TestCase):
'block-behaviours.json': b'{"c": "Foo$$Lambda/0x00000ff0"}',
'export.log': b'run 2',
'assets/minecraft/sounds/a.ogg': b'two',
# From the launcher's asset store (CI clones the languages in later), not the preparer's export.
'assets/pack.mcmeta': b'{"language": {"fr_fr": {}}}',
'assets/minecraft/lang/fr_fr.json': b'{"a": "b"}',
'asset-index.json': b'{"objects": {}}',
# The game's run-time caches, there once a harness ran the game on this preparation.
'horizon-sprites.cache': b'sprites',
'horizon-data.bin': b'data',
'horizon-data.bin.123.tmp': b'half',
'horizon-prepare.json': b'{"output_version": 1}',
'.horizon-prepare.lock': b'',
}
@@ -48,6 +56,11 @@ class PrepareOutputTests(unittest.TestCase):
(Path(tmp) / 'b/horizon-resources.json').write_text(json.dumps(manifest))
(Path(tmp) / 'b/new.nbt').write_bytes(b'')
self.assertNotEqual(first, prepare_output.digest(Path(tmp) / 'b'))
# en_us and deprecated are the JAR's, made by the preparer: they count.
(Path(tmp) / 'b/new.nbt').unlink()
self.assertEqual(first, prepare_output.digest(Path(tmp) / 'b'))
(Path(tmp) / 'b/assets/minecraft/lang/en_us.json').write_bytes(b'{}')
self.assertNotEqual(first, prepare_output.digest(Path(tmp) / 'b'))
def test_the_versions_file_ends_at_the_constant(self):
versions = prepare_output.parse_versions('# comment\n1 aa\n2 bb\n')
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
"""A minecart through a Nether portal on Horizon's dedicated server, tick by tick against vanilla's numbers.
python3 tools/cart_portal_e2e.py --bin-dir target/fast
PURPOSE: a minecart with a mob rider crosses a Nether portal with its rider (`Entity.teleportCrossDimension`
takes the passengers along and mounts them again), and an empty one arrives where and when vanilla's does:
the forward offset reset of `AbstractMinecart.getRelativePortalPosition`, the 0.4 clamp of its known movement
(`OldMinecartBehavior.getKnownMovement`), and the tick the portal is handled in (`handlePortal` opens the
minecart's own tick, and a cart that arrives in a level that ticks later in the same server tick moves in it
that tick).
WHY: before the fix a cart with a pig on it never left the portal, and an empty one landed 0.6 blocks off the
exit portal's centre a tick late, fell onto the ground between the portal and the rails and lost half its
speed each tick.
REFS: the expected numbers are what the vanilla 26.3 server printed for the same scenes (stone floor at
y=99, powered rails under the minecart and behind the exit portal, a minecart pushed at 0.4 blocks per
tick along 10 rails into the portal, ticks frozen and stepped one at a time, no player).
LOGS: the server's output goes to --out (a temporary folder by default), one file per lane.
READS-BY: tools/pr_ready.py does not run it; run it by hand after changing game/nether_portal.rs.
ENDS-WHEN: each lane has checked its ticks, or the first mismatch is printed (exit status 1).
"""
import argparse
from pathlib import Path
import re
import shutil
import sys
import tempfile
import time
sys.path.insert(0, str(Path(__file__).resolve().parent))
import lab # noqa: E402
from server_bench import DONE, Server # noqa: E402
PROPERTIES = {
'online-mode': 'false', 'level-type': 'minecraft:flat', 'spawn-protection': '0', 'view-distance': '4',
'simulation-distance': '4', 'pause-when-empty-seconds': '-1', 'spawn-monsters': 'false',
'spawn-animals': 'false', 'spawn-npcs': 'false', 'generate-structures': 'false', 'allow-flight': 'true',
'difficulty': 'normal', 'enable-command-block': 'true',
}
NETHER = 'execute in minecraft:the_nether run '
RAIL = 'minecraft:powered_rail[powered=true,shape=east_west]'
TOL = 1e-5
# Per lane: tick -> (cart x, y, speed along x, pig x, pig y or None). The numbers vanilla 26.3 printed. The pig's y
# is left out on the tick it arrives unticked in the Overworld (vanilla 100.0, Horizon puts it 0.1875 above the
# cart): it is the one value the rider's first, unticked sample does not match.
LANES = {
'overworld_to_nether_with_pig': {
'rider': True, 'z': 4, 'zn': 0, 'dest_z': 0.5,
'ticks': {
21: (9.732167700000005, 100.0625, 1.5984504303618299, 9.732167700000005, 100.25),
22: (1.9, 100.0225, 0.38, 1.9, 100.21),
23: (2.2799999952316283, 100.0, 0.38, 2.2799999952316283, 100.1875),
30: (4.867302549534948, 100.0625, 0.7883303007081885, 4.867302549534948, 100.25),
40: (8.867302549534951, 100.0625, 1.3569615856515638, 8.867302549534951, 100.25),
}},
'overworld_to_nether_empty': {
'rider': False, 'z': 4, 'zn': 0, 'dest_z': 0.5,
'ticks': {
21: (9.9, 100.0625, 1.0332454298233449, None, None),
22: (1.9, 100.0225, 0.38, None, None),
30: (5.0599999904632575, 100.0625, 0.6583788209356798, None, None),
40: (9.059999990463261, 100.0625, 0.9404627730163926, None, None),
}},
'nether_to_overworld_with_pig': {
'rider': True, 'z': 3, 'zn': 0, 'dest_z': 3.5,
'ticks': {
21: (9.732167700000005, 100.0625, 1.5984504303618299, 9.732167700000005, 100.25),
22: (84.5, 100.0625, 0.4, 84.5, -1),
23: (84.9, 100.0225, 0.38, 84.9, 100.21),
30: (87.46730254953496, 100.0625, 0.7305218663071098, 87.46730254953496, 100.25),
40: (91.46730254953502, 100.0625, 1.300864178186122, 91.46730254953502, 100.25),
}},
'nether_to_overworld_empty': {
'rider': False, 'z': 3, 'zn': 0, 'dest_z': 3.5,
'ticks': {
21: (9.9, 100.0625, 1.0332454298233449, None, None),
22: (84.5, 100.0625, 0.4, None, None),
23: (84.9, 100.0225, 0.38, None, None),
30: (87.65999999046329, 100.0625, 0.6233112718079998, None, None),
40: (91.65999999046335, 100.0625, 0.9171487218920757, None, None),
}},
}
class Console:
"""The server's console: each command's output is the lines before a marker `say` that follows it."""
def __init__(self, server):
self.server, self.n = server, 0
def __call__(self, command, timeout=60):
# Horizon keeps the level an `execute in` named for the next plain command: name the Overworld.
if command.split()[0] in ('forceload', 'fill', 'setblock', 'summon'):
command = 'execute in minecraft:overworld run ' + command
self.n += 1
mark = f'CARTMARK{self.n}X'
start = self.server.count()
self.server.console(command)
self.server.console('say ' + mark)
end = time.time() + timeout
while time.time() < end:
lines = self.server.since(start)
for i, line in enumerate(lines):
if mark in line:
return lines[:i]
time.sleep(0.02)
raise RuntimeError(f'no reply to {command!r} in {timeout} s')
def gametime(self):
for line in self('time query gametime'):
found = re.search(r'time is (\d+)', line)
if found:
return int(found.group(1))
raise RuntimeError('cannot read the game time')
def step(self, n):
before = self.gametime()
self(f'tick step {n}')
end = time.time() + 60
while time.time() < end:
if self.gametime() >= before + n:
return
time.sleep(0.05)
raise RuntimeError(f'tick step {n} did not finish')
def loaded(self, dim, x, y, z):
end = time.time() + 180
while time.time() < end:
if any('Test passed' in l for l in self(f'execute in minecraft:{dim} if loaded {x} {y} {z}')):
return
time.sleep(1)
raise RuntimeError(f'the chunk at {dim} {x} {y} {z} never loaded')
def numbers(self, selector, path):
for line in self(f'data get entity {selector} {path}'):
found = re.search(r'entity data: \[(.*)\]$', line)
if found:
return [float(v.rstrip('d')) for v in found.group(1).split(', ')]
return None
def build(console, name, lane):
"""The scenes of the sbs cartA and cartB runs, one lane: rails 0..9 into a portal at x=10, a portal behind
which rails run on in the other level."""
z, zn = lane['z'], lane['zn']
o2n = name.startswith('overworld')
cart_dim = '' if o2n else NETHER
far = (z, zn) if o2n else (zn, z)
# The destination level's portal and rails; the Overworld's forced chunks come first.
if o2n:
console(NETHER + 'forceload add 0 -16 15 47')
console(f'forceload add 0 {z} 15 {z}')
for dim, x, zz in (('overworld', 8, z), ('the_nether', 8, -10)):
console.loaded(dim, x, 100, zz)
else:
console(NETHER + 'forceload add 0 -16 15 47')
console(f'forceload add 80 {z} 95 {z}')
for dim, x, zz in (('overworld', 88, z), ('the_nether', 8, -10)):
console.loaded(dim, x, 100, zz)
console('tick freeze')
if o2n:
build_side(console, '', 0, 12, 10, z, rails=(0, 9))
build_side(console, NETHER, 0, 14, 1, zn, rails=(2, 12), redstone=(6, zn + 1))
console(f'setblock 4 100 {z + 1} minecraft:redstone_block')
start, at_z = 1.5, z + 0.5
run_in = ''
else:
build_side(console, '', 80, 95, 84, z, rails=(85, 95), redstone=(90, z + 1))
build_side(console, NETHER, 0, 14, 10, zn, rails=(0, 9))
console(NETHER + f'setblock 4 100 {zn + 1} minecraft:redstone_block')
start, at_z = 1.5, zn + 0.5
run_in = NETHER
passenger = (',Passengers:[{id:"minecraft:pig",Tags:["L0"],NoAI:1b,Silent:1b,PersistenceRequired:1b}]'
if lane['rider'] else '')
console(run_in + f'summon minecraft:minecart {start} 100.1 {at_z} {{Tags:["L0"],Motion:[0.4d,0.0d,0.0d]{passenger}}}')
del cart_dim, far
def build_side(console, prefix, x0, x1, portal_x, z, rails, redstone=None):
"""Stone floor, air above, an obsidian frame with a lit portal (axis z) at `portal_x`, powered rails."""
console(prefix + f'fill {x0} 95 {z - 2} {x1 + 2} 106 {z + 3} minecraft:air')
console(prefix + f'fill {x0} 99 {z - 2} {x1 + 2} 99 {z + 3} minecraft:stone')
console(prefix + f'fill {portal_x} 99 {z - 1} {portal_x} 103 {z + 2} minecraft:obsidian')
console(prefix + f'fill {portal_x} 100 {z} {portal_x} 102 {z + 1} minecraft:nether_portal[axis=z]')
console(prefix + f'fill {rails[0]} 100 {z} {rails[1]} 100 {z} {RAIL}')
if redstone:
console(prefix + f'setblock {redstone[0]} 100 {redstone[1]} minecraft:redstone_block')
def run_lane(bins, name, lane, out):
work = Path(tempfile.mkdtemp(prefix='hz-cart-portal-'))
folder = work / 'server'
folder.mkdir()
(folder / 'eula.txt').write_text('eula=true\n')
props = dict(PROPERTIES, **{'server-port': str(lab.free_port()), 'server-ip': '127.0.0.1'})
(folder / 'server.properties').write_text(''.join(f'{k}={v}\n' for k, v in props.items()))
command = [bins / 'horizon-client', '--resources', lab.RESOURCES, '--dedicated', '--nogui']
server = Server(command, folder, out / f'{name}.log')
failed = []
try:
server.wait_for(DONE, 300)
console = Console(server)
console('forceload add -16 -16 16 16')
time.sleep(3)
build(console, name, lane)
at, cart_sel, pig_sel = 0, '@e[type=minecraft:minecart,tag=L0,limit=1]', '@e[type=minecraft:pig,tag=L0,limit=1]'
for tick, (x, y, speed, pig_x, pig_y) in lane['ticks'].items():
console.step(tick - at)
at = tick
pos = console.numbers(cart_sel, 'Pos')
motion = console.numbers(cart_sel, 'Motion')
expect = [x, y, lane['dest_z'] if tick >= 22 else lane['z'] + 0.5 if name.startswith('overworld')
else lane['zn'] + 0.5]
if tick == 21:
expect[2] = pos[2] if pos else 0.0
if not pos or any(abs(a - b) > TOL for a, b in zip(pos, expect)):
failed.append(f'{name} t{tick}: cart at {pos}, vanilla {expect}')
if not motion or abs(motion[0] - speed) > TOL:
failed.append(f'{name} t{tick}: cart speed {motion and motion[0]}, vanilla {speed}')
if lane['rider'] and tick >= 22:
pig = console.numbers(pig_sel, 'Pos')
bad = not pig or abs(pig[0] - pig_x) > TOL or abs(pig[2] - lane['dest_z']) > TOL
if pig_y is not None and pig_y > 0 and pig and abs(pig[1] - pig_y) > TOL:
bad = True
if bad:
failed.append(f'{name} t{tick}: pig at {pig}, vanilla x {pig_x} y {pig_y} z {lane["dest_z"]}')
print(f'{name} t{tick}: cart {pos} speed {motion and motion[0]}')
finally:
server.stop()
shutil.rmtree(work, ignore_errors=True)
return failed
def main():
parser = argparse.ArgumentParser(description=__doc__.split('\n')[0])
parser.add_argument('--bin-dir', default='target/fast', type=Path)
parser.add_argument('--out', type=Path)
parser.add_argument('--lane', action='append', choices=sorted(LANES))
args = parser.parse_args()
out = args.out or Path(tempfile.mkdtemp(prefix='cart-portal-logs-'))
out.mkdir(parents=True, exist_ok=True)
failed = []
for name in args.lane or LANES:
failed += run_lane(args.bin_dir.resolve(), name, LANES[name], out)
for line in failed:
print('FAIL', line)
print('cart portal: ' + ('FAILED' if failed else 'all lanes match vanilla'), f'(logs in {out})')
return 1 if failed else 0
if __name__ == '__main__':
sys.exit(main())
+5 -5
View File
@@ -13,7 +13,7 @@ ClientAdvancements.get port crates/horizon-client/src/advancements.rs::Tree::get
ClientChunkCache.isValidChunk skip:other chunks are stored by position, so there is no ring slot to check
ClientChunkCache.drop port crates/horizon-client-render/src/streaming.rs::Streaming::forget gap: minor: no ChunkEvent unload hook; block entities and renderer columns are cleared in Game::handle_packet
ClientChunkCache.getChunk skip:java lookup of a loaded chunk; Horizon reads world.chunks directly
ClientChunkCache.replaceBiomes missing minor: ChunksBiomes (id 13) has no arm in Game::handle_packet, so server biome changes (e.g. /fillbiome) do not reach the client until the chunk is resent
ClientChunkCache.replaceBiomes port crates/horizon-client/src/game/remote_chunks.rs::Game::remote_chunks_biomes each loaded column takes the packet's biomes per section, then it and its neighbours are marked dirty so their sections are drawn again (/fillbiome reaches the client)
ClientChunkCache.flipUpdateTrackingSets skip:other double-buffered change lists for LevelRenderer; Horizon hands changed columns to the renderer directly
ClientChunkCache.Storage.getIndex skip:java ring-buffer index into the view range; Horizon keys chunks by position
ClientChunkCache.Storage.replace port crates/horizon-client/src/game/remote_chunks.rs::Game::remote_chunk gap: minor: old chunk unload is in Streaming::forget, not here
@@ -48,10 +48,10 @@ ClientCommonPacketListenerImpl.addOrUpdatePackPrompt port crates/horizon-client/
ClientCommonPacketListenerImpl.PackConfirmScreen.<init> port crates/horizon-client/src/game/server_packs.rs::Game::handle_pack_push creates the prompt with its pending requests
ClientCommonPacketListenerImpl.PackConfirmScreen.update port crates/horizon-client/src/game/server_packs.rs::Game::handle_pack_push adds the new request to the open prompt
ClientConfigurationPacketListenerImpl.handleRegistryData port crates/horizon-client/src/net.rs::configure registry entries are collected in configuration
ClientConfigurationPacketListenerImpl.handleUpdateTags missing minor: UpdateTags is not read in configure (net.rs); Horizon uses its built-in tags, which match vanilla unless a datapack changes them
ClientConfigurationPacketListenerImpl.handleUpdateTags port crates/horizon-client/src/net.rs::configure UpdateTags fills the registry's tags before the play state, so a datapack's tags are the server's
ClientConfigurationPacketListenerImpl.handleSelectKnownPacks port crates/horizon-client/src/net.rs::configure gap: minor: always answers with no known packs, so the server sends every registry entry
ClientConfigurationPacketListenerImpl.runWithResources skip:data resource loading for known packs; Horizon reads its data from tables
ClientConfigurationPacketListenerImpl.handleCodeOfConduct port crates/horizon-client/src/net.rs::configure gap: player: accepts the server's code of conduct at once; the player never sees its text or can decline
ClientConfigurationPacketListenerImpl.handleCodeOfConduct port crates/horizon-client/src/net.rs::configure the screen shows the text with the not-again box, Acknowledge and Disconnect before the connection continues (game/code_of_conduct.rs)
ClientConfigurationPacketListenerImpl.handleConfigurationFinished port crates/horizon-client/src/net.rs::configure gap: minor: no registry filter for an integrated server (see filterRegistries)
ClientConfigurationPacketListenerImpl.filterRegistries skip:data Horizon's registries come from its own data; nothing to filter
ClientDebugSubscriber.requestedSubscriptions missing minor: Horizon never requests debug subscriptions; vanilla asks for server tick time while the F3+2 chart is on
@@ -88,7 +88,7 @@ ClientLevel.setBlock port crates/horizon-client/src/game/remote.rs::Game::set_bl
ClientLevel.<init> port crates/horizon-client/src/game/remote.rs::Game::enter_remote_dimension gap: minor: the environment layers are applied in Game::render
ClientLevel.addEnvironmentAttributeLayers port crates/horizon-client/src/game.rs::Game::render sky colour and sky light move during a lightning flash
ClientLevel.pollLightUpdates skip:other light is applied when it arrives or relit at once (light.rs relight); there is no queue to poll
ClientLevel.getPrecipitationAt port crates/horizon-client/src/game.rs::GameLevel::precipitation gap: minor: no check that the chunk is loaded; vanilla answers none for an unloaded column
ClientLevel.getPrecipitationAt port crates/horizon-client/src/game.rs::GameLevel::precipitation the column's biome decides; no column, no answer (the chunk lookup fails, as vanilla answers none for an unloaded column)
ClientLevel.removeBlockBreakingProgress inline crates/horizon-client/src/game.rs::Game::destroy_block_progress minor: the 400-tick expiry runs on every update, not every 20 ticks
ClientLevel.removeProgress inline crates/horizon-client/src/game.rs::Game::destroy_block_progress cracks are keyed by breaker id, so there are no per-position sets to tidy
ClientLevel.shouldTickDeath missing minor: death animations tick for every remote mob held; no simulation-distance check
@@ -104,7 +104,7 @@ ClientLevel.spawnParticle port crates/horizon-client/src/animate_tick.rs::try_sp
ClientLevel.spawnFluidParticle port crates/horizon-client/src/animate_tick.rs::try_spawn_drip drip particle over a solid top (doAnimateTick)
ClientLevel.fillReportDetails skip:debug crash report details only
ClientLevel.playPlayerSound skip:other no caller in the 26.3 client sources
ClientLevel.playSound missing player: distance-delayed sounds (over 10 blocks, sqrt(distance)/40 s) play at once, so far fireworks and similar sounds arrive early; audio.rs has no delay
ClientLevel.playSound port crates/horizon-client-render/src/audio.rs::Audio::play_distance_delayed a sound over 10 blocks from the camera starts sqrt(distance)/40 s later (Audio::play_after)
ClientLevel.getRelativeTickSpeed skip:other no caller in the 26.3 client sources
ClientLevel.destroyBlockProgress port crates/horizon-client/src/game.rs::Game::destroy_block_progress sets or clears another player's crack on a block
ClientLevel.addAlwaysVisibleParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::add_with always-show flag set
1 AccountProfileKeyPairManager.prepareKeyPair port crates/horizon-client-render/src/auth.rs::prepare_chat_keys on a background thread, at start and on each online-mode join
13 ClientChunkCache.isValidChunk skip:other chunks are stored by position, so there is no ring slot to check
14 ClientChunkCache.drop port crates/horizon-client-render/src/streaming.rs::Streaming::forget gap: minor: no ChunkEvent unload hook; block entities and renderer columns are cleared in Game::handle_packet
15 ClientChunkCache.getChunk skip:java lookup of a loaded chunk; Horizon reads world.chunks directly
16 ClientChunkCache.replaceBiomes missing port crates/horizon-client/src/game/remote_chunks.rs::Game::remote_chunks_biomes minor: ChunksBiomes (id 13) has no arm in Game::handle_packet, so server biome changes (e.g. /fillbiome) do not reach the client until the chunk is resent each loaded column takes the packet's biomes per section, then it and its neighbours are marked dirty so their sections are drawn again (/fillbiome reaches the client)
17 ClientChunkCache.flipUpdateTrackingSets skip:other double-buffered change lists for LevelRenderer; Horizon hands changed columns to the renderer directly
18 ClientChunkCache.Storage.getIndex skip:java ring-buffer index into the view range; Horizon keys chunks by position
19 ClientChunkCache.Storage.replace port crates/horizon-client/src/game/remote_chunks.rs::Game::remote_chunk gap: minor: old chunk unload is in Streaming::forget, not here
48 ClientCommonPacketListenerImpl.PackConfirmScreen.<init> port crates/horizon-client/src/game/server_packs.rs::Game::handle_pack_push creates the prompt with its pending requests
49 ClientCommonPacketListenerImpl.PackConfirmScreen.update port crates/horizon-client/src/game/server_packs.rs::Game::handle_pack_push adds the new request to the open prompt
50 ClientConfigurationPacketListenerImpl.handleRegistryData port crates/horizon-client/src/net.rs::configure registry entries are collected in configuration
51 ClientConfigurationPacketListenerImpl.handleUpdateTags missing port crates/horizon-client/src/net.rs::configure minor: UpdateTags is not read in configure (net.rs); Horizon uses its built-in tags, which match vanilla unless a datapack changes them UpdateTags fills the registry's tags before the play state, so a datapack's tags are the server's
52 ClientConfigurationPacketListenerImpl.handleSelectKnownPacks port crates/horizon-client/src/net.rs::configure gap: minor: always answers with no known packs, so the server sends every registry entry
53 ClientConfigurationPacketListenerImpl.runWithResources skip:data resource loading for known packs; Horizon reads its data from tables
54 ClientConfigurationPacketListenerImpl.handleCodeOfConduct port crates/horizon-client/src/net.rs::configure gap: player: accepts the server's code of conduct at once; the player never sees its text or can decline the screen shows the text with the not-again box, Acknowledge and Disconnect before the connection continues (game/code_of_conduct.rs)
55 ClientConfigurationPacketListenerImpl.handleConfigurationFinished port crates/horizon-client/src/net.rs::configure gap: minor: no registry filter for an integrated server (see filterRegistries)
56 ClientConfigurationPacketListenerImpl.filterRegistries skip:data Horizon's registries come from its own data; nothing to filter
57 ClientDebugSubscriber.requestedSubscriptions missing minor: Horizon never requests debug subscriptions; vanilla asks for server tick time while the F3+2 chart is on
88 ClientLevel.<init> port crates/horizon-client/src/game/remote.rs::Game::enter_remote_dimension gap: minor: the environment layers are applied in Game::render
89 ClientLevel.addEnvironmentAttributeLayers port crates/horizon-client/src/game.rs::Game::render sky colour and sky light move during a lightning flash
90 ClientLevel.pollLightUpdates skip:other light is applied when it arrives or relit at once (light.rs relight); there is no queue to poll
91 ClientLevel.getPrecipitationAt port crates/horizon-client/src/game.rs::GameLevel::precipitation gap: minor: no check that the chunk is loaded; vanilla answers none for an unloaded column the column's biome decides; no column, no answer (the chunk lookup fails, as vanilla answers none for an unloaded column)
92 ClientLevel.removeBlockBreakingProgress inline crates/horizon-client/src/game.rs::Game::destroy_block_progress minor: the 400-tick expiry runs on every update, not every 20 ticks
93 ClientLevel.removeProgress inline crates/horizon-client/src/game.rs::Game::destroy_block_progress cracks are keyed by breaker id, so there are no per-position sets to tidy
94 ClientLevel.shouldTickDeath missing minor: death animations tick for every remote mob held; no simulation-distance check
104 ClientLevel.spawnFluidParticle port crates/horizon-client/src/animate_tick.rs::try_spawn_drip drip particle over a solid top (doAnimateTick)
105 ClientLevel.fillReportDetails skip:debug crash report details only
106 ClientLevel.playPlayerSound skip:other no caller in the 26.3 client sources
107 ClientLevel.playSound missing port crates/horizon-client-render/src/audio.rs::Audio::play_distance_delayed player: distance-delayed sounds (over 10 blocks, sqrt(distance)/40 s) play at once, so far fireworks and similar sounds arrive early; audio.rs has no delay a sound over 10 blocks from the camera starts sqrt(distance)/40 s later (Audio::play_after)
108 ClientLevel.getRelativeTickSpeed skip:other no caller in the 26.3 client sources
109 ClientLevel.destroyBlockProgress port crates/horizon-client/src/game.rs::Game::destroy_block_progress sets or clears another player's crack on a block
110 ClientLevel.addAlwaysVisibleParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::add_with always-show flag set
+11 -11
View File
@@ -8,12 +8,12 @@ ClientPacketListener.handleTeleportEntity inline crates/horizon-client/src/game/
ClientPacketListener.handleTickingState port crates/horizon-client/src/game/remote.rs::Game::handle_packet TickingState arm: clock rate and frozen flag
ClientPacketListener.handleTickingStep port crates/horizon-client/src/game/remote.rs::Game::handle_packet TickingStep arm: frozen ticks to run
ClientPacketListener.handleMoveEntity port crates/horizon-client/src/game/remote.rs::Game::move_entity relative move and rotation from the Pos, PosRot and Rot packets
ClientPacketListener.handleMinecartAlongTrack missing player: the server's rail steps for a minecart are dropped, so a cart on a dedicated server follows the client's own rail physics and can drift from where the server has it
ClientPacketListener.handleMinecartAlongTrack port crates/horizon-client/src/game/remote.rs::Game::remote_move_minecart the server's steps join the cart's own queue while it runs NewMinecartBehavior
ClientPacketListener.handleRemoveEntities port crates/horizon-client/src/game/remote.rs::Game::remove_entity gap: minor: a removed vehicle carrying the player is not remembered for a later teleport
ClientPacketListener.setValuesFromPositionPacket port crates/horizon-client/src/game/remote.rs::Game::remote_teleport gap: minor: the entity case (TeleportEntity) is in the handle_packet arm, not here
ClientPacketListener.handleRotatePlayer port crates/horizon-client/src/game/remote.rs::Game::handle_packet PlayerRotation arm; gap: minor: no ServerboundMovePlayerPacket.Rot reply is sent
ClientPacketListener.handleChunkBlocksUpdate port crates/horizon-client/src/game/remote.rs::Game::handle_packet SectionBlocksUpdate arm: each change through the block update
ClientPacketListener.handleChunksBiomes missing player: biome updates for loaded chunks are dropped, so grass, leaves and water keep the old biome colour
ClientPacketListener.handleChunksBiomes port crates/horizon-client/src/game/remote_chunks.rs::Game::remote_chunks_biomes each loaded column replaces its biomes, then its sections and its neighbours' are drawn again
ClientPacketListener.enableChunkLight inline crates/horizon-client-render/src/streaming.rs::Streaming::insert_remote gap: minor: the air-only section status is not set; every section of the 3x3 columns is marked dirty
ClientPacketListener.handleForgetLevelChunk port crates/horizon-client/src/game/remote.rs::Game::handle_packet ForgetLevelChunk arm: Streaming::forget, block entities and renderer columns dropped
ClientPacketListener.queueLightRemoval inline crates/horizon-client-render/src/streaming.rs::Streaming::forget gap: minor: the column's light state is dropped with the column; no per-section light clearing
@@ -26,8 +26,8 @@ ClientPacketListener.handleDeleteChat port crates/horizon-client/src/game/remote
ClientPacketListener.handleAnimate port crates/horizon-client/src/game/remote.rs::Game::remote_animate action 0 stops sleeping, 1 and 2 emit crit and enchanted hit particles
ClientPacketListener.handleHurtAnimation port crates/horizon-client/src/game/remote.rs::Game::handle_packet HurtAnimation arm: own player's hurt tilt, or a mob's hurt time
ClientPacketListener.handleSetSpawn port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetDefaultSpawnPosition arm; gap: minor: only an overworld spawn is kept, other dimensions are dropped
ClientPacketListener.handleAddTransientBlockPacket missing minor: the transient block shown while a falling block lands is not drawn; the block update still arrives
ClientPacketListener.findTotem missing player: the totem of undying pop-up over the screen is not shown when death protection triggers; Horizon has no item activation
ClientPacketListener.handleAddTransientBlockPacket port crates/horizon-client/src/game/transient_blocks.rs::Game::add_transient_block the block is queued, shown for its time to live and dropped once its section is drawn again
ClientPacketListener.findTotem port crates/horizon-client/src/game/totem.rs::Game::find_totem the first hand's item with death_protection, else a new totem, then LocalPlayer.displayItemActivation
ClientPacketListener.handleSetExperience port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetExperience arm
ClientPacketListener.determineLevelLoadingReason port crates/horizon-client-render/src/level_loading.rs::reason_for_respawn the reason of a Respawn packet: death, a Nether or an End crossing
ClientPacketListener.handleOpenScreen port crates/horizon-client/src/game/remote.rs::Game::remote_open_screen
@@ -40,14 +40,14 @@ ClientPacketListener.handleContainerClose port crates/horizon-client/src/game/re
ClientPacketListener.handleBlockEvent port crates/horizon-client/src/game/remote.rs::Game::handle_packet BlockEvent arm: client block event, then the block entity event
ClientPacketListener.handleGameEvent port crates/horizon-client/src/game/remote.rs::Game::handle_packet GameEvent arm (weather, mode, credits, respawn, arrow hit, sting, limited crafting, chunks load start); gap: minor: demo mode (5) is not shown
ClientPacketListener.openDemoIntroScreen skip:other demo mode only (GameEvent 5); Horizon has no demo mode
ClientPacketListener.startWaitingForNewLevel missing player: no level loading screen after a dimension change or respawn on a dedicated server; a join shows only the 'Loading terrain' status text
ClientPacketListener.startWaitingForNewLevel inline crates/horizon-client/src/game/remote.rs::Game::enter_remote_dimension the tracker starts and the LevelLoadingScreen draws until the player's section is shown
ClientPacketListener.handleMapItemData port crates/horizon-client/src/game/maps.rs::Game::remote_map_item_data
ClientPacketListener.handleUpdateAdvancementsPacket port crates/horizon-client/src/game/advancements.rs::Game::remote_advancements UpdateAdvancements arm passes the added icons
ClientPacketListener.handleSelectAdvancementsTab missing minor: the server's advancement tab choice is ignored
ClientPacketListener.handleCommands port crates/horizon-client/src/game/remote.rs::Game::handle_packet Commands arm: command tree from the packet, merged with Horizon's own
ClientPacketListener.handleStopSoundEvent port crates/horizon-client/src/game/remote.rs::Game::handle_packet StopSound arm: audio stop_sounds
ClientPacketListener.handleUpdateRecipes skip:data Horizon reads recipes from its own tables; the server's list is not read
ClientPacketListener.handleLookAt missing player: a server command that turns the camera (/faceplayer, /tp with facing) does not turn this player's view
ClientPacketListener.handleLookAt port crates/horizon-client/src/game/remote.rs::Game::remote_look_at the anchor's point, or the entity's, turns the player's view
ClientPacketListener.handleTagQueryPacket port crates/horizon-client/src/game/debug_keys.rs::Game::answer_tag_query reached from the TagQuery arm of remote.rs handle_packet
ClientPacketListener.handleAwardStats port crates/horizon-client/src/game/remote.rs::Game::handle_packet AwardStats arm: known stat keys into the stats values
ClientPacketListener.handleRecipeBookAdd port crates/horizon-client/src/game/recipe_book.rs::Game::remote_recipe_book_add
@@ -57,7 +57,7 @@ ClientPacketListener.refreshRecipeBook inline crates/horizon-client/src/game/rec
ClientPacketListener.updateTags skip:data the server's tag payload is not read; tags come from Horizon's own data (net.rs configure ignores UpdateTags)
ClientPacketListener.handleUpdateTags skip:data the server's tag payload is not read; tags come from Horizon's own data (net.rs configure ignores UpdateTags)
ClientPacketListener.handleChangeDifficulty port crates/horizon-client/src/game/remote.rs::Game::handle_packet ChangeDifficulty arm
ClientPacketListener.handleSetCamera missing player: a spectator's view does not attach to the entity it spectates (for example /spectate)
ClientPacketListener.handleSetCamera port crates/horizon-client/src/game/remote_camera.rs::Game::remote_set_camera the view follows the entity; the player's own id gives it back
ClientPacketListener.handleInitializeBorder port crates/horizon-client/src/game/remote.rs::Game::handle_packet InitializeBorder arm: centre, size or lerp, absolute max, warnings
ClientPacketListener.handleSetBorderCenter port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetBorderCenter arm
ClientPacketListener.handleSetBorderLerpSize port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetBorderLerpSize arm
@@ -75,7 +75,7 @@ ClientPacketListener.handlePlayerInfoRemove port crates/horizon-client/src/game/
ClientPacketListener.handlePlayerInfoUpdate port crates/horizon-client/src/game/remote.rs::Game::handle_packet PlayerInfoUpdate arm: new entries, then listing, latency, name, hat, list order and game mode for tab players
ClientPacketListener.applyPlayerInfoUpdate inline crates/horizon-client/src/game/remote.rs::Game::handle_packet gap: minor: the own player's game-mode update does not change the local mode; INITIALIZE_CHAT is ignored
ClientPacketListener.initializeChatSession port crates/horizon-client/src/game/remote.rs::Remote::new_chat_validator the session each INITIALIZE_CHAT entry carries is kept and checked against the services' profile-key keys when the player's chat is first read
ClientPacketListener.enforcesSecureChat missing minor: the Login flag enforces_secure_chat is not read
ClientPacketListener.enforcesSecureChat inline crates/horizon-client/src/game/remote.rs::Game::handle_packet the Login flag is kept as serverEnforcesSecureChat and the fallback message validator enforces it
ClientPacketListener.handleGameRuleValues port crates/horizon-client/src/game/lan_screen.rs::Game::remote_world_options_rules
ClientPacketListener.handleSoundEntityEvent port crates/horizon-client/src/game/remote.rs::Game::handle_packet SoundEntity arm: the sound plays where the tracked entity is
ClientPacketListener.handleAddObjective port crates/horizon-client/src/game/remote.rs::Game::remote_objective add, remove and update of an objective
@@ -88,7 +88,7 @@ ClientPacketListener.handlePlaceRecipe port crates/horizon-client/src/game/recip
ClientPacketListener.handleLightUpdatePacket port crates/horizon-client/src/game/remote_chunks.rs::Game::remote_light_update
ClientPacketListener.handleMerchantOffers port crates/horizon-client/src/game/remote.rs::Game::remote_merchant_offers
ClientPacketListener.handleSetChunkCacheRadius port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetChunkCacheRadius arm: streaming radius, columns out of range dropped
ClientPacketListener.handleSetSimulationDistance missing minor: the server's simulation distance is not read
ClientPacketListener.handleSetSimulationDistance inline crates/horizon-client/src/game/remote.rs::Game::handle_packet SetSimulationDistance arm: ClientLevel.serverSimulationDistance
ClientPacketListener.handleSetChunkCacheCenter port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetChunkCacheCenter arm
ClientPacketListener.handleBlockChangedAck port crates/horizon-client/src/game/remote.rs::Game::end_predictions
ClientPacketListener.handleBundlePacket inline crates/horizon-client/src/net.rs::receive_play bundle delimiters are dropped; the packets inside arrive as a stream and are handled one by one, in order
@@ -117,7 +117,7 @@ ClientPacketListener.setKeyPair port crates/horizon-client/src/game/remote_chat.
ClientRegistryLayer.createRegistryAccess skip:data registry layers come from Horizon's own tables
ClientSuggestionProvider.getOnlinePlayerNames port crates/horizon-client/src/game/remote.rs::Game::remote_online_names sorted names of the server's player list
ClientSuggestionProvider.getCustomTabSuggestions port crates/horizon-client-core/src/registries.rs::Registries::custom_tab_suggestions the player names and the server's custom completions, each once
ClientSuggestionProvider.getSelectedEntities missing minor: no suggestion for the entity under the crosshair
ClientSuggestionProvider.getSelectedEntities port crates/horizon-client/src/game/pick_entity.rs::Game::crosshair_entity_uuid the UUID of the entity under the crosshair, before any block; the command suggestions add it
ClientSuggestionProvider.suggestRegistryElements port crates/horizon-command/src/args.rs::suggest_resources registry ids come from Horizon's registry tables
ClientSuggestionProvider.getRelevantCoordinates port crates/horizon-command/src/args.rs::suggest_coordinates the block under the crosshair for block positions
ClientSuggestionProvider.getAbsoluteCoordinates port crates/horizon-command/src/args.rs::suggest_coordinates gap: minor: the exact hit point is not offered; Vec3 arguments only get ~
@@ -129,7 +129,7 @@ LevelLoadTracker.startClientLoad inline crates/horizon-client/src/game/remote.rs
LevelLoadTracker.isLevelReady inline crates/horizon-client/src/game/remote.rs::Game::tick_remote_level gap: minor: readiness is the player's column being lit; vanilla's close delay is not kept
LevelLoadTracker.getPlayerCompiledSectionCallback inline crates/horizon-client/src/game/remote.rs::Game::tick_remote_level gap: minor: the gate is the column being lit, not the section being compiled
LevelLoadTracker.WaitingForPlayerChunk.isReady inline crates/horizon-client/src/game/remote.rs::Game::tick_remote_level gap: minor: no timeout and no spectator or dead exemption
LevelLoadTracker.WaitingForServer.loadingPacketsReceived missing minor: the server's level-chunks-start event (GameEvent 13) is ignored
LevelLoadTracker.WaitingForServer.loadingPacketsReceived port crates/horizon-client-render/src/level_loading.rs::LevelLoadTracker::loading_packets_received GameEvent 13 (LEVEL_CHUNKS_LOAD_START) moves the wait on to the player's chunk
MultiPlayerGameMode.startPrediction port crates/horizon-client/src/game/remote.rs::Game::next_sequence gap: minor: only the sequence number; each caller builds and sends its own packet
MultiPlayerGameMode.ensureHasSentCarriedItem inline crates/horizon-client/src/game/remote.rs::Game::remote_send_state the SetCarriedItem check, made on every send
MultiPlayerGameMode.performUseItemOn port crates/horizon-client/src/game/remote.rs::Game::remote_use_block gap: minor: no world-border check, and UseItemOn always sends world_border false
1 ClientPacketListener.<init> port crates/horizon-client/src/game/remote.rs::Game::join_server gap: minor: clears the chat on join; vanilla restores chat state kept across a reconfiguration
8 ClientPacketListener.handleTickingState port crates/horizon-client/src/game/remote.rs::Game::handle_packet TickingState arm: clock rate and frozen flag
9 ClientPacketListener.handleTickingStep port crates/horizon-client/src/game/remote.rs::Game::handle_packet TickingStep arm: frozen ticks to run
10 ClientPacketListener.handleMoveEntity port crates/horizon-client/src/game/remote.rs::Game::move_entity relative move and rotation from the Pos, PosRot and Rot packets
11 ClientPacketListener.handleMinecartAlongTrack missing port crates/horizon-client/src/game/remote.rs::Game::remote_move_minecart player: the server's rail steps for a minecart are dropped, so a cart on a dedicated server follows the client's own rail physics and can drift from where the server has it the server's steps join the cart's own queue while it runs NewMinecartBehavior
12 ClientPacketListener.handleRemoveEntities port crates/horizon-client/src/game/remote.rs::Game::remove_entity gap: minor: a removed vehicle carrying the player is not remembered for a later teleport
13 ClientPacketListener.setValuesFromPositionPacket port crates/horizon-client/src/game/remote.rs::Game::remote_teleport gap: minor: the entity case (TeleportEntity) is in the handle_packet arm, not here
14 ClientPacketListener.handleRotatePlayer port crates/horizon-client/src/game/remote.rs::Game::handle_packet PlayerRotation arm; gap: minor: no ServerboundMovePlayerPacket.Rot reply is sent
15 ClientPacketListener.handleChunkBlocksUpdate port crates/horizon-client/src/game/remote.rs::Game::handle_packet SectionBlocksUpdate arm: each change through the block update
16 ClientPacketListener.handleChunksBiomes missing port crates/horizon-client/src/game/remote_chunks.rs::Game::remote_chunks_biomes player: biome updates for loaded chunks are dropped, so grass, leaves and water keep the old biome colour each loaded column replaces its biomes, then its sections and its neighbours' are drawn again
17 ClientPacketListener.enableChunkLight inline crates/horizon-client-render/src/streaming.rs::Streaming::insert_remote gap: minor: the air-only section status is not set; every section of the 3x3 columns is marked dirty
18 ClientPacketListener.handleForgetLevelChunk port crates/horizon-client/src/game/remote.rs::Game::handle_packet ForgetLevelChunk arm: Streaming::forget, block entities and renderer columns dropped
19 ClientPacketListener.queueLightRemoval inline crates/horizon-client-render/src/streaming.rs::Streaming::forget gap: minor: the column's light state is dropped with the column; no per-section light clearing
26 ClientPacketListener.handleAnimate port crates/horizon-client/src/game/remote.rs::Game::remote_animate action 0 stops sleeping, 1 and 2 emit crit and enchanted hit particles
27 ClientPacketListener.handleHurtAnimation port crates/horizon-client/src/game/remote.rs::Game::handle_packet HurtAnimation arm: own player's hurt tilt, or a mob's hurt time
28 ClientPacketListener.handleSetSpawn port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetDefaultSpawnPosition arm; gap: minor: only an overworld spawn is kept, other dimensions are dropped
29 ClientPacketListener.handleAddTransientBlockPacket missing port crates/horizon-client/src/game/transient_blocks.rs::Game::add_transient_block minor: the transient block shown while a falling block lands is not drawn; the block update still arrives the block is queued, shown for its time to live and dropped once its section is drawn again
30 ClientPacketListener.findTotem missing port crates/horizon-client/src/game/totem.rs::Game::find_totem player: the totem of undying pop-up over the screen is not shown when death protection triggers; Horizon has no item activation the first hand's item with death_protection, else a new totem, then LocalPlayer.displayItemActivation
31 ClientPacketListener.handleSetExperience port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetExperience arm
32 ClientPacketListener.determineLevelLoadingReason port crates/horizon-client-render/src/level_loading.rs::reason_for_respawn the reason of a Respawn packet: death, a Nether or an End crossing
33 ClientPacketListener.handleOpenScreen port crates/horizon-client/src/game/remote.rs::Game::remote_open_screen
40 ClientPacketListener.handleBlockEvent port crates/horizon-client/src/game/remote.rs::Game::handle_packet BlockEvent arm: client block event, then the block entity event
41 ClientPacketListener.handleGameEvent port crates/horizon-client/src/game/remote.rs::Game::handle_packet GameEvent arm (weather, mode, credits, respawn, arrow hit, sting, limited crafting, chunks load start); gap: minor: demo mode (5) is not shown
42 ClientPacketListener.openDemoIntroScreen skip:other demo mode only (GameEvent 5); Horizon has no demo mode
43 ClientPacketListener.startWaitingForNewLevel missing inline crates/horizon-client/src/game/remote.rs::Game::enter_remote_dimension player: no level loading screen after a dimension change or respawn on a dedicated server; a join shows only the 'Loading terrain' status text the tracker starts and the LevelLoadingScreen draws until the player's section is shown
44 ClientPacketListener.handleMapItemData port crates/horizon-client/src/game/maps.rs::Game::remote_map_item_data
45 ClientPacketListener.handleUpdateAdvancementsPacket port crates/horizon-client/src/game/advancements.rs::Game::remote_advancements UpdateAdvancements arm passes the added icons
46 ClientPacketListener.handleSelectAdvancementsTab missing minor: the server's advancement tab choice is ignored
47 ClientPacketListener.handleCommands port crates/horizon-client/src/game/remote.rs::Game::handle_packet Commands arm: command tree from the packet, merged with Horizon's own
48 ClientPacketListener.handleStopSoundEvent port crates/horizon-client/src/game/remote.rs::Game::handle_packet StopSound arm: audio stop_sounds
49 ClientPacketListener.handleUpdateRecipes skip:data Horizon reads recipes from its own tables; the server's list is not read
50 ClientPacketListener.handleLookAt missing port crates/horizon-client/src/game/remote.rs::Game::remote_look_at player: a server command that turns the camera (/faceplayer, /tp with facing) does not turn this player's view the anchor's point, or the entity's, turns the player's view
51 ClientPacketListener.handleTagQueryPacket port crates/horizon-client/src/game/debug_keys.rs::Game::answer_tag_query reached from the TagQuery arm of remote.rs handle_packet
52 ClientPacketListener.handleAwardStats port crates/horizon-client/src/game/remote.rs::Game::handle_packet AwardStats arm: known stat keys into the stats values
53 ClientPacketListener.handleRecipeBookAdd port crates/horizon-client/src/game/recipe_book.rs::Game::remote_recipe_book_add
57 ClientPacketListener.updateTags skip:data the server's tag payload is not read; tags come from Horizon's own data (net.rs configure ignores UpdateTags)
58 ClientPacketListener.handleUpdateTags skip:data the server's tag payload is not read; tags come from Horizon's own data (net.rs configure ignores UpdateTags)
59 ClientPacketListener.handleChangeDifficulty port crates/horizon-client/src/game/remote.rs::Game::handle_packet ChangeDifficulty arm
60 ClientPacketListener.handleSetCamera missing port crates/horizon-client/src/game/remote_camera.rs::Game::remote_set_camera player: a spectator's view does not attach to the entity it spectates (for example /spectate) the view follows the entity; the player's own id gives it back
61 ClientPacketListener.handleInitializeBorder port crates/horizon-client/src/game/remote.rs::Game::handle_packet InitializeBorder arm: centre, size or lerp, absolute max, warnings
62 ClientPacketListener.handleSetBorderCenter port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetBorderCenter arm
63 ClientPacketListener.handleSetBorderLerpSize port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetBorderLerpSize arm
75 ClientPacketListener.handlePlayerInfoUpdate port crates/horizon-client/src/game/remote.rs::Game::handle_packet PlayerInfoUpdate arm: new entries, then listing, latency, name, hat, list order and game mode for tab players
76 ClientPacketListener.applyPlayerInfoUpdate inline crates/horizon-client/src/game/remote.rs::Game::handle_packet gap: minor: the own player's game-mode update does not change the local mode; INITIALIZE_CHAT is ignored
77 ClientPacketListener.initializeChatSession port crates/horizon-client/src/game/remote.rs::Remote::new_chat_validator the session each INITIALIZE_CHAT entry carries is kept and checked against the services' profile-key keys when the player's chat is first read
78 ClientPacketListener.enforcesSecureChat missing inline crates/horizon-client/src/game/remote.rs::Game::handle_packet minor: the Login flag enforces_secure_chat is not read the Login flag is kept as serverEnforcesSecureChat and the fallback message validator enforces it
79 ClientPacketListener.handleGameRuleValues port crates/horizon-client/src/game/lan_screen.rs::Game::remote_world_options_rules
80 ClientPacketListener.handleSoundEntityEvent port crates/horizon-client/src/game/remote.rs::Game::handle_packet SoundEntity arm: the sound plays where the tracked entity is
81 ClientPacketListener.handleAddObjective port crates/horizon-client/src/game/remote.rs::Game::remote_objective add, remove and update of an objective
88 ClientPacketListener.handleLightUpdatePacket port crates/horizon-client/src/game/remote_chunks.rs::Game::remote_light_update
89 ClientPacketListener.handleMerchantOffers port crates/horizon-client/src/game/remote.rs::Game::remote_merchant_offers
90 ClientPacketListener.handleSetChunkCacheRadius port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetChunkCacheRadius arm: streaming radius, columns out of range dropped
91 ClientPacketListener.handleSetSimulationDistance missing inline crates/horizon-client/src/game/remote.rs::Game::handle_packet minor: the server's simulation distance is not read SetSimulationDistance arm: ClientLevel.serverSimulationDistance
92 ClientPacketListener.handleSetChunkCacheCenter port crates/horizon-client/src/game/remote.rs::Game::handle_packet SetChunkCacheCenter arm
93 ClientPacketListener.handleBlockChangedAck port crates/horizon-client/src/game/remote.rs::Game::end_predictions
94 ClientPacketListener.handleBundlePacket inline crates/horizon-client/src/net.rs::receive_play bundle delimiters are dropped; the packets inside arrive as a stream and are handled one by one, in order
117 ClientRegistryLayer.createRegistryAccess skip:data registry layers come from Horizon's own tables
118 ClientSuggestionProvider.getOnlinePlayerNames port crates/horizon-client/src/game/remote.rs::Game::remote_online_names sorted names of the server's player list
119 ClientSuggestionProvider.getCustomTabSuggestions port crates/horizon-client-core/src/registries.rs::Registries::custom_tab_suggestions the player names and the server's custom completions, each once
120 ClientSuggestionProvider.getSelectedEntities missing port crates/horizon-client/src/game/pick_entity.rs::Game::crosshair_entity_uuid minor: no suggestion for the entity under the crosshair the UUID of the entity under the crosshair, before any block; the command suggestions add it
121 ClientSuggestionProvider.suggestRegistryElements port crates/horizon-command/src/args.rs::suggest_resources registry ids come from Horizon's registry tables
122 ClientSuggestionProvider.getRelevantCoordinates port crates/horizon-command/src/args.rs::suggest_coordinates the block under the crosshair for block positions
123 ClientSuggestionProvider.getAbsoluteCoordinates port crates/horizon-command/src/args.rs::suggest_coordinates gap: minor: the exact hit point is not offered; Vec3 arguments only get ~
129 LevelLoadTracker.isLevelReady inline crates/horizon-client/src/game/remote.rs::Game::tick_remote_level gap: minor: readiness is the player's column being lit; vanilla's close delay is not kept
130 LevelLoadTracker.getPlayerCompiledSectionCallback inline crates/horizon-client/src/game/remote.rs::Game::tick_remote_level gap: minor: the gate is the column being lit, not the section being compiled
131 LevelLoadTracker.WaitingForPlayerChunk.isReady inline crates/horizon-client/src/game/remote.rs::Game::tick_remote_level gap: minor: no timeout and no spectator or dead exemption
132 LevelLoadTracker.WaitingForServer.loadingPacketsReceived missing port crates/horizon-client-render/src/level_loading.rs::LevelLoadTracker::loading_packets_received minor: the server's level-chunks-start event (GameEvent 13) is ignored GameEvent 13 (LEVEL_CHUNKS_LOAD_START) moves the wait on to the player's chunk
133 MultiPlayerGameMode.startPrediction port crates/horizon-client/src/game/remote.rs::Game::next_sequence gap: minor: only the sequence number; each caller builds and sends its own packet
134 MultiPlayerGameMode.ensureHasSentCarriedItem inline crates/horizon-client/src/game/remote.rs::Game::remote_send_state the SetCarriedItem check, made on every send
135 MultiPlayerGameMode.performUseItemOn port crates/horizon-client/src/game/remote.rs::Game::remote_use_block gap: minor: no world-border check, and UseItemOn always sends world_border false
+8 -8
View File
@@ -6,7 +6,7 @@ ShulkerModel.createShellMesh skip:data layer baked into entity-models.json, loa
ShulkerModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
SilverfishModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
BoggedModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
BoggedModel.setupAnim missing player: a sheared bogged keeps its mushrooms on its head; Bogged shearing sets sheared but nothing hides the mushrooms part
BoggedModel.setupAnim inline crates/horizon-client/src/mobs/anim.rs::setup the mushrooms part comes off once the bogged is sheared
SkeletonModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
SkeletonModel.createDefaultSkeletonMesh skip:data layer baked into entity-models.json, loaded by key
SkeletonModel.createSingleModelDualBodyLayer skip:data layer baked into entity-models.json, loaded by key
@@ -70,9 +70,9 @@ ElytraModel.setupAnim port crates/horizon-client/src/game/perspective.rs::Game::
ShieldModel.createLayer skip:data layer baked into entity-models.json, loaded by key
LeashKnotModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
ArrowModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
ArrowModel.setupAnim missing player: an arrow that lands in a block does not wobble for 7 ticks; arrows.rs sets shake but the arrow draw has no roll
ShulkerBulletModel.createBodyLayer missing player: shulker bullets draw as a flat spark sprite, not the tumbling 3D orb; the baked shulker_bullet model is never loaded
ShulkerBulletModel.setupAnim missing player: the bullet's spin and facing toward its flight are absent, since no model is drawn
ArrowModel.setupAnim inline crates/horizon-client/src/game/creatures.rs::Game::mob_geometry the root rolls while the shake timer runs
ShulkerBulletModel.createBodyLayer inline crates/horizon-client/src/game/creatures.rs::Game::mob_geometry the model is the baked shulker_bullet#main, drawn there as the tumbling orb
ShulkerBulletModel.setupAnim inline crates/horizon-client/src/game/creatures.rs::Game::mob_geometry yaw and pitch toward the flight, the tumble turned by its age, scaled -0.5
TridentModel.createLayer skip:data layer baked into entity-models.json, loaded by key
WindChargeModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
WindChargeModel.setupAnim port crates/horizon-client/src/game/wind.rs::Game::wind_charge_geometry wind and wind_charge parts spin in opposite directions
@@ -82,9 +82,9 @@ PiglinHeadModel.setupAnim inline crates/horizon-client-render/src/block_entity_r
SkullModel.createHeadModel skip:data layer baked into entity-models.json, loaded by key
SkullModel.createHumanoidHeadLayer skip:data layer baked into entity-models.json, loaded by key
SkullModel.setupAnim inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one head yaw and pitch in the skull branch of render_one
PlayerCapeModel.createCapeLayer missing player: capes never draw; skins.rs downloads the cape texture but nothing draws the cape layer
PlayerCapeModel.setupAnim missing player: the cape does not lean or flap with movement, since no cape is drawn
PlayerEarsModel.createEarsLayer missing minor: extra ears (showExtraEars accounts) are not drawn
PlayerCapeModel.createCapeLayer port crates/horizon-client/src/mobs/render.rs::cape the layer is the baked player#cape model, posed and drawn there
PlayerCapeModel.setupAnim port crates/horizon-client/src/mobs/render.rs::cape the cape part turned by the flap and the two leans
PlayerEarsModel.createEarsLayer port crates/horizon-client/src/mobs/render.rs::ears the layer is the baked player#ears model, drawn there
PlayerModel.createMesh skip:data layer baked into entity-models.json, loaded by key
PlayerModel.createArmorMeshSet skip:data layer baked into entity-models.json, loaded by key
PlayerModel.setupAnim port crates/horizon-client/src/mobs/anim.rs::setup gap: player: hat, jacket, sleeve and pant toggles (modelPart options) are not read, so a hidden layer still draws; humanoid pose is in the Humanoid branch
@@ -116,7 +116,7 @@ BuiltInBlockModels.createChest port crates/horizon-client-render/src/block_entit
BuiltInBlockModels.createSingletonChest port crates/horizon-client-render/src/block_entity_render.rs::classify Chest kind, single chest
BuiltInBlockModels.createXmasChest port crates/horizon-client-render/src/block_entity_render.rs::classify Chest kind with the Christmas texture
BuiltInBlockModels.createCopperGolem port crates/horizon-client-render/src/block_entity_render.rs::classify CopperGolem kind
BuiltInBlockModels.createFlowerBedModel missing minor: the 0.25 block shift for a flower bed shown by a copper golem is not applied
BuiltInBlockModels.createFlowerBedModel port crates/horizon-client/src/game/mob_blocks.rs::antenna_block_matrix wildflowers and pink petals take the flower bed's 0.25 shift on a golem's antenna
BuiltInBlockModels.createEnchantingTable port crates/horizon-client-render/src/block_entity_render.rs::classify EnchantingTable kind
BuiltInBlockModels.specialModelWithPropertyDispatch port crates/horizon-client-render/src/block_entity_render.rs::classify property dispatch by block state values
BuiltInBlockModels.Builder.put skip:java builder map of special models
1 PiglinModel.setupAttackAnimation port crates/horizon-client/src/mobs/anim.rs::humanoid melee swing branch via weapon_swing, which piglin_pose sets
6 ShulkerModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
7 SilverfishModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
8 BoggedModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
9 BoggedModel.setupAnim missing inline crates/horizon-client/src/mobs/anim.rs::setup player: a sheared bogged keeps its mushrooms on its head; Bogged shearing sets sheared but nothing hides the mushrooms part the mushrooms part comes off once the bogged is sheared
10 SkeletonModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
11 SkeletonModel.createDefaultSkeletonMesh skip:data layer baked into entity-models.json, loaded by key
12 SkeletonModel.createSingleModelDualBodyLayer skip:data layer baked into entity-models.json, loaded by key
70 ShieldModel.createLayer skip:data layer baked into entity-models.json, loaded by key
71 LeashKnotModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
72 ArrowModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
73 ArrowModel.setupAnim missing inline crates/horizon-client/src/game/creatures.rs::Game::mob_geometry player: an arrow that lands in a block does not wobble for 7 ticks; arrows.rs sets shake but the arrow draw has no roll the root rolls while the shake timer runs
74 ShulkerBulletModel.createBodyLayer missing inline crates/horizon-client/src/game/creatures.rs::Game::mob_geometry player: shulker bullets draw as a flat spark sprite, not the tumbling 3D orb; the baked shulker_bullet model is never loaded the model is the baked shulker_bullet#main, drawn there as the tumbling orb
75 ShulkerBulletModel.setupAnim missing inline crates/horizon-client/src/game/creatures.rs::Game::mob_geometry player: the bullet's spin and facing toward its flight are absent, since no model is drawn yaw and pitch toward the flight, the tumble turned by its age, scaled -0.5
76 TridentModel.createLayer skip:data layer baked into entity-models.json, loaded by key
77 WindChargeModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
78 WindChargeModel.setupAnim port crates/horizon-client/src/game/wind.rs::Game::wind_charge_geometry wind and wind_charge parts spin in opposite directions
82 SkullModel.createHeadModel skip:data layer baked into entity-models.json, loaded by key
83 SkullModel.createHumanoidHeadLayer skip:data layer baked into entity-models.json, loaded by key
84 SkullModel.setupAnim inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one head yaw and pitch in the skull branch of render_one
85 PlayerCapeModel.createCapeLayer missing port crates/horizon-client/src/mobs/render.rs::cape player: capes never draw; skins.rs downloads the cape texture but nothing draws the cape layer the layer is the baked player#cape model, posed and drawn there
86 PlayerCapeModel.setupAnim missing port crates/horizon-client/src/mobs/render.rs::cape player: the cape does not lean or flap with movement, since no cape is drawn the cape part turned by the flap and the two leans
87 PlayerEarsModel.createEarsLayer missing port crates/horizon-client/src/mobs/render.rs::ears minor: extra ears (showExtraEars accounts) are not drawn the layer is the baked player#ears model, drawn there
88 PlayerModel.createMesh skip:data layer baked into entity-models.json, loaded by key
89 PlayerModel.createArmorMeshSet skip:data layer baked into entity-models.json, loaded by key
90 PlayerModel.setupAnim port crates/horizon-client/src/mobs/anim.rs::setup gap: player: hat, jacket, sleeve and pant toggles (modelPart options) are not read, so a hidden layer still draws; humanoid pose is in the Humanoid branch
116 BuiltInBlockModels.createSingletonChest port crates/horizon-client-render/src/block_entity_render.rs::classify Chest kind, single chest
117 BuiltInBlockModels.createXmasChest port crates/horizon-client-render/src/block_entity_render.rs::classify Chest kind with the Christmas texture
118 BuiltInBlockModels.createCopperGolem port crates/horizon-client-render/src/block_entity_render.rs::classify CopperGolem kind
119 BuiltInBlockModels.createFlowerBedModel missing port crates/horizon-client/src/game/mob_blocks.rs::antenna_block_matrix minor: the 0.25 block shift for a flower bed shown by a copper golem is not applied wildflowers and pink petals take the flower bed's 0.25 shift on a golem's antenna
120 BuiltInBlockModels.createEnchantingTable port crates/horizon-client-render/src/block_entity_render.rs::classify EnchantingTable kind
121 BuiltInBlockModels.specialModelWithPropertyDispatch port crates/horizon-client-render/src/block_entity_render.rs::classify property dispatch by block state values
122 BuiltInBlockModels.Builder.put skip:java builder map of special models
+10 -10
View File
@@ -12,11 +12,11 @@ LavaParticle.Provider.createParticle inline crates/horizon-client-render/src/par
NoteParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Note arm: colour from the pitch, friction 0.66, lifetime 6, size x1.5
NoteParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size Size::GrowIn over 32 ticks
NoteParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Note arm
NoxiousGasCloudParticle.tick missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears
NoxiousGasCloudParticle.pickRandomParticleSpawnPoint missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears
NoxiousGasCloudParticle.spawnNoxiousGasParticle missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears
NoxiousGasParticle.tick missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears; the fade-out is missing with it
NoxiousGasParticle.Provider.createParticle missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears
NoxiousGasCloudParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle the `NoxiousGasCloud` arm: base tick, then every second tick a gas particle at a random spawn point that `canBeReachedByNoxiousGas` accepts
NoxiousGasCloudParticle.pickRandomParticleSpawnPoint port crates/horizon-client-render/src/particles.rs::pick_random_particle_spawn_point up to three blocks out in a random horizontal direction, a quarter of a block below the source block's centre, from the level's random
NoxiousGasCloudParticle.spawnNoxiousGasParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::spawn_noxious_gas_particle the gas is added always visible (`ClientLevel.addAlwaysVisibleParticle`), so the particle level does not thin it
NoxiousGasParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle the `NoxiousGas` arm: base tick and the sprite by age, then alpha falls from its value to 0 over the ticks past the fade start
NoxiousGasParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make the `NoxiousGas` arm: white, lifetime `6 / (0.5 to 1) * 3` ticks with the fade half way; the cloud arm lives 20 ticks and draws nothing
Particle.<init> port crates/horizon-client-render/src/particles.rs::Particle::base_at 4-arg constructor (box and lifetime draw); the 7-arg one is Particle::base_moving
Particle.toString skip:debug
Particle.setLocationFromBoundingbox port crates/horizon-client-render/src/particles.rs::Particle::set_location_from_bb
@@ -25,7 +25,7 @@ ParticleDescription.fromJson port crates/horizon-client-render/src/particles.rs:
ParticleEngine.makeParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make gap: minor: VibrationSignal options return None here; ParticleEngine::vibration creates them
ParticleEngine.createParticleGroup skip:other no render-type groups: ParticleEngine::render draws every particle in one pass, and the ITEM_PICKUP group is tick_pickups
ParticleEngine.countParticles skip:debug
ParticleEngine.hasSpaceInParticleLimit port crates/horizon-client-render/src/particles.rs (test spore_blossom_air_stops_at_its_particle_limit) per-type particle caps, pinned by that test
ParticleEngine.hasSpaceInParticleLimit port crates/horizon-client-render/src/particles.rs::ParticleEngine::create the only capped type is `ParticleLimit.SPORE_BLOSSOM`: a spore blossom air particle past `SPORE_BLOSSOM_LIMIT` (1000) is dropped in `create`, counted by scanning the live and queued particles instead of vanilla's counted map
ParticleEngine.clearParticles port crates/horizon-client-render/src/particles.rs::ParticleEngine::clear clears emitters, live and pending particles; no limit counts to clear
ParticleGroup.tickParticles port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick ticks live particles and drops the removed ones
ParticleGroup.tickParticle skip:debug crash-report wrapper around particle.tick
@@ -64,8 +64,8 @@ SnowflakeParticle.<init> inline crates/horizon-client-render/src/particles.rs::P
SnowflakeParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::Snowflake: base tick, sprite from age, drag 0.95/0.9/0.95
SnowflakeParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Snowflake branch: colour 0.923/0.964/0.999
SonicBoomParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Explosion|SonicBoom arm, SonicBoom branch: lifetime 16, size 1.5
SpellParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle gap: player: the spyglass check is missing: while scoping in first person, vanilla makes spell particles within 3 blocks of the eye fully transparent; Horizon only lerps alpha
SpellParticle.isCloseToScopingPlayer missing player: spell particles near the eye stay visible while the player looks through a spyglass in first person; vanilla hides them
SpellParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle the `Spell` arm: alpha 0 while the particle is close to the scoping eye (`close_to_scoping_player`), else eased back to the original alpha at 0.05
SpellParticle.isCloseToScopingPlayer port crates/horizon-client-render/src/particles.rs::ParticleEngine::close_to_scoping_player within three blocks of the local player's eye while it scopes in first person; the spell arms set alpha 0 there and ease back to the original alpha at 0.05
SpellParticle.InstantProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Effect|InstantEffect arm: colour and power from the option
SpellParticle.MobEffectProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make EntityEffect arm: colour and alpha from the option
SpellParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make uncoloured spell arm (Infested, TrialOmen, RaidOmen)
@@ -79,7 +79,7 @@ SulfurBubbleParticle.randomHorizontalWiggling inline crates/horizon-client-rende
SulfurBubbleParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make SulfurBubbles arm
SuspendedParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Underwater/CrimsonSpore/WarpedSpore/SporeBlossomAir arm: spawned 0.125 below the point
SuspendedParticle.CrimsonSporeProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make CrimsonSpore arm: gaussian drift, colour 0.9/0.4/0.5
SuspendedParticle.SporeBlossomAirProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make gap: minor: SporeBlossomAir arm; the SPORE_BLOSSOM particle limit (1000) is not applied
SuspendedParticle.SporeBlossomAirProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make SporeBlossomAir arm: spawned 0.125 below the point; the 1000 `SPORE_BLOSSOM` cap is applied in `ParticleEngine::create`
SuspendedParticle.UnderwaterProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Underwater arm: colour 0.4/0.4/0.7
SuspendedParticle.WarpedSporeProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make WarpedSpore arm: colour 0.1/0.1/0.3, size 0.001
SuspendedTownParticle.move port crates/horizon-client-render/src/particles.rs::Particle::move_by free_move path: moves the box without block collision
@@ -133,7 +133,7 @@ ClientLanguage.appendFrom port crates/horizon-client-core/src/lang.rs::Language:
LanguageManager.extractLanguages port crates/horizon-client-core/src/lang.rs::available lists the languages the packs declare
LanguageManager.onResourceManagerReload port crates/horizon-client/src/game/options.rs::Game::load_language gap: minor: no error screen when the translations are empty or missing
AnimationMetadataSection.calculateFrameSize port crates/horizon-assets/src/animation.rs::AnimationMetadata::frame_size
GuiSpriteScaling.NineSlice.validate missing minor: Horizon accepts nine-slice metadata whose borders cover the whole sprite (parse_nine_slice has no check)
GuiSpriteScaling.NineSlice.validate port crates/horizon-client-render/src/ui.rs::parse_nine_slice borders that meet leave no centre slice, so the metadata is dropped and the sprite draws as a plain stretch
GuiSpriteScaling.NineSlice.Border.unpackValue skip:java codec helper that collapses a uniform border to one number; Horizon stores four values
BlockStateDefinitions.createItemFrameFakeState skip:data block state definition for the map property; Horizon chooses the frame model by flags
BlockStateDefinitions.getItemFrameFakeState port crates/horizon-client/src/game/frames.rs::Game::frame_geometry gap: minor: picks the glow and map frame model by flags; no block state
1 HugeExplosionParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::HugeExplosion: age, sprite from age, removed at lifetime
12 NoteParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Note arm: colour from the pitch, friction 0.66, lifetime 6, size x1.5
13 NoteParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size Size::GrowIn over 32 ticks
14 NoteParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Note arm
15 NoxiousGasCloudParticle.tick missing port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears the `NoxiousGasCloud` arm: base tick, then every second tick a gas particle at a random spawn point that `canBeReachedByNoxiousGas` accepts
16 NoxiousGasCloudParticle.pickRandomParticleSpawnPoint missing port crates/horizon-client-render/src/particles.rs::pick_random_particle_spawn_point player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears up to three blocks out in a random horizontal direction, a quarter of a block below the source block's centre, from the level's random
17 NoxiousGasCloudParticle.spawnNoxiousGasParticle missing port crates/horizon-client-render/src/particles.rs::ParticleEngine::spawn_noxious_gas_particle player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears the gas is added always visible (`ClientLevel.addAlwaysVisibleParticle`), so the particle level does not thin it
18 NoxiousGasParticle.tick missing port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears; the fade-out is missing with it the `NoxiousGas` arm: base tick and the sprite by age, then alpha falls from its value to 0 over the ticks past the fade start
19 NoxiousGasParticle.Provider.createParticle missing port crates/horizon-client-render/src/particles.rs::ParticleEngine::make player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears the `NoxiousGas` arm: white, lifetime `6 / (0.5 to 1) * 3` ticks with the fade half way; the cloud arm lives 20 ticks and draws nothing
20 Particle.<init> port crates/horizon-client-render/src/particles.rs::Particle::base_at 4-arg constructor (box and lifetime draw); the 7-arg one is Particle::base_moving
21 Particle.toString skip:debug
22 Particle.setLocationFromBoundingbox port crates/horizon-client-render/src/particles.rs::Particle::set_location_from_bb
25 ParticleEngine.makeParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make gap: minor: VibrationSignal options return None here; ParticleEngine::vibration creates them
26 ParticleEngine.createParticleGroup skip:other no render-type groups: ParticleEngine::render draws every particle in one pass, and the ITEM_PICKUP group is tick_pickups
27 ParticleEngine.countParticles skip:debug
28 ParticleEngine.hasSpaceInParticleLimit port crates/horizon-client-render/src/particles.rs (test spore_blossom_air_stops_at_its_particle_limit) crates/horizon-client-render/src/particles.rs::ParticleEngine::create per-type particle caps, pinned by that test the only capped type is `ParticleLimit.SPORE_BLOSSOM`: a spore blossom air particle past `SPORE_BLOSSOM_LIMIT` (1000) is dropped in `create`, counted by scanning the live and queued particles instead of vanilla's counted map
29 ParticleEngine.clearParticles port crates/horizon-client-render/src/particles.rs::ParticleEngine::clear clears emitters, live and pending particles; no limit counts to clear
30 ParticleGroup.tickParticles port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick ticks live particles and drops the removed ones
31 ParticleGroup.tickParticle skip:debug crash-report wrapper around particle.tick
64 SnowflakeParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::Snowflake: base tick, sprite from age, drag 0.95/0.9/0.95
65 SnowflakeParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Snowflake branch: colour 0.923/0.964/0.999
66 SonicBoomParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Explosion|SonicBoom arm, SonicBoom branch: lifetime 16, size 1.5
67 SpellParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle gap: player: the spyglass check is missing: while scoping in first person, vanilla makes spell particles within 3 blocks of the eye fully transparent; Horizon only lerps alpha the `Spell` arm: alpha 0 while the particle is close to the scoping eye (`close_to_scoping_player`), else eased back to the original alpha at 0.05
68 SpellParticle.isCloseToScopingPlayer missing port crates/horizon-client-render/src/particles.rs::ParticleEngine::close_to_scoping_player player: spell particles near the eye stay visible while the player looks through a spyglass in first person; vanilla hides them within three blocks of the local player's eye while it scopes in first person; the spell arms set alpha 0 there and ease back to the original alpha at 0.05
69 SpellParticle.InstantProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Effect|InstantEffect arm: colour and power from the option
70 SpellParticle.MobEffectProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make EntityEffect arm: colour and alpha from the option
71 SpellParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make uncoloured spell arm (Infested, TrialOmen, RaidOmen)
79 SulfurBubbleParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make SulfurBubbles arm
80 SuspendedParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Underwater/CrimsonSpore/WarpedSpore/SporeBlossomAir arm: spawned 0.125 below the point
81 SuspendedParticle.CrimsonSporeProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make CrimsonSpore arm: gaussian drift, colour 0.9/0.4/0.5
82 SuspendedParticle.SporeBlossomAirProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make gap: minor: SporeBlossomAir arm; the SPORE_BLOSSOM particle limit (1000) is not applied SporeBlossomAir arm: spawned 0.125 below the point; the 1000 `SPORE_BLOSSOM` cap is applied in `ParticleEngine::create`
83 SuspendedParticle.UnderwaterProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Underwater arm: colour 0.4/0.4/0.7
84 SuspendedParticle.WarpedSporeProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make WarpedSpore arm: colour 0.1/0.1/0.3, size 0.001
85 SuspendedTownParticle.move port crates/horizon-client-render/src/particles.rs::Particle::move_by free_move path: moves the box without block collision
133 LanguageManager.extractLanguages port crates/horizon-client-core/src/lang.rs::available lists the languages the packs declare
134 LanguageManager.onResourceManagerReload port crates/horizon-client/src/game/options.rs::Game::load_language gap: minor: no error screen when the translations are empty or missing
135 AnimationMetadataSection.calculateFrameSize port crates/horizon-assets/src/animation.rs::AnimationMetadata::frame_size
136 GuiSpriteScaling.NineSlice.validate missing port crates/horizon-client-render/src/ui.rs::parse_nine_slice minor: Horizon accepts nine-slice metadata whose borders cover the whole sprite (parse_nine_slice has no check) borders that meet leave no centre slice, so the metadata is dropped and the sprite draws as a plain stretch
137 GuiSpriteScaling.NineSlice.Border.unpackValue skip:java codec helper that collapses a uniform border to one number; Horizon stores four values
138 BlockStateDefinitions.createItemFrameFakeState skip:data block state definition for the map property; Horizon chooses the frame model by flags
139 BlockStateDefinitions.getItemFrameFakeState port crates/horizon-client/src/game/frames.rs::Game::frame_geometry gap: minor: picks the glow and map frame model by flags; no block state
+3 -3
View File
@@ -128,7 +128,7 @@ DownloadedPackSource.onRecoveryFailure port crates/horizon-client/src/server_pac
DownloadedPackSource.waitForPackFeedback skip:java future over the feedback callback; replies go through ServerPacks::take_replies
ServerPackManager.markExistingPacksAsRemoved inline crates/horizon-client/src/server_packs.rs::ServerPacks::push packs with the same id are removed as ServerReplaced
ServerPackManager.pushPack port crates/horizon-client/src/server_packs.rs::ServerPacks::push declined prompt answers DECLINED
ServerPackManager.pushLocalPack missing player: a world's own resource pack (resources.zip in the save) is not loaded when the world opens
ServerPackManager.pushLocalPack port crates/horizon-client/src/server_packs.rs::ServerPacks::push_local the world's `resourcepacks/resources.zip` is pushed as a pack already on disk (`Download::Done`, no url or sha1); a declined prompt reports DECLINED
ServerPackManager.pushNewPack inline crates/horizon-client/src/server_packs.rs::ServerPacks::push adds the pack and accepts it when allowed
ServerPackManager.findPackInfo inline crates/horizon-client/src/server_packs.rs::ServerPacks::pop finds the live pack with the id
ServerPackManager.popPack port crates/horizon-client/src/server_packs.rs::ServerPacks::pop marks the pack ServerRemoved
@@ -139,9 +139,9 @@ ServerPackManager.updateDownloads port crates/horizon-client/src/server_packs.rs
AbstractSoundInstance.getOrResolve port crates/horizon-client-render/src/sound_engine.rs::SoundEngine::resolve event lookup, then a random sound from it
BeeSoundInstance.tick port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick Ticking::Bee follows the bee; pitch and volume from speed
DirectionalSoundInstance.setPosition port crates/horizon-client/src/game/end.rs::Game::tick_end gap: minor: the end flash plays without a position, so the 10-block offset toward the view is not kept
ElytraOnPlayerSoundInstance.tick missing player: no elytra wind loop (entity.elytra.flying) while gliding; gliding is silent
ElytraOnPlayerSoundInstance.tick port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick the `Elytra` arm: `item.elytra.flying` follows the player each tick, stops 20 ticks after gliding ends, volume `speed / 4` clamped, silent for a second then fading in over the next
EntityBoundSoundInstance.tick missing minor: a sound tied to an entity does not follow it; it stays at its start point
RidingEntitySoundInstance.tick missing player: no riding loop for a happy ghast or nautilus (HAPPY_GHAST_RIDING, NAUTILUS_RIDING)
RidingEntitySoundInstance.tick port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick the `RidingEntity` arm for `entity.happy_ghast.riding` and `entity.nautilus.riding`: stops when the ridden mob is gone or silent, volume 0 unless the underwater test matches and the speed is at least 0.01, else the clamped lerp
RidingMinecartSoundInstance.shoudlPlaySound port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick RidingMinecart arm; the predicate is always true for OldMinecartBehavior, Horizon's only minecart
SnifferSoundInstance.tick port crates/horizon-client/src/game/sniffers.rs::Game::tick_sniffer_effects gap: player: the digging sound plays once when digging starts; vanilla loops it while digging
Sound.Type.getByName port crates/horizon-client-render/src/sound_events.rs::parse_sound type file or event; other values reject the sound
1 EquipmentClientInfo.Layer.leatherDyeable skip:data equipment asset factory; Horizon reads the equipment JSON in armor_stands.rs
128 DownloadedPackSource.waitForPackFeedback skip:java future over the feedback callback; replies go through ServerPacks::take_replies
129 ServerPackManager.markExistingPacksAsRemoved inline crates/horizon-client/src/server_packs.rs::ServerPacks::push packs with the same id are removed as ServerReplaced
130 ServerPackManager.pushPack port crates/horizon-client/src/server_packs.rs::ServerPacks::push declined prompt answers DECLINED
131 ServerPackManager.pushLocalPack missing port crates/horizon-client/src/server_packs.rs::ServerPacks::push_local player: a world's own resource pack (resources.zip in the save) is not loaded when the world opens the world's `resourcepacks/resources.zip` is pushed as a pack already on disk (`Download::Done`, no url or sha1); a declined prompt reports DECLINED
132 ServerPackManager.pushNewPack inline crates/horizon-client/src/server_packs.rs::ServerPacks::push adds the pack and accepts it when allowed
133 ServerPackManager.findPackInfo inline crates/horizon-client/src/server_packs.rs::ServerPacks::pop finds the live pack with the id
134 ServerPackManager.popPack port crates/horizon-client/src/server_packs.rs::ServerPacks::pop marks the pack ServerRemoved
139 AbstractSoundInstance.getOrResolve port crates/horizon-client-render/src/sound_engine.rs::SoundEngine::resolve event lookup, then a random sound from it
140 BeeSoundInstance.tick port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick Ticking::Bee follows the bee; pitch and volume from speed
141 DirectionalSoundInstance.setPosition port crates/horizon-client/src/game/end.rs::Game::tick_end gap: minor: the end flash plays without a position, so the 10-block offset toward the view is not kept
142 ElytraOnPlayerSoundInstance.tick missing port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick player: no elytra wind loop (entity.elytra.flying) while gliding; gliding is silent the `Elytra` arm: `item.elytra.flying` follows the player each tick, stops 20 ticks after gliding ends, volume `speed / 4` clamped, silent for a second then fading in over the next
143 EntityBoundSoundInstance.tick missing minor: a sound tied to an entity does not follow it; it stays at its start point
144 RidingEntitySoundInstance.tick missing port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick player: no riding loop for a happy ghast or nautilus (HAPPY_GHAST_RIDING, NAUTILUS_RIDING) the `RidingEntity` arm for `entity.happy_ghast.riding` and `entity.nautilus.riding`: stops when the ridden mob is gone or silent, volume 0 unless the underwater test matches and the speed is at least 0.01, else the clamped lerp
145 RidingMinecartSoundInstance.shoudlPlaySound port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick RidingMinecart arm; the predicate is always true for OldMinecartBehavior, Horizon's only minecart
146 SnifferSoundInstance.tick port crates/horizon-client/src/game/sniffers.rs::Game::tick_sniffer_effects gap: player: the digging sound plays once when digging starts; vanilla loops it while digging
147 Sound.Type.getByName port crates/horizon-client-render/src/sound_events.rs::parse_sound type file or event; other values reject the sound
+16 -16
View File
@@ -48,30 +48,30 @@ ConfirmExperimentalFeaturesScreen.DetailsScreen.init port crates/horizon-client/
ConfirmExperimentalFeaturesScreen.DetailsScreen.repositionElements skip:ui layout only
ConfirmExperimentalFeaturesScreen.DetailsScreen.PackListEntry.extractContent port crates/horizon-client/src/title/confirm_experiments.rs::ConfirmExperimentsScreen::draw each entry's bold title and the flags under it
ConfirmExperimentalFeaturesScreen.DetailsScreen.PackListEntry.getNarration skip:ui narration text only
CreateWorldScreen.openFresh port crates/horizon-client/src/title.rs::Title::new_world gap: minor: no resource load for datapack registries before the screen
CreateWorldScreen.openFresh port crates/horizon-client/src/title.rs::Title::new_world the screen shows createWorld.preparing while the world creation data is read, then opens
CreateWorldScreen.testWorld skip:debug TW button, shown only when running in the IDE
CreateWorldScreen.openCreateWorldScreen skip:data loads datapack registries and world presets before the screen; Horizon reads data from tables
CreateWorldScreen.createFromExisting port crates/horizon-client/src/title/create_world.rs::WorldOptions::of_world gap: minor: no datapacks copied; name, seed and mode come from title.rs click_action arm 15
CreateWorldScreen.createFromExisting port crates/horizon-client/src/title/create_world.rs::WorldOptions::of_world name, seed and mode come from title.rs click_action arm 15; the old world's datapacks come from datapack::for_recreate
CreateWorldScreen.init port crates/horizon-client/src/title/create_world.rs::buttons tab bar and footer buttons; tabs are drawn by draw_create
CreateWorldScreen.repositionElements skip:ui layout only
CreateWorldScreen.onCreate port crates/horizon-client/src/title.rs::Title::create_action gap: minor: no experimental or deprecated-data warning before creating
CreateWorldScreen.createWorldAndCleanup inline crates/horizon-client/src/game/startup_menu.rs::StartupMenu::queue_entry gap: minor: temp datapack folder cleanup not modelled (no datapack copy)
CreateWorldScreen.createNewWorld port crates/horizon-client/src/game.rs::Game::open_world gap: minor: the folder is made in WorldRequest::prepare and no datapacks are copied in
CreateWorldScreen.createWorldAndCleanup inline crates/horizon-client/src/game/startup_menu.rs::StartupMenu::queue_entry the temp pack folder is removed once its packs are installed (game.rs) and when Create is cancelled (Title::cancel_create)
CreateWorldScreen.createNewWorld port crates/horizon-client/src/game.rs::Game::open_world the folder is made in WorldRequest::prepare; the picked data packs are installed into it before the world opens (datapack::install)
CreateWorldScreen.createLevelSettings port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare name, mode, difficulty, hardcore and commands written with the new level; gap: minor: debug world (spectator, peaceful) not modelled
CreateWorldScreen.keyPressed port crates/horizon-client/src/title.rs::confirm_key_size player: Enter with no button focused creates the world (Screenless Create mode falls through to create_action), as the screen does after its widgets decline the key
CreateWorldScreen.extractRenderState port crates/horizon-client/src/title/create_world.rs::Title::draw_create draws the footer separator
CreateWorldScreen.extractMenuBackground port crates/horizon-client/src/title/create_world.rs::Title::draw_create draws the tab header strip and menu background
CreateWorldScreen.getOrCreateTempDataPackDir port crates/horizon-client/src/title.rs:971 -> crate::datapack::temp_folder the Create screen's chosen packs live in a temp folder under options.pack_dir
CreateWorldScreen.getOrCreateTempDataPackDir port crates/horizon-client-render/src/datapack.rs::temp_folder the Create screen's chosen packs live in a temp folder kept in options.pack_dir, made on the first drop or Data Packs click
CreateWorldScreen.openExperimentsScreen port crates/horizon-client/src/title.rs::Title::click_action the Experiments button opens experiments::ExperimentsScreen with the selected packs' flags
CreateWorldScreen.openDataPackSelectionScreen port crates/horizon-client/src/title.rs::Mode::DataPacks the More tab's Data Packs button opens the pack selection screen, and Back reopens it (reopen_data_packs)
CreateWorldScreen.tryApplyNewDataPacks port crates/horizon-client/src/title.rs::creation_packs + preparing the chosen pack_dir and data_packs feed the worldgen read before the world is created
CreateWorldScreen.openDataPackSelectionScreen port crates/horizon-client/src/title.rs::Title::click_action the More tab's Data Packs button (arm 31) opens the pack selection screen on the temp folder; Back reopens it (reopen_data_packs)
CreateWorldScreen.tryApplyNewDataPacks port crates/horizon-client/src/title.rs::Title::offer_data_packs the chosen ids apply at once and the data is read again, or the experimental confirm comes first
CreateWorldScreen.applyNewPackConfig inline crates/horizon-client/src/title.rs::creation_packs the selected ids are applied at creation rather than validated into a separate config object
CreateWorldScreen.createDefaultLoadConfig skip:data builds the datapack load config; Horizon loads no datapack set at creation
CreateWorldScreen.removeTempDataPackDir inline crates/horizon-client/src/title.rs:831 the temp folder's cleanup on leaving the create screen
CreateWorldScreen.removeTempDataPackDir inline crates/horizon-client/src/title.rs::Title::cancel_create the temp folder goes when Create is cancelled; it is also removed once its packs are installed
CreateWorldScreen.copyBetweenDirs port crates/horizon-client-render/src/datapack.rs::copy_pack a dropped pack is copied into the temp folder
CreateWorldScreen.createNewWorldDirectory port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare makes the world folder and writes level.dat; gap: minor: datapack files not copied in
CreateWorldScreen.createTempDataPackDirFromExistingWorld port crates/horizon-client/src/title.rs:3644 Re-Create reopens the world's own data pack selection
CreateWorldScreen.getDataPackSelectionSettings inline crates/horizon-client/src/title.rs::Mode::Create { options } options.pack_dir and options.data_packs are the selection settings
CreateWorldScreen.createTempDataPackDirFromExistingWorld port crates/horizon-client-render/src/datapack.rs::for_recreate Re-Create copies the old world's packs into a temp folder and keeps its selection
CreateWorldScreen.getDataPackSelectionSettings inline crates/horizon-client/src/title.rs::Title::creation_packs options.pack_dir and options.data_packs are the selection settings the picker and the worldgen read open on
CreateWorldScreen.GameTab.<init> port crates/horizon-client/src/title/create_world.rs::buttons Game tab: name box, game mode, difficulty, allow cheats; gap: minor: vanilla's Experiments button sits on this tab in non-stable builds
CreateWorldScreen.MoreTab.openGameRulesScreen port crates/horizon-client/src/title.rs::Title::click_action Game Rules button (arm 29) over a copy of the rules, via RulesScreen::for_creation
CreateWorldScreen.WorldTab.<init> port crates/horizon-client/src/title/create_world.rs::buttons World tab: world type, customize, seed box (field 1), structures and bonus chest; gap: player: only Default and Superflat are offered, vanilla also offers Amplified and other presets
@@ -130,21 +130,21 @@ WorldCreationUiState.WorldTypeEntry.describePreset port crates/horizon-client/sr
WorldCreationUiState.WorldTypeEntry.isAmplified inline crates/horizon-client/src/title/create_world.rs::buttons the amplified preset's help tooltip on the world type cycle button
WorldOpenFlows.createFreshLevel port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare writes a fresh level.dat; gap: minor: vanilla uses it for the debug recreate and the demo world; no datapack dimensions
WorldOpenFlows.createWorldAccess port crates/horizon-client/src/save.rs::SessionLock::acquire takes the world's session lock as the access; gap: minor: no symlink warning
WorldOpenFlows.createLevelFromExistingSettings port crates/horizon-client/src/game.rs::Game::open_world starts the world with the given settings; gap: minor: no datapack repository is built
WorldOpenFlows.loadWorldStem port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare reads level data and generator settings; gap: minor: no datapack load or safe-mode retry
WorldOpenFlows.createLevelFromExistingSettings port crates/horizon-client/src/game.rs::Game::open_world starts the world with the given settings; the packs come from datapack::for_recreate and are installed before the world opens
WorldOpenFlows.loadWorldStem port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare reads level data and generator settings and discovers the world's packs (datapack::discover_with_features); gap: minor: no safe-mode retry when a pack fails to load
WorldOpenFlows.recreateWorldData port crates/horizon-client/src/title/create_world.rs::WorldOptions::of_world reads the old world's settings for Re-Create; title.rs click_action arm 15 fills name, seed and mode
WorldOpenFlows.loadWorldDataBlocking port crates/horizon-client/src/game/world_loading.rs::WorldLoader::finish waits for the load worker and returns its result
WorldOpenFlows.askForBackup port crates/horizon-client/src/title.rs::Title::play a world that needs its files fixed or comes from a newer version asks for a backup first (Mode::BackupBeforeUpgrade)
WorldOpenFlows.confirmWorldCreation skip:data lifecycle warning for experimental or deprecated data; Horizon has no such data
WorldOpenFlows.openWorld port crates/horizon-client/src/game/startup_menu.rs::StartupMenu::queue_entry Play (title.rs arm 10) queues the existing world for loading
WorldOpenFlows.openWorldLoadLevelData port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare gap: player: a level.dat that will not read (Level::read gives None) opens with a random seed and the folder name, not the Recover World Data screen
WorldOpenFlows.openWorldLoadLevelData port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare the level data is read by WorldRequest::prepare; a level.dat that does not read opens RecoverWorldDataScreen instead of the world (Title::play, Mode::Recover)
WorldOpenFlows.openWorldCheckVersionCompatibility port crates/horizon-client/src/title.rs::Title::play a world from another series opens the incompatible AlertScreen instead
WorldOpenFlows.createBackupAndOpenWorld port crates/horizon-client/src/title.rs::Title::start_backup the backup runs on its own thread; the world opens once it finishes (backup_then_open)
WorldOpenFlows.upgradeAndOpenWorld port crates/horizon-client/src/save.rs::upgrade_older_world runs the file fixes and upgrades on open; gap: player: no file-fix progress screen and no upgrade done, join now confirm
WorldOpenFlows.upgradeAndOpenWorld port crates/horizon-client/src/save.rs::upgrade_older_world runs the file fixes and upgrades on open under FileFixerProgressScreen (level_loading::draw_file_fixer, Cancel wired in game.rs); gap: player: no upgrade-done join-now confirm
WorldOpenFlows.tryFileFixAndReportErrors port crates/horizon-world/src/datafix/files.rs::fix_world_files runs the file fixer; gap: player: cancel, symlink and failed-cleanup alerts are not shown
WorldOpenFlows.openWorldLoadLevelStem port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare loads the level stem and generator; gap: minor: no safe-mode retry
WorldOpenFlows.openWorldCheckWorldStemCompatibility port crates/horizon-client/src/title.rs::Title::after_prompt an old customized or experimental world asks its own question before the load, the stem issue of Mode::BackupBeforeUpgrade
WorldOpenFlows.openWorldLoadBundledResourcePack missing player: the world's own resources.zip is not applied on open; only an old-world file fix moves it into resourcepacks (datafix fix 4772)
WorldOpenFlows.openWorldLoadBundledResourcePack inline crates/horizon-client/src/game/server_packs.rs::Game::server_packs_world_pack the world's pack goes in as a local pack and the world connects (game/integrated.rs); a pack that fails to load has no prompt
WorldOpenFlows.openWorldCheckDiskSpace port crates/horizon-client/src/title.rs::Title::after_prompt little space left opens the LowDiskSpace ConfirmScreen instead of the world
WorldOpenFlows.loadBundledResourcePack missing player: pushes the world's resources.zip as a local pack; Horizon has no such path
WorldOpenFlows.loadBundledResourcePack port crates/horizon-client/src/game/server_packs.rs::Game::server_packs_world_pack saves/<world>/resourcepacks/resources.zip is pushed as the world pack before the connect
WorldOpenFlows.promptBundledPackLoadFailure missing minor: prompt when the world's own pack fails to load; no such pack in Horizon
1 SocialInteractionsPlayerList.enableScissor skip:service social list (hidden and blocked players); Horizon has no Social Interactions screen
48 ConfirmExperimentalFeaturesScreen.DetailsScreen.repositionElements skip:ui layout only
49 ConfirmExperimentalFeaturesScreen.DetailsScreen.PackListEntry.extractContent port crates/horizon-client/src/title/confirm_experiments.rs::ConfirmExperimentsScreen::draw each entry's bold title and the flags under it
50 ConfirmExperimentalFeaturesScreen.DetailsScreen.PackListEntry.getNarration skip:ui narration text only
51 CreateWorldScreen.openFresh port crates/horizon-client/src/title.rs::Title::new_world gap: minor: no resource load for datapack registries before the screen the screen shows createWorld.preparing while the world creation data is read, then opens
52 CreateWorldScreen.testWorld skip:debug TW button, shown only when running in the IDE
53 CreateWorldScreen.openCreateWorldScreen skip:data loads datapack registries and world presets before the screen; Horizon reads data from tables
54 CreateWorldScreen.createFromExisting port crates/horizon-client/src/title/create_world.rs::WorldOptions::of_world gap: minor: no datapacks copied; name, seed and mode come from title.rs click_action arm 15 name, seed and mode come from title.rs click_action arm 15; the old world's datapacks come from datapack::for_recreate
55 CreateWorldScreen.init port crates/horizon-client/src/title/create_world.rs::buttons tab bar and footer buttons; tabs are drawn by draw_create
56 CreateWorldScreen.repositionElements skip:ui layout only
57 CreateWorldScreen.onCreate port crates/horizon-client/src/title.rs::Title::create_action gap: minor: no experimental or deprecated-data warning before creating
58 CreateWorldScreen.createWorldAndCleanup inline crates/horizon-client/src/game/startup_menu.rs::StartupMenu::queue_entry gap: minor: temp datapack folder cleanup not modelled (no datapack copy) the temp pack folder is removed once its packs are installed (game.rs) and when Create is cancelled (Title::cancel_create)
59 CreateWorldScreen.createNewWorld port crates/horizon-client/src/game.rs::Game::open_world gap: minor: the folder is made in WorldRequest::prepare and no datapacks are copied in the folder is made in WorldRequest::prepare; the picked data packs are installed into it before the world opens (datapack::install)
60 CreateWorldScreen.createLevelSettings port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare name, mode, difficulty, hardcore and commands written with the new level; gap: minor: debug world (spectator, peaceful) not modelled
61 CreateWorldScreen.keyPressed port crates/horizon-client/src/title.rs::confirm_key_size player: Enter with no button focused creates the world (Screenless Create mode falls through to create_action), as the screen does after its widgets decline the key
62 CreateWorldScreen.extractRenderState port crates/horizon-client/src/title/create_world.rs::Title::draw_create draws the footer separator
63 CreateWorldScreen.extractMenuBackground port crates/horizon-client/src/title/create_world.rs::Title::draw_create draws the tab header strip and menu background
64 CreateWorldScreen.getOrCreateTempDataPackDir port crates/horizon-client/src/title.rs:971 -> crate::datapack::temp_folder crates/horizon-client-render/src/datapack.rs::temp_folder the Create screen's chosen packs live in a temp folder under options.pack_dir the Create screen's chosen packs live in a temp folder kept in options.pack_dir, made on the first drop or Data Packs click
65 CreateWorldScreen.openExperimentsScreen port crates/horizon-client/src/title.rs::Title::click_action the Experiments button opens experiments::ExperimentsScreen with the selected packs' flags
66 CreateWorldScreen.openDataPackSelectionScreen port crates/horizon-client/src/title.rs::Mode::DataPacks crates/horizon-client/src/title.rs::Title::click_action the More tab's Data Packs button opens the pack selection screen, and Back reopens it (reopen_data_packs) the More tab's Data Packs button (arm 31) opens the pack selection screen on the temp folder; Back reopens it (reopen_data_packs)
67 CreateWorldScreen.tryApplyNewDataPacks port crates/horizon-client/src/title.rs::creation_packs + preparing crates/horizon-client/src/title.rs::Title::offer_data_packs the chosen pack_dir and data_packs feed the worldgen read before the world is created the chosen ids apply at once and the data is read again, or the experimental confirm comes first
68 CreateWorldScreen.applyNewPackConfig inline crates/horizon-client/src/title.rs::creation_packs the selected ids are applied at creation rather than validated into a separate config object
69 CreateWorldScreen.createDefaultLoadConfig skip:data builds the datapack load config; Horizon loads no datapack set at creation
70 CreateWorldScreen.removeTempDataPackDir inline crates/horizon-client/src/title.rs:831 crates/horizon-client/src/title.rs::Title::cancel_create the temp folder's cleanup on leaving the create screen the temp folder goes when Create is cancelled; it is also removed once its packs are installed
71 CreateWorldScreen.copyBetweenDirs port crates/horizon-client-render/src/datapack.rs::copy_pack a dropped pack is copied into the temp folder
72 CreateWorldScreen.createNewWorldDirectory port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare makes the world folder and writes level.dat; gap: minor: datapack files not copied in
73 CreateWorldScreen.createTempDataPackDirFromExistingWorld port crates/horizon-client/src/title.rs:3644 crates/horizon-client-render/src/datapack.rs::for_recreate Re-Create reopens the world's own data pack selection Re-Create copies the old world's packs into a temp folder and keeps its selection
74 CreateWorldScreen.getDataPackSelectionSettings inline crates/horizon-client/src/title.rs::Mode::Create { options } crates/horizon-client/src/title.rs::Title::creation_packs options.pack_dir and options.data_packs are the selection settings options.pack_dir and options.data_packs are the selection settings the picker and the worldgen read open on
75 CreateWorldScreen.GameTab.<init> port crates/horizon-client/src/title/create_world.rs::buttons Game tab: name box, game mode, difficulty, allow cheats; gap: minor: vanilla's Experiments button sits on this tab in non-stable builds
76 CreateWorldScreen.MoreTab.openGameRulesScreen port crates/horizon-client/src/title.rs::Title::click_action Game Rules button (arm 29) over a copy of the rules, via RulesScreen::for_creation
77 CreateWorldScreen.WorldTab.<init> port crates/horizon-client/src/title/create_world.rs::buttons World tab: world type, customize, seed box (field 1), structures and bonus chest; gap: player: only Default and Superflat are offered, vanilla also offers Amplified and other presets
130 WorldCreationUiState.WorldTypeEntry.isAmplified inline crates/horizon-client/src/title/create_world.rs::buttons the amplified preset's help tooltip on the world type cycle button
131 WorldOpenFlows.createFreshLevel port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare writes a fresh level.dat; gap: minor: vanilla uses it for the debug recreate and the demo world; no datapack dimensions
132 WorldOpenFlows.createWorldAccess port crates/horizon-client/src/save.rs::SessionLock::acquire takes the world's session lock as the access; gap: minor: no symlink warning
133 WorldOpenFlows.createLevelFromExistingSettings port crates/horizon-client/src/game.rs::Game::open_world starts the world with the given settings; gap: minor: no datapack repository is built starts the world with the given settings; the packs come from datapack::for_recreate and are installed before the world opens
134 WorldOpenFlows.loadWorldStem port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare reads level data and generator settings; gap: minor: no datapack load or safe-mode retry reads level data and generator settings and discovers the world's packs (datapack::discover_with_features); gap: minor: no safe-mode retry when a pack fails to load
135 WorldOpenFlows.recreateWorldData port crates/horizon-client/src/title/create_world.rs::WorldOptions::of_world reads the old world's settings for Re-Create; title.rs click_action arm 15 fills name, seed and mode
136 WorldOpenFlows.loadWorldDataBlocking port crates/horizon-client/src/game/world_loading.rs::WorldLoader::finish waits for the load worker and returns its result
137 WorldOpenFlows.askForBackup port crates/horizon-client/src/title.rs::Title::play a world that needs its files fixed or comes from a newer version asks for a backup first (Mode::BackupBeforeUpgrade)
138 WorldOpenFlows.confirmWorldCreation skip:data lifecycle warning for experimental or deprecated data; Horizon has no such data
139 WorldOpenFlows.openWorld port crates/horizon-client/src/game/startup_menu.rs::StartupMenu::queue_entry Play (title.rs arm 10) queues the existing world for loading
140 WorldOpenFlows.openWorldLoadLevelData port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare gap: player: a level.dat that will not read (Level::read gives None) opens with a random seed and the folder name, not the Recover World Data screen the level data is read by WorldRequest::prepare; a level.dat that does not read opens RecoverWorldDataScreen instead of the world (Title::play, Mode::Recover)
141 WorldOpenFlows.openWorldCheckVersionCompatibility port crates/horizon-client/src/title.rs::Title::play a world from another series opens the incompatible AlertScreen instead
142 WorldOpenFlows.createBackupAndOpenWorld port crates/horizon-client/src/title.rs::Title::start_backup the backup runs on its own thread; the world opens once it finishes (backup_then_open)
143 WorldOpenFlows.upgradeAndOpenWorld port crates/horizon-client/src/save.rs::upgrade_older_world runs the file fixes and upgrades on open; gap: player: no file-fix progress screen and no upgrade done, join now confirm runs the file fixes and upgrades on open under FileFixerProgressScreen (level_loading::draw_file_fixer, Cancel wired in game.rs); gap: player: no upgrade-done join-now confirm
144 WorldOpenFlows.tryFileFixAndReportErrors port crates/horizon-world/src/datafix/files.rs::fix_world_files runs the file fixer; gap: player: cancel, symlink and failed-cleanup alerts are not shown
145 WorldOpenFlows.openWorldLoadLevelStem port crates/horizon-client/src/game/world_loading.rs::WorldRequest::prepare loads the level stem and generator; gap: minor: no safe-mode retry
146 WorldOpenFlows.openWorldCheckWorldStemCompatibility port crates/horizon-client/src/title.rs::Title::after_prompt an old customized or experimental world asks its own question before the load, the stem issue of Mode::BackupBeforeUpgrade
147 WorldOpenFlows.openWorldLoadBundledResourcePack missing inline crates/horizon-client/src/game/server_packs.rs::Game::server_packs_world_pack player: the world's own resources.zip is not applied on open; only an old-world file fix moves it into resourcepacks (datafix fix 4772) the world's pack goes in as a local pack and the world connects (game/integrated.rs); a pack that fails to load has no prompt
148 WorldOpenFlows.openWorldCheckDiskSpace port crates/horizon-client/src/title.rs::Title::after_prompt little space left opens the LowDiskSpace ConfirmScreen instead of the world
149 WorldOpenFlows.loadBundledResourcePack missing port crates/horizon-client/src/game/server_packs.rs::Game::server_packs_world_pack player: pushes the world's resources.zip as a local pack; Horizon has no such path saves/<world>/resourcepacks/resources.zip is pushed as the world pack before the connect
150 WorldOpenFlows.promptBundledPackLoadFailure missing minor: prompt when the world's own pack fails to load; no such pack in Horizon
+1 -1
View File
@@ -113,7 +113,7 @@ Dolphin.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_
Dolphin.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt GotFish and Moistness, 2400 when absent
Dolphin.registerGoals port crates/horizon-client/src/mobs/aquatic.rs::register gap: player: no MoveToItemGoal and PlayWithItemsGoal (dolphins ignore items thrown in the water), no FollowPlayerRiddenEntityGoal for boats or nautiluses, no AvoidEntity of guardians, and HurtByTarget does not skip guardians
Dolphin.createAttributes skip:data attributes come from the entity tables
Dolphin.canAttack missing minor: a baby dolphin that is hurt can still pick the attacker as target
Dolphin.canAttack port crates/horizon-client/src/mobs/goals.rs::spares_target a baby dolphin takes nobody as its target
Dolphin.pickUpItem port crates/horizon-client/src/game/foxes.rs::Game::tick_dolphins the game finds the first allowed item in the water within 8 blocks and takes it
Dolphin.handleEntityEvent port crates/horizon-client-render/src/particles.rs::entity_event_burst event 38: seven happy villager particles about the dolphin
Dolphin.closeToNextPos inline crates/horizon-client/src/mobs/dolphin.rs::SwimToTreasure::tick nav target within 12 blocks (144 squared) of the dolphin
1 MemoryMap.equals skip:java map equality of a built memory set
113 Dolphin.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt GotFish and Moistness, 2400 when absent
114 Dolphin.registerGoals port crates/horizon-client/src/mobs/aquatic.rs::register gap: player: no MoveToItemGoal and PlayWithItemsGoal (dolphins ignore items thrown in the water), no FollowPlayerRiddenEntityGoal for boats or nautiluses, no AvoidEntity of guardians, and HurtByTarget does not skip guardians
115 Dolphin.createAttributes skip:data attributes come from the entity tables
116 Dolphin.canAttack missing port crates/horizon-client/src/mobs/goals.rs::spares_target minor: a baby dolphin that is hurt can still pick the attacker as target a baby dolphin takes nobody as its target
117 Dolphin.pickUpItem port crates/horizon-client/src/game/foxes.rs::Game::tick_dolphins the game finds the first allowed item in the water within 8 blocks and takes it
118 Dolphin.handleEntityEvent port crates/horizon-client-render/src/particles.rs::entity_event_burst event 38: seven happy villager particles about the dolphin
119 Dolphin.closeToNextPos inline crates/horizon-client/src/mobs/dolphin.rs::SwimToTreasure::tick nav target within 12 blocks (144 squared) of the dolphin
+4 -4
View File
@@ -89,7 +89,7 @@ Level.addBlockEntityTicker port crates/horizon-client/src/game.rs::GameLevel::lo
Level.guardEntityTick skip:debug crash-report wrapper around an entity tick
Level.getBlockEntity skip:other bare block_entities map lookup at each caller; no port of its own
Level.setBlockEntity port crates/horizon-client/src/game.rs::GameLevel::load_block_entity
Level.loadedAndEntityCanStandOnFace missing unsure: no face-sturdy test for placed hanging entities found
Level.loadedAndEntityCanStandOnFace inline crates/horizon-client/src/mobs/shulker.rs::can_stay_at the supporting block's face toward the shell is tested for sturdiness (`BlockState.entityCanStandOnFace`) before the opened shell's box; the chunk is loaded by construction where the sim asks
Level.getWorldBorderAdjustedRespawnData missing unsure: respawn clamp to the world border not found
Level.hasEntities missing unsure: no entity-in-box test found for this use
Level.getClockTimeTicks port crates/horizon-client/src/game.rs::GameLevel::day_time day time; no separate clock time
@@ -99,7 +99,7 @@ Level.setRainLevel port crates/horizon-client/src/game/remote.rs::Game::handle_p
Level.fillReportDetails skip:debug crash-report details
BlockGetter.getBlockEntity skip:other bare block_entities map lookup at each caller
BlockGetter.isBlockInLine skip:other no caller in 26.3 code
BlockGetter.clipWithInteractionOverride missing unsure: no interaction-shape override found for the pick ray; clip_ray not checked in depth
BlockGetter.clipWithInteractionOverride port crates/horizon-world/src/world_ray.rs::clip_with_interaction the block's interaction shape is clipped too, and when it lands nearer the hit takes its direction
BlockGetter.traverseBlocks port crates/horizon-core/src/geometry/traversal.rs::traverse_blocks
BlockGetter.forEachBlockIntersectedBetween port crates/horizon-core/src/geometry/sweep.rs::sweep_intersected_blocks
BlockGetter.addCollisionsAlongTravel inline crates/horizon-client-render/src/collisions.rs::SceneCollisions::resolve_with_context gap: minor: the shape list along the path is built inside the resolver
@@ -112,7 +112,7 @@ ChunkPos.getRegionZ port crates/horizon-core/src/position.rs::ChunkPos::region
ChunkPos.getBlockAt skip:other no caller in 26.3 code
ChunkPos.contains port crates/horizon-core/src/position.rs::BlockPos::chunk a block is in a chunk when its chunk() is that chunk
ChunkPos.getChessboardDistance inline crates/horizon-client/src/ticket_levels.rs::compute Chebyshev distance folded into the ticket level loop
ChunkPos.distanceSquared missing unsure: caller not identified in 26.3 sources; no port located
ChunkPos.distanceSquared inline crates/horizon-client/src/lan/hub.rs::requeue_ring the pending chunk queue is sorted by the squared chunk distance, the only use vanilla makes of it (PlayerChunkSender.collectChunksToSend)
CollisionGetter.isUnobstructed port crates/horizon-client/src/game.rs::GameLevel::is_unobstructed the state-based overload
CollisionGetter.noBorderCollision port crates/horizon-client-render/src/collisions.rs::EntityCollisions::movement_border border shape when inside close to the border
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
@@ -132,7 +132,7 @@ LevelReader.getBlockStatesIfLoaded port crates/horizon-client-render/src/world.r
LevelReader.getNoiseBiome skip:other no caller in 26.3 code
LevelReader.getHeightmapPos port crates/horizon-client-render/src/world.rs::World::world_surface gap: minor: the height only; the BlockPos is built by the caller
LevelReader.getChunk missing unsure: same as Level.getChunk
LevelReader.findBlocksInBoxByManhattanDistance missing unsure: no block search by Manhattan distance found in Horizon
LevelReader.findBlocksInBoxByManhattanDistance inline crates/horizon-client/src/mobs/bee.rs::manhattan_box the box in vanilla's order (`BlockPos.withinBoxByManhattanDistance`); the first-match search over it is inlined at each caller, e.g. Pollinate::find_flower for findNearbyFlower
LevelWriter.destroyBlock port crates/horizon-world/src/level/update.rs::Sim::destroy_block
LocalMobCapCalculator.getPlayersNear port crates/horizon-client/src/mobs/spawn_selection.rs::SpawnState::players_near
LocalMobCapCalculator.addMob port crates/horizon-client/src/mobs/spawn_selection.rs::SpawnState::add_local
1 Enchantment.getSlotItems inline crates/horizon-world/src/loot.rs::LootData::enchantment_level slot filter and item lookup folded into the best-level loop
89 Level.guardEntityTick skip:debug crash-report wrapper around an entity tick
90 Level.getBlockEntity skip:other bare block_entities map lookup at each caller; no port of its own
91 Level.setBlockEntity port crates/horizon-client/src/game.rs::GameLevel::load_block_entity
92 Level.loadedAndEntityCanStandOnFace missing inline crates/horizon-client/src/mobs/shulker.rs::can_stay_at unsure: no face-sturdy test for placed hanging entities found the supporting block's face toward the shell is tested for sturdiness (`BlockState.entityCanStandOnFace`) before the opened shell's box; the chunk is loaded by construction where the sim asks
93 Level.getWorldBorderAdjustedRespawnData missing unsure: respawn clamp to the world border not found
94 Level.hasEntities missing unsure: no entity-in-box test found for this use
95 Level.getClockTimeTicks port crates/horizon-client/src/game.rs::GameLevel::day_time day time; no separate clock time
99 Level.fillReportDetails skip:debug crash-report details
100 BlockGetter.getBlockEntity skip:other bare block_entities map lookup at each caller
101 BlockGetter.isBlockInLine skip:other no caller in 26.3 code
102 BlockGetter.clipWithInteractionOverride missing port crates/horizon-world/src/world_ray.rs::clip_with_interaction unsure: no interaction-shape override found for the pick ray; clip_ray not checked in depth the block's interaction shape is clipped too, and when it lands nearer the hit takes its direction
103 BlockGetter.traverseBlocks port crates/horizon-core/src/geometry/traversal.rs::traverse_blocks
104 BlockGetter.forEachBlockIntersectedBetween port crates/horizon-core/src/geometry/sweep.rs::sweep_intersected_blocks
105 BlockGetter.addCollisionsAlongTravel inline crates/horizon-client-render/src/collisions.rs::SceneCollisions::resolve_with_context gap: minor: the shape list along the path is built inside the resolver
112 ChunkPos.getBlockAt skip:other no caller in 26.3 code
113 ChunkPos.contains port crates/horizon-core/src/position.rs::BlockPos::chunk a block is in a chunk when its chunk() is that chunk
114 ChunkPos.getChessboardDistance inline crates/horizon-client/src/ticket_levels.rs::compute Chebyshev distance folded into the ticket level loop
115 ChunkPos.distanceSquared missing inline crates/horizon-client/src/lan/hub.rs::requeue_ring unsure: caller not identified in 26.3 sources; no port located the pending chunk queue is sorted by the squared chunk distance, the only use vanilla makes of it (PlayerChunkSender.collectChunksToSend)
116 CollisionGetter.isUnobstructed port crates/horizon-client/src/game.rs::GameLevel::is_unobstructed the state-based overload
117 CollisionGetter.noBorderCollision port crates/horizon-client-render/src/collisions.rs::EntityCollisions::movement_border border shape when inside close to the border
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
132 LevelReader.getNoiseBiome skip:other no caller in 26.3 code
133 LevelReader.getHeightmapPos port crates/horizon-client-render/src/world.rs::World::world_surface gap: minor: the height only; the BlockPos is built by the caller
134 LevelReader.getChunk missing unsure: same as Level.getChunk
135 LevelReader.findBlocksInBoxByManhattanDistance missing inline crates/horizon-client/src/mobs/bee.rs::manhattan_box unsure: no block search by Manhattan distance found in Horizon the box in vanilla's order (`BlockPos.withinBoxByManhattanDistance`); the first-match search over it is inlined at each caller, e.g. Pollinate::find_flower for findNearbyFlower
136 LevelWriter.destroyBlock port crates/horizon-world/src/level/update.rs::Sim::destroy_block
137 LocalMobCapCalculator.getPlayersNear port crates/horizon-client/src/mobs/spawn_selection.rs::SpawnState::players_near
138 LocalMobCapCalculator.addMob port crates/horizon-client/src/mobs/spawn_selection.rs::SpawnState::add_local
+14 -3
View File
@@ -18,7 +18,9 @@ It only grows: the last line is the current version, and a change to the output
The digest covers every file of the preparation, canonicalised where the preparer is not deterministic between
runs (verified across the CI runners): JSON is compared as data (key order, the order of each `tags` list of
creative-inventory.json, and the addresses in `$$Lambda/0x...` class names don't count); logs, compiled classes,
the sounds (copied from the player's launcher by another step), the stamp naming the OUTPUT_VERSION
the sounds, the languages but the JAR's and the asset index (copied from the player's launcher by another step; CI
clones the languages into a preparation after the fact), the game's run-time caches (horizon-sprites.cache,
horizon-data.bin), the stamp naming the OUTPUT_VERSION
(horizon-prepare.json), the lock file (.horizon-prepare.lock) and the manifest's file hashes (raw bytes) don't count. The manifest's own scalar fields (schema, data version, jar hashes, file count) do.
"""
@@ -41,6 +43,12 @@ MANIFEST = 'horizon-resources.json'
STAMP = 'horizon-prepare.json'
# The empty file a preparation locks while it writes (crates/horizon-prepare/src/lib.rs, LOCK).
LOCK = '.horizon-prepare.lock'
# Written into a preparation by something other than the preparer's export: the sounds and languages step
# (crates/horizon-prepare/src/sounds.rs) and, at run time, the game's caches (horizon-client-render's sprite_cache.rs,
# horizon-core's prepared_files.rs). Whether a harness ran the game before the check must not change the digest.
DATA_FILE = 'horizon-data.bin'
FROM_ELSEWHERE = ('assets/minecraft/sounds.json', 'assets/pack.mcmeta', 'asset-index.json', 'horizon-sprites.cache',
DATA_FILE, STAMP, LOCK)
LAMBDA = re.compile(r'\$\$Lambda/0x[0-9a-fA-F]+')
CONSTANT = re.compile(r'^pub const OUTPUT_VERSION: u32 = (\d+);', re.M)
@@ -74,8 +82,11 @@ def canonical(rel: str, data: bytes) -> bytes:
def skipped(rel: str) -> bool:
parts = rel.split('/')
return rel.endswith(('.log', '.class')) or parts[:3] == ['assets', 'minecraft', 'sounds'] \
or rel in ('assets/minecraft/sounds.json', STAMP, LOCK)
# The languages but the client JAR's own come from the launcher's asset store like the sounds; CI clones them
# into a preparation after the fact (tools/ci/vanilla-data.sh).
languages = parts[:3] == ['assets', 'minecraft', 'lang'] and parts[3:] not in (['en_us.json'], ['deprecated.json'])
return rel.endswith(('.log', '.class')) or parts[:3] == ['assets', 'minecraft', 'sounds'] or languages \
or rel in FROM_ELSEWHERE or rel.startswith(f'{DATA_FILE}.')
def digest(resources: Path) -> tuple[str, int]: