Compare commits

...
Author SHA1 Message Date
rdbt b880384691 Merge pull request #15161 from horizon-rust/fix/parity-revert-13953 2026-11-11 10:00:11 +00:00
Ludovic Marie 77a34310b0 Merge pull request #15159 from horizon-rust/governance/codeowners-council
🤖 Agent: CODEOWNERS moves from @ludovic111 to @horizon-rust/council, as the council decided (P35); the team exists with the six members and write access; reviewer tests pass. Paths guarded by /nightly are unchanged.
2026-11-11 10:39:41 +01:00
Horizon agentandClaude Opus 5.5 7627df18b0 CODEOWNERS: the council team reviews release paths (P35)
The council decided (P35, 2026-11-10) that code-owner reviews move from the
founder to a GitHub team of the six council members. The team
horizon-rust/council exists and has write access, which code owners need.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-11 10:17:07 +01:00
Horizon agent ee66246131 Merge remote-tracking branch 'origin/main' into fix/parity-revert-13953
# Conflicts:
#	tools/parity_hosts.json
2026-11-11 10:06:20 +01:00
Horizon agentandClaude Opus 5.5 766a5d4c87 Parity: container_screen's oracle skips the first release; stand_lodestone is named-only
container_screen: the oracle makes its screens with Unsafe.allocateInstance,
which skips AbstractContainerScreen's constructor and so its
`skipNextRelease = true`. #13723 made Horizon's Clicks start that way,
as the real screen does, and the oracle then disagreed on every case
whose first event is a release (373 cases). The oracle now sets the
field the constructor would.

