Decode Item Owner and Thrower through numeric UUID streams

Co-Authored-By: Codex <[email protected]>
This commit is contained in:
Florian ERNSTandCodex committed 2026-11-06 17:27:10 +01:00
1 parent 41f822b659
commit a8201731a1
3 files changed
+370 -2

No files matched your search

+153 -2
View File
@@ -2380,6 +2380,14 @@ pub fn evoker_fangs_to_nbt(
tag
}
/// `UUIDUtil.CODEC` via `NbtOps.getIntStream`: boxed Number conversions,
/// with the first-four partial value retained by `TagValueInput.read`.
fn item_uuid_from_tag(tag: Option<&Tag>) -> Option<[i32; 4]> {
let values = horizon_world::datafix::dynamic::as_int_stream(tag)?;
values.get(..4)?.try_into().ok()
}
/// `ItemEntity.readAdditionalSaveData`: Owner and Thrower use the numeric UUID codec.
pub fn item_entity_from_nbt(tag: &Tag, id: u32, bob_offset: f32) -> Option<ItemEntity> {
let stack = item_from_nbt(tag.get("Item")?)?;
let position = position(tag)?;
@@ -2391,8 +2399,8 @@ pub fn item_entity_from_nbt(tag: &Tag, id: u32, bob_offset: f32) -> Option<ItemE
fire_ticks: number(tag.get("Fire")).unwrap_or(0) as i32,
id,
stack,
thrower: uuid_from_tag(tag.get("Thrower")),
target: uuid_from_tag(tag.get("Owner")),
thrower: item_uuid_from_tag(tag.get("Thrower")),
target: item_uuid_from_tag(tag.get("Owner")),
position,
previous: position,
velocity: motion(tag),
@@ -4469,6 +4477,149 @@ mod tests {
assert_eq!(legacy.pickup_delay, i64::from(u32::MAX));
}
#[test]
fn item_numeric_uuid_codec_adopts_numeric_streams_before_save() {
let cases = [
(Tag::IntArray(vec![54, 1, -2, 3]), [54, 1, -2, 3]),
(
Tag::LongArray(vec![
54,
4_294_967_297,
-4_294_967_298,
9_007_199_254_740_993,
]),
[54, 1, -2, 1],
),
(
Tag::List(vec![
Tag::Float(54.9_f32.to_bits()),
Tag::Float(1.75_f32.to_bits()),
Tag::Float((-2.75_f32).to_bits()),
Tag::Float((-0.5_f32).to_bits()),
]),
[54, 1, -2, 0],
),
(
Tag::List(vec![
Tag::Double(54.9_f64.to_bits()),
Tag::Double(1.75_f64.to_bits()),
Tag::Double((-2.75_f64).to_bits()),
Tag::Double((-0.5_f64).to_bits()),
]),
[54, 1, -2, 0],
),
(
Tag::List(vec![
Tag::Long(54),
Tag::Long(4_294_967_297),
Tag::Long(-4_294_967_298),
Tag::Long(9_007_199_254_740_993),
]),
[54, 1, -2, 1],
),
(
Tag::ByteArray(vec![54, 1, (-2_i8) as u8, 3]),
[54, 1, -2, 3],
),
// Util.fixedSize returns a partial first-four result for overlong streams.
(Tag::IntArray(vec![54, 1, -2, 3, 4]), [54, 1, -2, 3]),
];
for field in ["Owner", "Thrower"] {
for (value, expected) in &cases {
let raw = Tag::compound([
("Pos", doubles(&[2.5, 200.0, 2.5])),
(
"Item",
Tag::compound([("id", string("minecraft:stone")), ("count", Tag::Int(1))]),
),
("CustomName", string("kept")),
(field, value.clone()),
]);
let mut item = item_entity_from_nbt(&raw, 7, 0.0).unwrap();
assert_eq!(
if field == "Owner" {
item.target
} else {
item.thrower
},
Some(*expected),
"{field}: {value:?}"
);
assert_eq!(
if field == "Owner" {
item.thrower
} else {
item.target
},
None
);
for source in [None, Some(&raw)] {
let saved = item_entity_to_nbt(&item, [54, 0, 0, 1], source);
assert_eq!(saved.get(field), Some(&Tag::IntArray(expected.to_vec())));
assert_eq!(saved.get("Item").unwrap().get("count"), Some(&Tag::Int(1)));
}
let saved = item_entity_to_nbt(&item, [54, 0, 0, 1], Some(&raw));
assert_eq!(saved.get("CustomName"), raw.get("CustomName"));
item.target = None;
item.thrower = None;
let cleared = item_entity_to_nbt(&item, [54, 0, 0, 1], Some(&saved));
assert!(cleared.get("Owner").is_none());
assert!(cleared.get("Thrower").is_none());
}
}
}
#[test]
fn item_numeric_uuid_codec_rejects_nonstreams_and_short_inputs() {
let cases = [
None,
Some(string("00000036-0000-0001-ffff-fffe00000003")),
Some(Tag::IntArray(vec![])),
Some(Tag::IntArray(vec![54, 1, -2])),
Some(Tag::Int(7)),
Some(Tag::compound([("value", Tag::Int(7))])),
Some(Tag::List(vec![
Tag::Int(54),
Tag::Int(1),
Tag::Int(-2),
string("3"),
])),
];
for field in ["Owner", "Thrower"] {
for value in &cases {
let mut raw = Tag::compound([
("Pos", doubles(&[2.5, 200.0, 2.5])),
(
"Item",
Tag::compound([("id", string("minecraft:stone")), ("count", Tag::Int(1))]),
),
]);
if let Some(value) = value {
put(&mut raw, field, value.clone());
}
let mut item = item_entity_from_nbt(&raw, 7, 0.0).unwrap();
assert_eq!(
(item.target, item.thrower),
(None, None),
"{field}: {value:?}"
);
for source in [None, Some(&raw)] {
let saved = item_entity_to_nbt(&item, [54, 0, 0, 1], source);
assert!(saved.get("Owner").is_none());
assert!(saved.get("Thrower").is_none());
}
item.target = Some([54, 1, -2, 3]);
item.thrower = Some([54, 4, 5, 6]);
let saved = item_entity_to_nbt(&item, [54, 0, 0, 1], Some(&raw));
assert_eq!(saved.get("Owner"), Some(&Tag::IntArray(vec![54, 1, -2, 3])));
assert_eq!(
saved.get("Thrower"),
Some(&Tag::IntArray(vec![54, 4, 5, 6]))
);
}
}
}
#[test]
fn item_entities_round_trip() {
let e = ItemEntity {
+106
View File
@@ -0,0 +1,106 @@
# Item Owner/Thrower numeric UUID decoding
On frozen main `41f822b659789780f58d32c63ddfb6887ea1b8bf`, the actual Game command route matches **183/239** complete fresh Java 26.3 records before the fix. All positive setup guards pass. The fixed runtime passes **239/239** with identical script/reference bytes; required checks are recorded below.
`ItemEntity.readAdditionalSaveData` reads Owner through `UUIDUtil.CODEC` and Thrower through `EntityReference.codec`, based on the same UUID codec. `NbtOps.getIntStream` uses boxed `Number.intValue`: fractional values truncate toward zero, while Long narrowing preserves low32 bits without a floating round trip. `Util.fixedSize` supplies the first four values as an overlong-stream partial result, which `TagValueInput.read` retains. UUID strings are not this numeric codec.
The fix is caller-specific: reuse `horizon_world::datafix::dynamic::as_int_stream`, then retain four entries. The shared `save::uuid_from_tag`, existing writer cleanup, pickup/throw attribution, Animal references and other entity families remain unchanged. No pending H053/H057 code is imported.
## Executable proof
Run `python3 tools/test_item_numeric_uuid_parity.py --binary /absolute/path/to/horizon-client --work-dir /fresh/path`. Use `--reference-only` for Java alone and `--reference /path/to/vanilla.jsonl` to assert byte-identical fresh Java bytes. The fixture uses the existing command oracle and Game command adapter; no new observer.
Fourteen independent UUID-bound, named/tagged stone ItemEntities have Health5s, NoGravity1b, fixed Pos/zero Motion, custom item data and no observation ticks. Real owner UUID/type/Health/mode/Pos, loaded chunk, stone support/air and relative clock0 are positive controls. Each field has independent result/success resets before its typed read, stored results and a presence query. Owner-only/Thrower-only cases verify field independence.
All239 canonical records compare complete feedback/style/callback objects without masks, tolerances or normalization. Sixteen predeclared raw traces retain14 full bodies and two absolute clocks separately. The fixed diagnostic matches all14 whole Item bodies exactly. Its two absolute-clock traces differ: fresh Java309 versus Game0; the old run records Java313 versus Game0. Actual relative delta0 is strict in both. These exact raw values remain unmodified. No wider entity schema or all UUID-reference codec claim follows from these Item observations.
| Input (both fields unless noted) | Java saved reference |
|---|---|
| absent | absent |
| `[I;54,1,-2,3]` | `[I;54,1,-2,3]` |
| `[L;54L,4294967297L,-4294967298L,9007199254740993L]` | `[I;54,1,-2,1]` |
| `[54.9f,1.75f,-2.75f,-0.5f]` or Double equivalents | `[I;54,1,-2,0]` |
| Long numeric list with the LongArray values | `[I;54,1,-2,1]` |
| `[B;54b,1b,-2b,3b]` | `[I;54,1,-2,3]` |
| valid UUID String | absent |
| three-entry IntArray | absent |
| five-entry IntArray `[I;54,1,-2,3,4]` | `[I;54,1,-2,3]` |
| scalar7 / compound `{value:7}` | absent |
| Owner-only / Thrower-only | only supplied field present |
Two resource-free units call the real Item decoder, assert actual target/thrower model adoption first, then save with raw input and without any raw input. They cover numeric forms, malformed/absent forms, stale-raw clearing after modeledNone, and modeledSome replacing invalid raw. Old units fail at LongArray adoption and String rejection respectively.
## Every old canonical residual
All56 residuals are below; the other183 complete records match. Full styled raw JSONL, source scripts, reference, actual binaries/hashes, bytecode and diagnostic traces remain in `.local/h054/`.
| Label | Fresh Java expected | Old Game actual |
|---|---|---|
| `long_array-Owner-read` | h054_3 has the following entity data: [I; 54, 1, -2, 1]; callbacks=[[True, 4]] | Found no elements matching Owner; callbacks=[[False, 0]] |
| `long_array-Owner-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `long_array-Owner-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `long_array-Owner-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `long_array-Thrower-read` | h054_3 has the following entity data: [I; 54, 1, -2, 1]; callbacks=[[True, 4]] | Found no elements matching Thrower; callbacks=[[False, 0]] |
| `long_array-Thrower-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `long_array-Thrower-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `long_array-Thrower-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `float_list-Owner-read` | h054_4 has the following entity data: [I; 54, 1, -2, 0]; callbacks=[[True, 4]] | Found no elements matching Owner; callbacks=[[False, 0]] |
| `float_list-Owner-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `float_list-Owner-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `float_list-Owner-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `float_list-Thrower-read` | h054_4 has the following entity data: [I; 54, 1, -2, 0]; callbacks=[[True, 4]] | Found no elements matching Thrower; callbacks=[[False, 0]] |
| `float_list-Thrower-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `float_list-Thrower-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `float_list-Thrower-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `double_list-Owner-read` | h054_5 has the following entity data: [I; 54, 1, -2, 0]; callbacks=[[True, 4]] | Found no elements matching Owner; callbacks=[[False, 0]] |
| `double_list-Owner-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `double_list-Owner-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `double_list-Owner-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `double_list-Thrower-read` | h054_5 has the following entity data: [I; 54, 1, -2, 0]; callbacks=[[True, 4]] | Found no elements matching Thrower; callbacks=[[False, 0]] |
| `double_list-Thrower-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `double_list-Thrower-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `double_list-Thrower-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `long_list-Owner-read` | h054_6 has the following entity data: [I; 54, 1, -2, 1]; callbacks=[[True, 4]] | Found no elements matching Owner; callbacks=[[False, 0]] |
| `long_list-Owner-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `long_list-Owner-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `long_list-Owner-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `long_list-Thrower-read` | h054_6 has the following entity data: [I; 54, 1, -2, 1]; callbacks=[[True, 4]] | Found no elements matching Thrower; callbacks=[[False, 0]] |
| `long_list-Thrower-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `long_list-Thrower-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `long_list-Thrower-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `byte_array-Owner-read` | h054_7 has the following entity data: [I; 54, 1, -2, 3]; callbacks=[[True, 4]] | Found no elements matching Owner; callbacks=[[False, 0]] |
| `byte_array-Owner-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `byte_array-Owner-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `byte_array-Owner-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `byte_array-Thrower-read` | h054_7 has the following entity data: [I; 54, 1, -2, 3]; callbacks=[[True, 4]] | Found no elements matching Thrower; callbacks=[[False, 0]] |
| `byte_array-Thrower-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `byte_array-Thrower-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `byte_array-Thrower-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `string-Owner-read` | Found no elements matching Owner; callbacks=[[False, 0]] | h054_8 has the following entity data: [I; 54, 1, -2, 3]; callbacks=[[True, 4]] |
| `string-Owner-result` | result has 0 [h054]; callbacks=[[True, 0]] | result has 4 [h054]; callbacks=[[True, 4]] |
| `string-Owner-success` | success has 0 [h054]; callbacks=[[True, 0]] | success has 1 [h054]; callbacks=[[True, 1]] |
| `string-Owner-presence` | Test failed; callbacks=[[False, 0]] | Test passed. Count: 1; callbacks=[[True, 1]] |
| `string-Thrower-read` | Found no elements matching Thrower; callbacks=[[False, 0]] | h054_8 has the following entity data: [I; 54, 1, -2, 3]; callbacks=[[True, 4]] |
| `string-Thrower-result` | result has 0 [h054]; callbacks=[[True, 0]] | result has 4 [h054]; callbacks=[[True, 4]] |
| `string-Thrower-success` | success has 0 [h054]; callbacks=[[True, 0]] | success has 1 [h054]; callbacks=[[True, 1]] |
| `string-Thrower-presence` | Test failed; callbacks=[[False, 0]] | Test passed. Count: 1; callbacks=[[True, 1]] |
| `long-Owner-read` | h054_10 has the following entity data: [I; 54, 1, -2, 3]; callbacks=[[True, 4]] | Found no elements matching Owner; callbacks=[[False, 0]] |
| `long-Owner-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `long-Owner-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `long-Owner-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
| `long-Thrower-read` | h054_10 has the following entity data: [I; 54, 1, -2, 3]; callbacks=[[True, 4]] | Found no elements matching Thrower; callbacks=[[False, 0]] |
| `long-Thrower-result` | result has 4 [h054]; callbacks=[[True, 4]] | result has 0 [h054]; callbacks=[[True, 0]] |
| `long-Thrower-success` | success has 1 [h054]; callbacks=[[True, 1]] | success has 0 [h054]; callbacks=[[True, 0]] |
| `long-Thrower-presence` | Test passed. Count: 1; callbacks=[[True, 1]] | Test failed; callbacks=[[False, 0]] |
## Final validation
The fixed runtime matches all239 strict records with zero residuals. The old runtime has56 residuals above; all setup controls and183 other records match. Both full diagnostic captures remain executable/raw: old7/16 and fixed14/16, where each total comprises14 bodies plus two absolute clocks. Fixed bodies all match; only the exact clock values above differ.
Required checks ran once on the final production patch: `python3.12 tools/pr_ready.py --quick --verbose --base 41f822b659789780f58d32c63ddfb6887ea1b8bf` (UTC2026-11-06 16:13:19–16:26:20). The suite reports2082 Rust tests passed, including both new tests; integration/doc tests, rustfmt, clippy, public-tree and unchanged Cargo.lock checks pass. Python runs708 with three pre-existing `test_optional_release_assets` failures (masks0/1: `musl[@]`; mask2: `arm64[@]` unbound Bash arrays) and one skip, identical to the prior H190 run. Overall `pr_ready` exits1; no all-checks-green claim. The382 broad affected parity harnesses are listed for CI by `--quick`; the focused actual Game pair ran locally.
The current pinned-jar ledger reports two retained callee-coverage gaps (`Entity.discard` and `EntityReference.read`) on the newly cited Item reader. No generic discard or EntityReference engine is added, and no wider closure is claimed. Pinned bytecode and actual Item state establish this bounded numeric-codec route.
Workflow note: the repository ledger's baseline comparison internally created and automatically removed a temporary analysis worktree. No build or task edits ran there. Only the persistent checkout remains; future ledger checks will use `--no-compare`. The protected perf checkpoint and proof resources are unchanged.
Program SHA256: `d2891333ce0ed8d2c88581511febcd047626ce37bba293284846c8b12a1992e7`. Fresh Java canonical reference SHA256: `8429f9e379740d8db31ab30a7931bc3ee83dd225f1a245c46d3fae1919634b88`.
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Item numeric UUID codec through real summon/get commands; complete declared records."""
import hashlib
import json
from pathlib import Path
import sys
import tempfile
import test_command_exec_parity as driver
CASES = [
('absent', None), ('int', '[I;54,1,-2,3]'),
('long_array', '[L;54L,4294967297L,-4294967298L,9007199254740993L]'),
('float_list', '[54.9f,1.75f,-2.75f,-0.5f]'),
('double_list', '[54.9d,1.75d,-2.75d,-0.5d]'),
('long_list', '[54L,4294967297L,-4294967298L,9007199254740993L]'),
('byte_array', '[B;54b,1b,-2b,3b]'),
('string', '"00000036-0000-0001-ffff-fffe00000003"'),
('short', '[I;54,1,-2]'), ('long', '[I;54,1,-2,3,4]'),
('scalar', '7'), ('compound', '{value:7}'),
('owner_only', '[I;54,1,-2,3]'), ('thrower_only', '[I;54,1,-2,3]'),
]
def program():
ops=[]
def cmd(text,label,result=None,admission=True,diagnostic=False):
op=dict(op='cmd',cmd=text,label=label,admission=admission)
if result is not None:op['expect_result']=result
if diagnostic: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(dict(op='settle',required_chunks=[[0,0]]))
cmd('/fill 0 199 0 15 199 15 stone','floor',256)
cmd('/tp @s 14.5 200 14.5 0 0','player-position',1)
cmd('/gamemode creative @s','player-mode')
cmd('/execute if loaded 4 200 4','loaded',1)
cmd('/execute if block 4 199 4 stone','support',1)
cmd('/execute if block 4 200 4 air','air',1)
cmd('/execute if entity @s[type=player,name=Player,nbt={Health:20f,playerGameType:1,Pos:[14.5d,200d,14.5d]}]','player',1)
cmd('/data get entity @s UUID','player-uuid')
cmd('/scoreboard objectives add h054 dummy','objective')
cmd('/execute store result score start h054 run time query gametime','clock-start',diagnostic=True)
for index,(label,value) in enumerate(CASES,1):
x=2.5+2*((index-1)%5);z=2.5+2*((index-1)//5)
uid=f'00000036-0000-0000-0000-{index:012x}'
extra=''
if value is not None:
if label!='thrower_only':extra+=',Owner:'+value
if label!='owner_only':extra+=',Thrower:'+value
cmd(f'/summon item {x} 200 {z} {{UUID:[I;54,0,0,{index}],Health:5s,NoGravity:1b,Motion:[0d,0d,0d],Item:{{id:"minecraft:stone",count:1,components:{{"minecraft:custom_data":{{h054:1}}}}}},Tags:["h054_{index}"],CustomName:"h054_{index}"'+extra+'}',label+'-summon',1)
cmd(f'/execute if entity @e[type=item,nbt={{UUID:[I;54,0,0,{index}],Health:5s,Pos:[{x}d,200d,{z}d],Motion:[0d,0d,0d],NoGravity:1b,Tags:["h054_{index}"],CustomName:"h054_{index}",Item:{{id:"minecraft:stone",count:1,components:{{"minecraft:custom_data":{{h054:1}}}}}}}}]',label+'-actor',1)
cmd(f'/data get entity {uid} UUID',label+'-uuid',4)
cmd(f'/data get entity {uid} Item',label+'-item')
cmd(f'/data get entity {uid}',label+'-body',diagnostic=True)
for field in ['Owner','Thrower']:
key=label+'-'+field
for holder in ['result','success']:
cmd(f'/scoreboard players set {holder} h054 -99',key+'-reset-'+holder,-99)
cmd(f'/execute store result score result h054 store success score success h054 run data get entity {uid} {field}',key+'-read',admission=False)
for holder in ['result','success']:
cmd(f'/scoreboard players get {holder} h054',key+'-'+holder,admission=False)
cmd(f'/execute if data entity {uid} {field}',key+'-presence',admission=False)
cmd('/execute store result score end h054 run time query gametime','clock-end',diagnostic=True)
cmd('/scoreboard players operation end h054 -= start h054','clock-delta-operation',0)
cmd('/scoreboard players get end h054','clock-delta',0)
return ops
def main():
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:
parent = Path(tempfile.mkdtemp(prefix='item-numeric-uuid-', dir=driver.lab.LOCAL))
sys.argv += ['--work-dir', str(parent / 'run')]
build = Path(sys.argv[sys.argv.index('--work-dir')+1]).resolve()
assert not build.exists(), 'preserve attempts; choose a fresh directory'
build.mkdir(parents=True)
ops = program(); recorded = [op for op in ops if op['op']=='cmd' and op.get('record', True)]
(build/'declaration.json').write_text(json.dumps(dict(
script_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), records=len(recorded),
cases=CASES, labels=[op['label'] for op in recorded],
raw_diagnostics=[op['label'] for op in ops if op.get('trace')],
comparison='complete records, no masks/tolerances; bodies and absolute clocks are separate raw traces'), indent=2)+'\n')
expected_bytes = reference.read_bytes() if reference else None
driver.corpus_module.groups = lambda: [('Item numeric UUID codec', ops)]
sys.argv.append('--allow-gaps'); driver.main()
java_bytes = (build/'vanilla.jsonl').read_bytes()
if expected_bytes is not None:
assert java_bytes==expected_bytes and reference.read_bytes()==expected_bytes, 'fresh Java reference changed'
java = [json.loads(s) for s in java_bytes.splitlines()]
if '--reference-only' in sys.argv: return
native = [json.loads(s) for s in (build/'rust.jsonl').read_bytes().splitlines()]
assert len(recorded)==len(java)==len(native)
residuals = [dict(label=op['label'], expected=a, actual=b) for op,a,b in zip(recorded,java,native) if a!=b]
report = dict(records=len(recorded), passed=len(recorded)-len(residuals), residuals=residuals,
reference_sha256=hashlib.sha256(java_bytes).hexdigest())
(build/'strict-report.json').write_text(json.dumps(report, indent=2)+'\n')
for op,a,b in zip(recorded,java,native):
if op['admission']: assert a==b, ('admission',op,a,b)
print(f'Strict records: {report["passed"]}/{report["records"]}')
if residuals: raise SystemExit(1)
if __name__ == '__main__':
main()