Save the modeled mob loot-pickup flag

Write CanPickUpLoot on every save through the existing virtual getter, preserving the Allay and sulfur-cube overrides instead of retaining stale raw bytes. Cover decoded and mutated models plus frozen actual command reads.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
This commit is contained in:
TheMrZZandCodex gpt-6.1-sol committed 2026-11-06 04:30:58 +01:00
1 parent 1532c9c153
commit c843c1aa05
4 files changed
+114 -2

No files matched your search

+56 -1
View File
@@ -366,6 +366,8 @@ pub fn mob_to_nbt(
"DeathTime",
Tag::Short(m.death_time.clamp(0, i32::from(i16::MAX)) as i16),
),
// Mob.addAdditionalSaveData writes the current virtual getter, not raw input.
("CanPickUpLoot", Tag::Byte(i8::from(m.can_pick_up_loot()))),
("PersistenceRequired", Tag::Byte(i8::from(m.persistent))),
] {
put(&mut tag, key, value);
@@ -527,7 +529,6 @@ pub fn mob_to_nbt(
for (key, value) in [
("AbsorptionAmount", Tag::Float(0)),
("FallFlying", Tag::Byte(0)),
("CanPickUpLoot", Tag::Byte(i8::from(m.can_pick_up_loot()))),
("current_impulse_context_reset_grace_time", Tag::Int(0)),
(
"Brain",
@@ -7026,3 +7027,57 @@ mod piglin_live_anger_memory_tests {
assert_unrelated(&saved);
}
}
#[cfg(test)]
mod pickup_loot_save_tests {
use super::*;
use crate::mobs::Species;
#[test]
fn pickup_save_uses_live_state_instead_of_raw_and_preserves_overrides() {
for (species, id) in [
(Species::Pig, "minecraft:pig"),
(Species::Allay, "minecraft:allay"),
(Species::SulfurCube, "minecraft:sulfur_cube"),
] {
let mut kind = crate::mobs::sensor_scan_tests::kind();
let ty = Arc::make_mut(&mut kind);
ty.id = id.into();
ty.species = species;
let types = HashMap::from([(id.to_string(), kind)]);
for supplied in [None, Some(false), Some(true)] {
let mut raw = Tag::compound([
("id", string(id)),
("Pos", doubles(&[4.5, 200.0, 4.5])),
("audit:unrelated", Tag::Int(7)),
]);
if let Some(on) = supplied {
put(&mut raw, "CanPickUpLoot", Tag::Byte(i8::from(on)));
}
let mut mob = mob_from_nbt(&raw, &types, 1, None).unwrap();
for live in [false, true] {
mob.extra.pick_up_loot = live;
mob.extra.main_hand = live.then(|| "minecraft:stone".into());
mob.extra.body = live.then(|| "minecraft:stone".into());
let expected = if species == Species::SulfurCube {
!live
} else {
live
};
for input in [None, Some(&raw)] {
let saved = mob_to_nbt(&mob, [46, 0, 0, 1], input, None);
assert_eq!(
saved.get("CanPickUpLoot"),
Some(&Tag::Byte(i8::from(expected))),
"{id} supplied={supplied:?} live={live} raw={}",
input.is_some()
);
if input.is_some() {
assert_eq!(saved.get("audit:unrelated"), Some(&Tag::Int(7)));
}
}
}
}
}
}
}
+39 -1
View File
@@ -3855,6 +3855,42 @@ def awake_villager_saved_position():
def mob_saved_pickup_loot():
"""Valid boolean inputs only; full bodies retain unrelated save diagnostics."""
ops = [cmd('/tick freeze'), cmd('/forceload add 160 160 175 175'),
{'op': 'wait_chunks', 'chunks': [[10, 10]]},
cmd('/fill 160 199 160 175 199 175 stone', expect_result=256),
cmd('/execute if loaded 164 200 164', expect_result=1),
cmd('/execute if block 164 199 164 stone', expect_result=1),
cmd('/scoreboard objectives add pickup_save dummy', expect_result=1)]
ops += setup('/execute store result score start pickup_save run time query gametime')
for species, name in enumerate(['pig', 'allay', 'sulfur_cube']):
for value, supplied in enumerate(['', ',CanPickUpLoot:0b', ',CanPickUpLoot:1b']):
n = 1 + species * 3 + value
uuid = f'0000002e-0000-0000-0000-{n:012x}'
array = f'[I;46,0,0,{n}]'
tag = f'pickup_save_{n}'
body = 'Pos:[164.5d,200d,164.5d],Motion:[0d,0d,0d],NoAI:1b,NoGravity:1b,Silent:1b,PersistenceRequired:1b,Health:4f'
expected = int(name == 'sulfur_cube' or (name == 'pig' and value == 2))
ops += [cmd(f'/summon minecraft:{name} 164.5 200 164.5 {{UUID:{array},Tags:["{tag}"],{body}{supplied}}}', expect_result=1),
cmd(f'/execute as {uuid} if entity @s[type=minecraft:{name},tag={tag}]', expect_result=1),
cmd(f'/execute if data entity {uuid} {{UUID:{array},{body}}}', expect_result=1)]
for _ in range(2):
ops += setup('/scoreboard players set value pickup_save 317',
'/scoreboard players set success pickup_save 17')
ops += [cmd(f'/data get entity {uuid} CanPickUpLoot', expect_result=expected),
cmd(f'/execute store result score value pickup_save store success score success pickup_save run data get entity {uuid} CanPickUpLoot', expect_result=expected),
cmd('/scoreboard players get value pickup_save', expect_result=expected),
cmd('/scoreboard players get success pickup_save', expect_result=1),
cmd(f'/execute if data entity {uuid} {{CanPickUpLoot:{expected}b}}', expect_result=1)]
full = cmd(f'/data get entity {uuid}', record=False)
full['trace'] = True
ops.append(full)
ops += setup('/execute store result score elapsed pickup_save run time query gametime',
'/scoreboard players operation elapsed pickup_save -= start pickup_save')
return ops + [cmd('/scoreboard players get elapsed pickup_save', expect_result=0)]
def projectile_saved_uuid():
"""Direct identity guards are separate from repeated saved UUID views."""
from uuid import UUID
@@ -5526,7 +5562,8 @@ ISOLATED_REINFORCEMENTS = frozenset({'command zombie reinforcements', 'command r
# These overlays replace built-in recipes, never beside the corpus's fixed vanilla recipe-count controls.
ISOLATED_RECIPES = frozenset({'crafting recipe priority', 'crafting base recipe control', 'transmute result patches'})
ISOLATED_FRAME_MAPS = frozenset({'frame live map detachment'})
ISOLATED_GROUPS = ISOLATED_REINFORCEMENTS | ISOLATED_RECIPES | ISOLATED_WOLF_ANGER | ISOLATED_FRAME_MAPS
ISOLATED_PICKUP_SAVE = frozenset({'mob saved pickup loot'})
ISOLATED_GROUPS = ISOLATED_REINFORCEMENTS | ISOLATED_RECIPES | ISOLATED_WOLF_ANGER | ISOLATED_FRAME_MAPS | ISOLATED_PICKUP_SAVE
def groups():
@@ -5562,6 +5599,7 @@ def groups():
from item_merge_corpus import stack_limit_merges
return [
('setup', group_setup()),
('mob saved pickup loot', mob_saved_pickup_loot()),
('transmute result patches', transmute_patches(cmd, setup)),
('mob effect predicate view', mob_effect_predicates(cmd, setup)),
('frame live map detachment', frame_maps(cmd, setup)),
+5
View File
@@ -139,6 +139,7 @@ def main():
parser.add_argument('--map-decoration-patch-fixture', action='store_true')
parser.add_argument('--map-decoration-removal-diagnostic', action='store_true')
parser.add_argument("--selector-bounds-fixture", action="store_true")
parser.add_argument("--mob-pickup-save-fixture", action="store_true")
args = parser.parse_args()
build = args.work_dir.resolve() if args.work_dir else lab.LOCAL / 'command-exec-parity'
build.mkdir(parents=True, exist_ok=True)
@@ -164,6 +165,10 @@ def main():
sys.exit(f'{name} failed ({result.returncode}):\n{tail}')
groups = corpus_module.groups()
if args.mob_pickup_save_fixture or args.only == "mob saved pickup loot":
groups = [(name, ops) for name, ops in groups if name == "mob saved pickup loot"]
elif not args.only:
groups = [(name, ops) for name, ops in groups if name not in corpus_module.ISOLATED_PICKUP_SAVE]
if not args.only:
groups = [(name, ops) for name, ops in groups if name not in corpus_module.ISOLATED_FRAME_MAPS]
if not args.only and not args.wolf_anger_clock_diagnostic:
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env python3
"""Actual command saves of modeled loot-pickup flags, including species overrides."""
from pathlib import Path
import subprocess
import sys
if __name__ == '__main__':
root = Path(__file__).resolve().parents[1]
raise SystemExit(subprocess.run([
sys.executable, str(root / 'tools/test_command_exec_parity.py'),
'--mob-pickup-save-fixture', '--work-dir', str(root / '.local/mob-pickup-save-parity'),
*sys.argv[1:],
], cwd=root).returncode)