stand_lodestone needs --work-dir, a frozen Java run (--reference-dir)
and a built game (--binary), which an automatic run never passes, so it
failed in 0 s on every runner. It joins the other capture-preserving
harnesses in parity_hosts.json's named_only: it runs when named, and
in the nightly audit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-11 10:06:01 +01:00
Horizon agentandClaude Opus 5.5 938dcd673c Revert #13953: entity ticking waits for chunks two columns away
This reverts merge 5248e83f28 (#13953).

Since it merged (2026-11-09 15:51Z), entities in the command_exec
fixture world stop ticking: they no longer fall, die, despawn or pick
items up, and command_exec fails 159/184 (it passes at the merge's
parent, and fails at the merge, both run here). Every PR whose change
selects command_exec has failed parity since.

Vanilla's gate is right, but Horizon does not keep the ring around a
forceloaded or ticketed chunk resident the way vanilla's ticket levels
do (a FORCED ticket gives its neighbours FULL), so the residency check
holds back chunks vanilla ticks. #13852 needs that ring modelled first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-10 14:02:07 +01:00
6 changed files with 23 additions and 85 deletions

No files matched your search

+12 -12
View File
@@ -1,16 +1,16 @@
# Changes to these files wait for Ludovic's review once branch protection requires code owners' reviews
# Changes to these files wait for a council member's review (council poll P35, 2026-11-10) once branch protection requires code owners' reviews
# (tools/ci/protect_pipeline.sh, docs/security.md). They are what builds, signs and ships releases, the code that
# checks downloads before installing them (backend.rs downloads, installs and updates the launcher itself; links.json
# says where from; the Cargo.toml files pick the crates that do it), and this file. Everything else merges as soon as
# `verify` passes.
/.github/ @ludovic111
/tools/ci/ @ludovic111
/tools/release_train.py @ludovic111
/crates/horizon-release-sign/ @ludovic111
/crates/horizon-server/src/update.rs @ludovic111
/crates/horizon-server/Cargo.toml @ludovic111
/launcher/src/signed.rs @ludovic111
/launcher/src/backend.rs @ludovic111
/launcher/links.json @ludovic111
/launcher/Cargo.toml @ludovic111
/packaging/ @ludovic111
/.github/ @horizon-rust/council
/tools/ci/ @horizon-rust/council
/tools/release_train.py @horizon-rust/council
/crates/horizon-release-sign/ @horizon-rust/council
/crates/horizon-server/src/update.rs @horizon-rust/council
/crates/horizon-server/Cargo.toml @horizon-rust/council
/launcher/src/signed.rs @horizon-rust/council
/launcher/src/backend.rs @horizon-rust/council
/launcher/links.json @horizon-rust/council
/launcher/Cargo.toml @horizon-rust/council
/packaging/ @horizon-rust/council
+2 -2
View File
@@ -1146,8 +1146,8 @@ impl Game<'_> {
/// `DistanceManager.inEntityTickingRange` in an entity-ticking chunk
/// (block coordinates), from every player's tickets (`update_chunk_tickets`).
pub(super) fn entity_ticking_area(&self) -> impl Fn(i32, i32) -> bool + use<> {
self.tickets
.entity_ticking_area(&self.world.chunks, self.remote.is_none())
let levels = self.tickets.levels();
move |x, z| levels.entity_ticking_at(x, z)
}
/// The entity phase of `tick_creatures`: every mob's tick, deaths, and the
/// projectiles and effects that follow from them.
+4 -34
View File
@@ -1351,16 +1351,10 @@ mod tests {
)
.unwrap();
game.options.dedicated = Some(Vec::new());
// Entity ticking waits for every chunk within 2 columns
// (`ChunkMap.prepareEntityTickingChunk`).
for z in -2..=2 {
for x in -2..=2 {
game.world.chunks.insert(
(x, z),
std::sync::Arc::new(horizon_client::chunk::Chunk::empty(x, z)),
);
}
}
game.world.chunks.insert(
(0, 0),
std::sync::Arc::new(horizon_client::chunk::Chunk::empty(0, 0)),
);
game.streaming.forced.insert((0, 0));
Some(game)
}
@@ -1847,30 +1841,6 @@ mod tests {
game.particles.requested.as_ref().unwrap().len()
}
/// #13852: a chunk enters entity ticking only once every chunk within 2
/// columns is loaded (`ChunkMap.prepareEntityTickingChunk`), so TNT beside
/// a missing chunk keeps its fuse until the neighbourhood is whole.
#[test]
fn tnt_waits_while_a_chunk_two_columns_away_is_missing() {
let Some(mut game) = server_game() else {
return;
};
game.world.chunks.remove(&(2, 0));
game.add_primed_tnt(BlockPos::new(8, 64, 8), 0.0, 80, None, None);
game.tnts[0].no_gravity = true;
game.tnts[0].velocity = Vec3::ZERO;
for _ in 0..3 {
game.tick_world(true).unwrap();
}
assert_eq!(game.tnts[0].fuse, 80, "no entity ticking yet");
game.world.chunks.insert(
(2, 0),
std::sync::Arc::new(horizon_client::chunk::Chunk::empty(2, 0)),
);
game.tick_world(true).unwrap();
assert!(game.tnts[0].fuse < 80, "ticks once the chunk is back");
}
/// #10063: TNT lit between level ticks (flint and steel, a command) has its
/// first tick in the next level tick, so its fuse reads 79 after it, and it
/// explodes on the 80th (`PrimedTnt.tick`). Horizon held every TNT that
@@ -186,10 +186,6 @@ impl Levels {
fn get(&self, x: i32, z: i32) -> Option<(u8, u8)> {
Some(self.0.as_ref()?.get(x, z))
}
/// Whether any ticket exists; with none, everything ticks.
pub fn has_tickets(&self) -> bool {
self.0.is_some()
}
/// `DistanceManager.inEntityTickingRange`, in an entity-ticking chunk:
/// where entities tick.
pub fn entity_ticking(&self, x: i32, z: i32) -> bool {
@@ -256,41 +252,9 @@ pub struct ChunkTickets {
sources: Option<Sources>,
levels: Levels,
portal: Vec<PortalTicket>,
/// The resident chunk keys at one membership revision.
resident: std::sync::Mutex<(u64, Arc<crate::fast_hash::FxHashSet<(i32, i32)>>)>,
}
impl ChunkTickets {
/// `Levels::entity_ticking_at`, held back as `ChunkMap.prepareEntityTickingChunk`
/// does: a chunk waits until every chunk within 2 columns is loaded. With no
/// tickets everything ticks.
pub fn entity_ticking_area(
&self,
chunks: &crate::chunk_map::ChunkMap,
gate: bool,
) -> impl Fn(i32, i32) -> bool + use<> {
let levels = self.levels.clone();
let resident = (gate && levels.0.is_some()).then(|| {
let revision = chunks.membership_revision();
let mut cache = self.resident.lock().unwrap_or_else(|e| e.into_inner());
if cache.0 != revision {
*cache = (revision, Arc::new(chunks.keys().copied().collect()));
}
cache.1.clone()
});
// Callers ask once per entity: remember each chunk's answer, so an
// entity costs one lookup instead of the 25 of its neighbourhood.
let surrounded = std::cell::RefCell::new(crate::fast_hash::FxHashMap::default());
move |x, z| {
levels.entity_ticking_at(x, z)
&& resident.as_ref().is_none_or(|keys| {
let (cx, cz) = (x >> 4, z >> 4);
*surrounded.borrow_mut().entry((cx, cz)).or_insert_with(|| {
(cz - 2..=cz + 2).all(|z| (cx - 2..=cx + 2).all(|x| keys.contains(&(x, z))))
})
})
}
}
/// `None`: tick everywhere.
pub fn update(&mut self, mut sources: Option<Sources>) {
// The game submits a fresh `Sources` value every tick even when no
@@ -136,6 +136,9 @@ public final class VanillaContainerScreenOracle {
put(screen, Screen.class, "children", new ArrayList<>());
put(screen, AbstractContainerScreen.class, "quickCraftSlots", new HashSet<Slot>());
put(screen, AbstractContainerScreen.class, "lastQuickMoved", ItemStack.EMPTY);
// alloc skips the constructor, which sets skipNextRelease = true:
// a fresh screen swallows a release with no press before it.
put(screen, AbstractContainerScreen.class, "skipNextRelease", true);
if (c.has("book")) {
var book = c.getAsJsonArray("book");
var component = alloc(CraftingRecipeBookComponent.class);
+2 -1
View File
@@ -75,7 +75,8 @@
"unused_feature_startup": "capture-preserving harness: needs --kind, --output (a new capture folder) and --binary, which an automatic run doesn't pass, so it fails at startup whatever changed",
"warden_owned_memories": "capture-preserving harness: writes .local/h029/capture and asserts it is new, so it fails at startup on any checkout that ran it before (the Mac mini runner)",
"shulker_lock_drop": "capture-preserving harness: needs --work-dir (a fresh capture folder) and --reference-dir, which an automatic run doesn't pass, so it fails at startup whatever changed",
"pot_hopper": "capture-preserving harness: needs --work-dir (a fresh capture folder) and --reference-dir, which an automatic run doesn't pass, so it fails at startup whatever changed"
"pot_hopper": "capture-preserving harness: needs --work-dir (a fresh capture folder) and --reference-dir, which an automatic run doesn't pass, so it fails at startup whatever changed",
"stand_lodestone": "capture-preserving harness: needs --work-dir (a new capture folder), --reference-dir and --binary, which an automatic run doesn't pass, so it fails at startup whatever changed"
},
"known_flaky": {
"world_reopen": "counted 4 minecarts instead of 3 once on a loaded Mac mini runner, then passed at the same head (10-26)",