Merge pull request #12699 from RLC248/fix/12520-sculk-loaded-state

🤖 Agent: sculk placement finishes before command/item entity tags load; sculk_saved_fields harness added.
This commit is contained in:
Ludovic Marie authored and GitHub committed 2026-11-08 22:09:01 +01:00
commit 81403ce81c
4 files changed
+135

No files matched your search

@@ -323,6 +323,8 @@ impl Game<'_> {
) {
let key = (pos[0], pos[1], pos[2]);
self.pending.banner.remove(&pos);
let state = self.world.block(pos[0], pos[1], pos[2]);
self.sculk_place_entity(state);
let Some(saved) = self.saved_block_entity(key) else {
return;
};
@@ -2741,6 +2741,7 @@ impl Game<'_> {
return Ok(false);
}
self.ensure_stateful_entity_table();
self.sculk_prepare();
// A different block replaces the block entity (Level.setBlock).
let (old, new) = (
self.res.facts().get(previous).name.clone(),
@@ -2784,6 +2785,7 @@ impl Game<'_> {
let needs_entity = !self.world.block_entities.stateful_entity(landed.0) && old != new;
if needs_entity
&& self.res.facts().get(landed).name == new
&& !self.sculk_place_entity(landed)
&& let Some(entity) = self.new_block_entity_for_state(landed)
{
self.world.block_entities.insert(key, entity);
+15
View File
@@ -474,6 +474,21 @@ impl Game<'_> {
}
self.sculk.known = loaded;
}
self.sculk_sync_writes();
}
/// `LevelChunk.setBlockState`: finish sculk placement before a command
/// or `BlockItem.updateCustomBlockEntityTag` loads saved entity fields.
pub(super) fn sculk_place_entity(&mut self, state: BlockStateId) -> bool {
self.sculk_prepare();
if self.kind_of_state(state.0) == 0 {
return false;
}
self.sculk_sync_writes();
true
}
fn sculk_sync_writes(&mut self) {
let writes = std::mem::take(&mut self.world.watched_writes);
for (key, old, new) in writes {
let (old_kind, new_kind) = (self.kind_of_state(old), self.kind_of_state(new));
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""Sculk placement tags survive ticking and chunk reload in both games."""
from command_exec_corpus import cmd, group_setup, setup
import harness_args
import test_command_exec_parity as driver
def blocks(x, y, z):
return [
(f'{x + 3} {y} {z}', 'sculk_sensor', 'last_vibration_frequency', 5),
(f'{x + 7} {y} {z}', 'calibrated_sculk_sensor', 'last_vibration_frequency', 11),
(f'{x + 3} {y} {z + 4}', 'sculk_shrieker', 'warning_level', 2),
]
def place(sculk):
return [cmd(f'/setblock {pos} {block}{{{field}:{value}}}', expect_result=1)
for pos, block, field, value in sculk]
def read(sculk):
return [cmd(f'/data get block {pos} {field}', expect_result=value)
for pos, _, field, value in sculk]
def item_placement():
"""BlockItem loads saved data after the block's placement initialization."""
pos = '2 230 11'
operations = setup('/gamemode creative', '/tp @s 4.5 230 11.5',
'/setblock 2 229 11 stone')
cases = [
('sculk_sensor', 'last_vibration_frequency', 6),
('calibrated_sculk_sensor', 'last_vibration_frequency', 11),
('sculk_shrieker', 'warning_level', 2),
('sculk_sensor', 'last_vibration_frequency', 0),
]
for block, field, value in cases:
item = block
if value:
item += f'[block_entity_data={{id:"minecraft:{block}",{field}:{value}}}]'
operations += setup(f'/setblock {pos} air',
f'/item replace entity @s weapon.mainhand with {item}')
operations.append({'op': 'use_block', 'pos': [2, 229, 11], 'face': 1})
operations.append(cmd(f'/execute if block {pos} {block}', expect_result=1))
operations.append(cmd(f'/data get block {pos} {field}', expect_result=value))
operations.append({'op': 'tick', 'n': 1})
operations.append(cmd(f'/data get block {pos} {field}', expect_result=value))
operations += setup(f'/setblock {pos} air',
'/item replace entity @s weapon.mainhand with air')
return operations
def groups():
sculk = blocks(4, 230, 4)
ticking = [
*setup('/gamerule random_tick_speed 0', '/gamemode survival @s',
'/fill 0 229 0 15 229 15 stone', '/tp @s 4.5 230 4.5'),
cmd('/execute if block 4 229 4 stone', expect_result=1),
*place(sculk),
*read(sculk),
{'op': 'tick', 'n': 1},
*read(sculk),
# Both sensors hear player damage. Their player-attributed tendril
# clicks then trigger a shriek.
cmd('/damage @s 2 minecraft:cactus', expect_result=1),
{'op': 'tick', 'n': 20},
cmd('/data get block 7 230 4 last_vibration_frequency', expect_result=7),
cmd('/data get block 11 230 4 last_vibration_frequency', expect_result=7),
cmd('/execute if block 7 230 8 sculk_shrieker[shrieking=true]', expect_result=1),
cmd('/data get block 7 230 8 warning_level', expect_result=0),
]
pos = '14 230 14'
frequency = f'/data get block {pos} last_vibration_frequency'
replacements = [
cmd(f'/setblock {pos} sculk_sensor{{last_vibration_frequency:10}}', expect_result=1),
cmd(f'/setblock {pos} air', expect_result=1),
cmd(f'/setblock {pos} calibrated_sculk_sensor{{last_vibration_frequency:9}}',
expect_result=1),
cmd(f'/setblock {pos} sculk_sensor{{last_vibration_frequency:13}}', expect_result=1),
cmd(frequency, expect_result=13),
{'op': 'tick', 'n': 1},
cmd(frequency, expect_result=13),
cmd(f'/setblock {pos} sculk_sensor{{last_vibration_frequency:3}}', expect_result=1),
{'op': 'tick', 'n': 1},
cmd(frequency, expect_result=3),
cmd(f'/setblock {pos} air', expect_result=1),
cmd(f'/setblock {pos} sculk_sensor', expect_result=1),
{'op': 'tick', 'n': 1},
cmd(frequency, expect_result=0),
]
distant = blocks(4096, 300, 4096)
reload_chunk = [
*setup('/forceload add 4096 4096'),
{'op': 'wait_chunks', 'chunks': [[256, 256]]},
*place(distant),
{'op': 'tick', 'n': 1},
*read(distant),
*setup('/forceload remove 4096 4096'),
{'op': 'tick', 'n': 60},
cmd('/execute unless loaded 4096 300 4096', expect_result=1),
*setup('/forceload add 4096 4096'),
{'op': 'wait_chunks', 'chunks': [[256, 256]]},
{'op': 'tick', 'n': 1},
*read(distant),
]
return [('setup', group_setup()), ('sculk ticking', ticking),
('sculk replacements', replacements), ('sculk item placement', item_placement()),
('sculk chunk reload', reload_chunk)]
if __name__ == '__main__':
harness_args.ensure_work_dir(driver.lab.LOCAL / 'sculk-saved-fields-parity')
driver.corpus_module.groups = groups
driver.main()