Omit the saved lodestone tracker true default

Co-Authored-By: Codex <[email protected]>
This commit is contained in:
Florian ERNSTandCodex committed 2026-11-06 00:08:11 +01:00
1 parent 6d99e14ff0
commit 6df80522aa
3 files changed
+383

No files matched your search

+102
View File
@@ -714,6 +714,12 @@ fn canonical_other_component(id: &str, tag: &Tag) -> Tag {
/// A filled map's empty decorations equal its prototype (`Items.mapProperties`).
pub fn encode_components(stack: &ItemStack) -> Option<Tag> {
let mut components = components_tag(stack.damage, stack.data())?;
// LodestoneTracker.CODEC: optionalFieldOf("tracked", true).
if let Some(Tag::Compound(tracker)) = components.get_mut("minecraft:lodestone_tracker")
&& tracker.get(&JavaString::from("tracked")) == Some(&Tag::Byte(1))
{
tracker.remove(&JavaString::from("tracked"));
}
if &*stack.item == "minecraft:filled_map"
&& matches!(components.get("minecraft:map_decorations"), Some(Tag::Compound(entries)) if entries.is_empty())
&& let Tag::Compound(entries) = &mut components
@@ -1141,6 +1147,102 @@ pub fn command_components(text: &str) -> Result<Tag, String> {
mod tests {
use super::*;
fn check_lodestone_saved_default(decoded: bool) {
const TRACKER: &str = "minecraft:lodestone_tracker";
const CUSTOM: &str = "minecraft:custom_data";
let target = Tag::compound([
("dimension", Tag::String("minecraft:overworld".into())),
("pos", Tag::IntArray(vec![12, 200, 12])),
]);
let custom = Tag::compound([("keep", Tag::Int(17))]);
assert_eq!(
encode_components(&ItemStack::new("minecraft:compass", 1)),
None
);
let mut only_custom = ItemStack::new("minecraft:compass", 1);
only_custom
.data_mut()
.set_other(CUSTOM, Some(custom.clone()));
assert_eq!(
encode_components(&only_custom),
Some(Tag::compound([(CUSTOM, custom.clone())]))
);
// The valid false and absent controls precede the old true-default failure.
for (tracked, has_target) in [
(Some(false), true),
(None, true),
(Some(true), true),
(Some(true), false),
] {
let mut fields = Vec::new();
let mut expected_fields = Vec::new();
if has_target {
fields.push(("target", target.clone()));
expected_fields.push(("target", target.clone()));
}
if let Some(value) = tracked {
fields.push(("tracked", Tag::Byte(i8::from(value))));
if !value {
expected_fields.push(("tracked", Tag::Byte(0)));
}
}
let source = Tag::compound(fields);
let input = Tag::compound([(TRACKER, source.clone()), (CUSTOM, custom.clone())]);
let expected = Tag::compound([
(TRACKER, Tag::compound(expected_fields)),
(CUSTOM, custom.clone()),
]);
let stack = if decoded {
let (damage, extra) = decode_components(Some(&input));
ItemStack {
damage,
extra,
..ItemStack::new("minecraft:compass", 1)
}
} else {
let mut fresh = ItemStack::new("minecraft:compass", 1);
assert!(fresh.extra.is_none());
fresh.data_mut().set_other(TRACKER, Some(source.clone()));
fresh.data_mut().set_other(CUSTOM, Some(custom.clone()));
fresh
};
{
assert_eq!(stack.data().unwrap().other(TRACKER), Some(&source));
assert_eq!(encode_components(&stack), Some(expected.clone()));
assert_eq!(components_tag(0, stack.data()), Some(input.clone()));
let saved = crate::item_nbt::item_to_nbt(&stack);
assert_eq!(saved.get("components"), Some(&expected));
assert_eq!(
saved.get("id"),
Some(&Tag::String("minecraft:compass".into()))
);
assert_eq!(saved.get("count"), Some(&Tag::Int(1)));
assert_eq!(stack.data().unwrap().other(TRACKER), Some(&source));
}
}
let mut targetless = ItemStack::new("minecraft:compass", 1);
targetless
.data_mut()
.set_other(TRACKER, Some(Tag::compound([("tracked", Tag::Byte(1))])));
assert_eq!(
encode_components(&targetless),
Some(Tag::compound([(
TRACKER,
Tag::Compound(Default::default())
)]))
);
}
#[test]
fn lodestone_tracker_saved_default_decoded() {
check_lodestone_saved_default(true);
}
#[test]
fn lodestone_tracker_saved_default_fresh_model() {
check_lodestone_saved_default(false);
}
#[test]
fn map_decoration_types_round_trip_with_default_namespaces() {
let parse = |text| horizon_world::nbt::parse_snbt(text).unwrap().0;
+136
View File
@@ -0,0 +1,136 @@
# Lodestone tracker saved true default
Pinned Java 26.3 `LodestoneTracker.CODEC` uses
`Codec.BOOL.optionalFieldOf("tracked", true)`: valid true is omitted when saved,
false remains explicit, and an absent target is independent of that default.
The narrow repair is in `encode_components`, the actual saved ItemStack boundary.
It removes only tracked Byte1 from that component's cloned saved compound.
The decoder, live StackData.other value and `components_tag` remain intact; the
latter also supplies client stack views. No renderer, packet or ticking parity
claim. Existing map-decoration namespace/prototype behavior is unchanged. No
Item ID hard-code, generic boolean conversion, registry/validation expansion,
useOn, inventory ticking, POI/unloaded logic or other component-default cleanup.
## Declared actual Game/Java fixture
```
python3 tools/test_lodestone_default_parity.py --binary /path/to/game --work-dir /scratch/run
python3 tools/test_lodestone_default_parity.py --full-diagnostic --binary /path/to/game --work-dir /scratch/full
```
`--reference /scratch/old/vanilla.jsonl` requires byte-identical fresh Java bytes
before comparing another binary. The existing command harness drives real Game
and independently runs pinned Java, with fresh isolated data and a real owner.
No new observer. Five actual stationary ItemEntities hold compass count 1, with unique
UUIDs/fixed entity names, NoGravity and zero Motion. They are away from the player
and have PickupDelay 32767. No world steps or living-holder inventory updates.
Cases: true+target, false+target, target without tracked, targetless explicit
true, and absent whole tracker component. Every case retains custom_data.keep 17.
A loaded real lodestone guards the codec-valid GlobalPos target. UUID/type,
Health/Age/Pos/Motion/NoGravity/Item ID/count, loaded floor/air, owner identity,
setup acknowledgements and actual relative clock 0 are strict positive controls.
Position/Motion/UUID/count are read again afterward. The targetless default must
stay as a present empty component, distinct from the missing-component control.
Whole saved component and tracked-field reads use fresh result/success sentinels
at -99. Actual data-presence branches have fresh independent sentinels and exact
callbacks/no-action behavior. Successful zero (false or empty compound) remains
distinct from a missing path. Target dimension/typed IntArray position and the
unrelated custom_data value stay strict.
Predeclared before native capture: **199** complete raw records. Only the five
whole entity bodies and absolute clock-start/end labels are diagnostic; they
remain traced, and `--full-diagnostic` compares all **206** records. No masks,
tolerances, feedback/style normalization or dropped commands.
Frozen source: `6d99e14ff0c70ae60bc56b67829b526409fa068e`.
| Program | Exact script SHA256 |
|---|---|
| Scoped199 | `761185d59bbc90249a393a19abbdd33342dee9af0cca2d7956837ff805e91094` |
| Full206 | `f803a10ec5e8fa78f663358d309ecf2a07bd41f764cb37979513735f1a2353a4` |
Old actual Game: **185/199**. The seven suffixes below differ in EACH of `true`
and `targetless`, yielding 14 target observations from this one saved-codec gap.
All false/absent/missing-component, actual actor, type/target/custom-data, clock
and other controls pass. Full old diagnostic: **188/206** (also both affected
whole bodies and two absolute clocks).
| Exact suffix | Java expected | Old actual |
|---|---|---|
| -component-read | tracker with default omitted | tracker retaining tracked:1b |
| -component-result | 1 with target / 0 targetless | 2 with target / 1 targetless |
| -tracked-read | missing-field error, callback false/0 | tracked:1b, callback true/1 |
| -tracked-result | 0 | 1 |
| -tracked-success | 0 | 1 |
| -tracked-presence | no action/no callback | action/callback true/1 |
| -tracked-presence-result | -99 | 1 |
## Reachable regressions and evidence
`lodestone_tracker_saved_default_decoded` and
`lodestone_tracker_saved_default_fresh_model` independently fail old at saved
tracked Byte1 versus omission. Both pass fixed. Each uses the real ItemStack
encoder, with false/absent and missing-component controls preceding the failure.
The fresh model has no original item NBT. Tests cover target/custom_data retention,
full saved ItemStack ID/count, unchanged decoded/live value and unsaved component
view, and a targetless empty component even without unrelated custom_data.
The final source applies the default only at encode_components, after reviewing
its shared helper's client consumers. Intermediate unit drafts and final old/fixed
logs are retained; no intermediate helper implementation is part of the runtime
pair or published production patch.
Complete programs, fresh references, raw styled feedback/callbacks, full bodies,
transcripts, pinned bytecode, source hashes and immutable binaries are retained
under the owner's `.local/h329`, without publishing game files. Available file
capture timestamps and logged stage durations are retained. Unobserved event
stage timestamps remain unknown.
## Fixed results and all full-diagnostic residuals
Final actual Game: **199/199**, versus **185/199** old, using byte-identical
scoped scripts and fresh Java reference bytes. Both final decoded and fresh-model
unit tests fail independently old and pass fixed. Full diagnostics improve
**188/206 → 204/206**. All five complete saved entity body records match in this
fixture. No full 206/raw-clock equality, general entity/packet or tick claim.
The full script is byte-identical across builds; independent full Java references
differ only at clock-start/end. Their non-clock records are identical. The two
remaining raw record differences are listed below. Actual relative delta 0 is an
independently strict admission; no captured absolute value is rewritten.
| Exact label | Java expected (old / fixed) | Native actual (both) |
|---|---|---|
| `clock-start` | `317 / 309` | `0` |
| `clock-end` | `317 / 309` | `0` |
Scoped Java reference SHA256 (both): `fbe623194668b9bf0639ff632198aa9045c61558c3744901ad804dde906eb28d`.
Full Java reference SHA256 old: `2f20e1c75aa35dba8a7c3e02a50ee31286af77650a5c4be67e2dfcc93c43144e`; fixed: `5d3af57ac2dbf287daed95cc9775ef7b8bce9fcdcba3113897eef93aa2829ea9`.
## Required checks and source checkpoints
`tools/pr_ready.py --quick --base 6d99e14ff0c70ae60bc56b67829b526409fa068e`
ran once on the final production/test patch: **70 item tests** and **25 content-root
tests pass**, with formatting, workspace/headless clippy, doc tests and public
checks passing. Jobs 2, incremental 0, Rust test threads 2 and isolated test homes
were used; prepared resources were actually available for the content-root suite.
Broad parity is skipped by --quick; the explicit actual Game/Java pair above ran.
Python: **690 run, 4 failures, 1 skipped** in unchanged ARM64/Bash 3.2 packaging
checks. The narrow install-signature diagnostic confirms the missing ARM64 mock
archive; optional archive subcases fail on unset Bash arrays. No packaging fix.
The ledger check flags a long regression helper (not a vanilla implementation)
and an inference-only DispenserBlock classification change; dispenser source is
unchanged and no dispenser behavior is claimed. No full LodestoneTracker codec,
registry or stream-codec implementation is claimed by this one-field repair.
Actual main was pulled through `2266c4de14561146044c422e6c525b3ce52b0f79` after
batches. Later mod-index, wire-stream/client input/tick-script and HUD changes
were reviewed. The item codec, command harness/driver and Java command oracle are
unchanged. The new optional tick-script route is separate from this fixture's
command-script route. These are source reviews, not combined latest-main runtime
claims: the paired proof remains on frozen 6d99. Only evidence text was appended
after required checks; final public-tree and whitespace checks were repeated.
+145
View File
@@ -0,0 +1,145 @@
#!/usr/bin/env python3
"""Saved LodestoneTracker default with stationary real item entities, no world steps.
--full-diagnostic records whole entity bodies and absolute clocks too. Those
commands remain traced in the scoped program. All saved-component, typed identity,
presence, callback/store and relative-clock records are strict.
"""
import hashlib
import json
from pathlib import Path
import sys
import test_command_exec_parity as driver
def program(full=False):
ops = []
def cmd(text, label, result=None, callback=None, admission=True, diagnostic=False):
op = {'op': 'cmd', 'cmd': text, 'label': label, 'admission': admission}
if result is not None:
op['expect_result'] = result
if callback is not None:
op['expect_callback'] = callback
if diagnostic and not full:
op.update(record=False, trace=True)
ops.append(op)
cmd('/tick freeze', 'freeze')
cmd('/gamerule spawn_mobs false', 'no-spawn')
cmd('/gamerule advance_time false', 'no-daylight')
cmd('/forceload add 0 0', 'force', 1)
ops.append({'op': 'settle', 'required_chunks': [[0, 0]]})
cmd('/fill 0 199 0 15 199 15 stone', 'floor', 256)
cmd('/setblock 12 200 12 lodestone', 'lodestone', 1)
cmd('/execute if block 12 200 12 lodestone', 'target-block', 1)
cmd('/execute if loaded 12 200 12', 'target-loaded', 1)
cmd('/tp @s 8.5 200 12.5 0 0', 'owner-position', 1)
cmd('/gamemode creative @s', 'owner-mode')
cmd('/scoreboard objectives add h329 dummy', 'objective')
cmd('/execute store result score start h329 run time query gametime', 'clock-start', diagnostic=True)
cmd('/execute if entity @s[type=player,name=Player]', 'owner-type', 1)
for field, result in [('UUID', 4), ('Health', 20), ('playerGameType', 1), ('Pos', 3)]:
cmd(f'/data get entity @s {field}', 'owner-' + field, result)
target = 'target:{dimension:"minecraft:overworld",pos:[I;12,200,12]}'
cases = [('true', target + ',tracked:true'), ('false', target + ',tracked:false'),
('absent', target), ('targetless', 'tracked:true'), ('none', None)]
for n, (case, value) in enumerate(cases, 1):
x = n * 2
uuid = f'00000149-0000-0000-0000-{n:012x}'
lodestone = '' if value is None else f',"minecraft:lodestone_tracker":{{{value}}}'
cmd(f'/execute if loaded {x} 201 4', case + '-loaded', 1)
cmd(f'/execute if block {x} 199 4 stone', case + '-floor', 1)
cmd(f'/execute if block {x} 201 4 air', case + '-gap', 1)
cmd(f'/summon item {x + .5} 201 4.5 {{UUID:[I;329,0,0,{n}],NoGravity:1b,'
f'Motion:[0d,0d,0d],PickupDelay:32767s,CustomName:"Tracker_{case}",'
f'Item:{{id:"minecraft:compass",count:1,components:{{"minecraft:custom_data":{{keep:17}}{lodestone}}}}}}}',
case + '-summon', 1)
cmd(f'/execute as {uuid} if entity @s[type=minecraft:item]', case + '-actual-type', 1)
for field, result in [('UUID', 4), ('Pos', 3), ('Motion', 3), ('NoGravity', 1),
('Health', 5), ('Age', 0), ('PickupDelay', 32767),
('Item.id', 17), ('Item.count', 1)]:
cmd(f'/data get entity {uuid} {field}', case + '-' + field, result)
cmd(f'/data get entity {uuid} Item.components."minecraft:custom_data".keep',
case + '-custom-data', 17)
cmd(f'/data get entity {uuid}', case + '-full-body', diagnostic=True)
component = 'Item.components."minecraft:lodestone_tracker"'
if case in ('true', 'false', 'absent'):
cmd(f'/data get entity {uuid} {component}.target.dimension', case + '-dimension', 19)
cmd(f'/data get entity {uuid} {component}.target.pos', case + '-target-pos', 3)
for suffix, path, exists in [('component', component, case != 'none'),
('tracked', component + '.tracked', case == 'false')]:
affected = case in ('true', 'targetless')
for holder in ['result', 'success']:
cmd(f'/scoreboard players set {holder} h329 -99', case + '-' + suffix + '-reset-' + holder, -99)
cmd(f'/execute store result score result h329 store success score success h329 '
f'run data get entity {uuid} {path}', case + '-' + suffix + '-read',
callback=([True, 0] if case == 'false' else [False, 0]) if suffix == 'tracked' else None,
admission=not affected)
for holder in ['result', 'success']:
cmd(f'/scoreboard players get {holder} h329', case + '-' + suffix + '-' + holder,
admission=not affected)
cmd('/scoreboard players set presence h329 -99', case + '-' + suffix + '-reset-presence', -99)
cmd(f'/execute if data entity {uuid} {path} run scoreboard players set presence h329 1',
case + '-' + suffix + '-presence', admission=not affected or suffix == 'component')
ops[-1]['expect_results'] = [[True, 1]] if exists else []
cmd('/scoreboard players get presence h329', case + '-' + suffix + '-presence-result',
1 if exists else -99, admission=not affected or suffix == 'component')
for n, (case, _) in enumerate(cases, 1):
uuid = f'00000149-0000-0000-0000-{n:012x}'
for field, result in [('UUID', 4), ('Pos', 3), ('Motion', 3), ('Item.count', 1)]:
cmd(f'/data get entity {uuid} {field}', case + '-after-' + field, result)
cmd('/execute store result score now h329 run time query gametime', 'clock-end', diagnostic=True)
cmd('/scoreboard players operation now h329 -= start h329', 'clock-delta-operation', 0)
cmd('/scoreboard players get now h329', 'clock-delta', 0)
return ops
def main():
full = '--full-diagnostic' in sys.argv
if full:
sys.argv.remove('--full-diagnostic')
reference = None
if '--reference' in sys.argv:
i = sys.argv.index('--reference')
reference = Path(sys.argv[i + 1]).resolve()
del sys.argv[i:i + 2]
if '--work-dir' not in sys.argv:
sys.argv += ['--work-dir', str(driver.lab.LOCAL / 'lodestone-default-parity')]
build = Path(sys.argv[sys.argv.index('--work-dir') + 1]).resolve()
ops = program(full)
driver.corpus_module.groups = lambda: [('saved lodestone tracker default', ops)]
sys.argv.append('--allow-gaps')
driver.main()
recorded = [op for op in ops if op['op'] == 'cmd' and op.get('record', True)]
java = [json.loads(s) for s in (build / 'vanilla.jsonl').read_text().splitlines()]
for op, row in zip(recorded, java):
if 'expect_results' in op:
assert row['results'] == op['expect_results'], ('Java action admission', op, row)
if '--reference-only' in sys.argv:
return
source = reference or build / 'vanilla.jsonl'
if reference:
assert source.read_bytes() == (build / 'vanilla.jsonl').read_bytes(), 'fresh Java bytes changed'
expected = [json.loads(s) for s in source.read_text().splitlines()]
native = [json.loads(s) for s in (build / 'rust.jsonl').read_text().splitlines()]
assert len(expected) == len(native) == len(recorded)
differences = [{'label': op['label'], 'expected': a, 'actual': b}
for op, a, b in zip(recorded, expected, native) if a != b]
report = {'records': len(native), 'passed': len(native) - len(differences),
'reference_sha256': hashlib.sha256(source.read_bytes()).hexdigest(),
'comparison': 'complete raw records, no masks/tolerances', 'differences': differences}
(build / 'strict-report.json').write_text(json.dumps(report, indent=2) + '\n')
if not full:
for op, a, b in zip(recorded, expected, native):
if op['admission']:
assert a == b, ('actual fixture admission', op, a, b)
print(f'Strict raw records: {report["passed"]}/{report["records"]}')
if differences:
raise SystemExit(1)
if __name__ == '__main__':
main()