mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Preserve Pillager and WanderingTrader saved inventories
Co-Authored-By: Codex <[email protected]>
This commit is contained in:
1 parent
41f822b659
commit
85b299df9d
6 files changed
+360
-3
No files matched your search
@@ -620,6 +620,19 @@ pub fn mob_to_nbt(
|
||||
map.insert("LoveCause".into(), Tag::IntArray(cause.to_vec()));
|
||||
}
|
||||
}
|
||||
// InventoryCarrier.writeInventoryToTag: the occupied stacks, even when empty.
|
||||
if matches!(id, "minecraft:pillager" | "minecraft:wandering_trader") {
|
||||
map.insert(
|
||||
"Inventory".into(),
|
||||
Tag::List(
|
||||
m.extra
|
||||
.inventory
|
||||
.iter()
|
||||
.map(crate::save::item_to_nbt)
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
let species = m.species();
|
||||
// `VariantUtils.writeVariant`: only the species that store the name. A
|
||||
// mooshroom saves `Type` (below), a fox `Type`, a horse and a llama a
|
||||
@@ -1795,6 +1808,21 @@ pub fn mob_from_nbt(
|
||||
m.extra.flag = flag(tag, "ignited");
|
||||
m.max_swell = number(tag.get("Fuse")).unwrap_or(30) as i16 as i32;
|
||||
}
|
||||
// InventoryCarrier.readInventoryFromTag, using the existing occupied-stack model.
|
||||
let carrier_slots = match m.species() {
|
||||
crate::mobs::Species::Pillager => 5,
|
||||
crate::mobs::Species::WanderingTrader => 8,
|
||||
_ => 0,
|
||||
};
|
||||
if carrier_slots != 0
|
||||
&& let Some(Tag::List(items)) = tag.get("Inventory")
|
||||
{
|
||||
m.extra.inventory = items
|
||||
.iter()
|
||||
.filter_map(crate::save::item_from_nbt)
|
||||
.take(carrier_slots)
|
||||
.collect();
|
||||
}
|
||||
if matches!(
|
||||
m.species(),
|
||||
crate::mobs::Species::Piglin
|
||||
@@ -7556,3 +7584,135 @@ mod stand_fall_save_tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod carrier_inventory_tests {
|
||||
use super::*;
|
||||
use crate::mobs::Species;
|
||||
|
||||
#[test]
|
||||
fn carrier_inventory_is_adopted_and_saved_from_the_model() {
|
||||
let stack = Tag::compound([
|
||||
("id", string("minecraft:stone")),
|
||||
("count", Tag::Int(3)),
|
||||
(
|
||||
"components",
|
||||
Tag::compound([(
|
||||
"minecraft:custom_data",
|
||||
Tag::compound([("audit", Tag::Int(137))]),
|
||||
)]),
|
||||
),
|
||||
]);
|
||||
let offers = Tag::compound([(
|
||||
"Recipes",
|
||||
Tag::List(vec![Tag::compound([
|
||||
(
|
||||
"buy",
|
||||
Tag::compound([("id", string("minecraft:emerald")), ("count", Tag::Int(1))]),
|
||||
),
|
||||
(
|
||||
"sell",
|
||||
Tag::compound([("id", string("minecraft:dirt")), ("count", Tag::Int(2))]),
|
||||
),
|
||||
])]),
|
||||
)]);
|
||||
for (species, id) in [
|
||||
(Species::Pillager, "minecraft:pillager"),
|
||||
(Species::WanderingTrader, "minecraft:wandering_trader"),
|
||||
(Species::Piglin, "minecraft:piglin"),
|
||||
] {
|
||||
let mut kind = crate::mobs::sensor_scan_tests::kind();
|
||||
Arc::make_mut(&mut kind).id = id.into();
|
||||
Arc::make_mut(&mut kind).species = species;
|
||||
let types = HashMap::from([(id.to_string(), kind)]);
|
||||
for input in [
|
||||
None,
|
||||
Some(Tag::List(vec![])),
|
||||
Some(Tag::List(vec![stack.clone()])),
|
||||
] {
|
||||
let mut raw = Tag::compound([
|
||||
("id", string(id)),
|
||||
("Pos", doubles(&[1.5, 200.0, 4.5])),
|
||||
("CustomName", string("kept")),
|
||||
("Tags", Tag::List(vec![string("kept")])),
|
||||
]);
|
||||
if let Some(input) = input.clone() {
|
||||
put(&mut raw, "Inventory", input);
|
||||
}
|
||||
if species == Species::WanderingTrader {
|
||||
put(&mut raw, "Offers", offers.clone());
|
||||
put(&mut raw, "DespawnDelay", Tag::Int(48000));
|
||||
}
|
||||
if species == Species::Pillager {
|
||||
put(&mut raw, "PatrolLeader", Tag::Byte(1));
|
||||
put(&mut raw, "CanJoinRaid", Tag::Byte(0));
|
||||
}
|
||||
let mut mob = mob_from_nbt(&raw, &types, 1, None).unwrap();
|
||||
let has_stack = matches!(&input, Some(Tag::List(v)) if !v.is_empty());
|
||||
assert_eq!(
|
||||
mob.extra.inventory.len(),
|
||||
usize::from(has_stack),
|
||||
"{id} adoption"
|
||||
);
|
||||
let expected = Tag::List(if has_stack {
|
||||
vec![stack.clone()]
|
||||
} else {
|
||||
vec![]
|
||||
});
|
||||
let mut stale = raw.clone();
|
||||
put(
|
||||
&mut stale,
|
||||
"Inventory",
|
||||
Tag::List(vec![Tag::compound([
|
||||
("id", string("minecraft:dirt")),
|
||||
("count", Tag::Int(2)),
|
||||
])]),
|
||||
);
|
||||
for base in [None, Some(&raw), Some(&stale)] {
|
||||
let saved = mob_to_nbt(&mob, [137, 0, 0, 1], base, None, |_| None);
|
||||
assert_eq!(saved.get("Inventory"), Some(&expected), "{id} save");
|
||||
if base.is_some() {
|
||||
assert_eq!(saved.get("CustomName"), raw.get("CustomName"));
|
||||
assert_eq!(saved.get("Tags"), raw.get("Tags"));
|
||||
}
|
||||
if species == Species::WanderingTrader {
|
||||
assert_eq!(saved.get("Offers"), Some(&offers));
|
||||
assert_eq!(saved.get("DespawnDelay"), Some(&Tag::Int(48000)));
|
||||
}
|
||||
if species == Species::Pillager {
|
||||
assert_eq!(saved.get("PatrolLeader"), Some(&Tag::Byte(1)));
|
||||
assert_eq!(saved.get("CanJoinRaid"), Some(&Tag::Byte(0)));
|
||||
}
|
||||
}
|
||||
if has_stack {
|
||||
mob.extra.inventory[0].count = 4;
|
||||
let mut changed = stack.clone();
|
||||
put(&mut changed, "count", Tag::Int(4));
|
||||
assert_eq!(
|
||||
mob_to_nbt(&mob, [137, 0, 0, 1], Some(&raw), None, |_| None)
|
||||
.get("Inventory"),
|
||||
Some(&Tag::List(vec![changed]))
|
||||
);
|
||||
}
|
||||
mob.extra.inventory.clear();
|
||||
assert_eq!(
|
||||
mob_to_nbt(&mob, [137, 0, 0, 1], Some(&stale), None, |_| None).get("Inventory"),
|
||||
Some(&Tag::List(vec![]))
|
||||
);
|
||||
}
|
||||
}
|
||||
// This InventoryCarrier projection does not add a pig inventory.
|
||||
let mut kind = crate::mobs::sensor_scan_tests::kind();
|
||||
Arc::make_mut(&mut kind).id = "minecraft:pig".into();
|
||||
Arc::make_mut(&mut kind).species = Species::Pig;
|
||||
let mut pig = Mob::new(2, kind, Vec3::ZERO, 0.0);
|
||||
pig.extra
|
||||
.inventory
|
||||
.push(crate::save::item_from_nbt(&stack).unwrap());
|
||||
assert!(
|
||||
mob_to_nbt(&pig, [137, 0, 0, 2], None, None, |_| None)
|
||||
.get("Inventory")
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1083,7 +1083,7 @@ pub struct Extra {
|
||||
pub chested: bool,
|
||||
/// The chest contents of a donkey or mule (15 slots).
|
||||
pub items: Vec<Option<crate::items::ItemStack>>,
|
||||
/// `Piglin.inventory` (`SimpleContainer(8)`): what it picked up and kept.
|
||||
/// Occupied InventoryCarrier stacks: Piglin/trader (8 slots), Pillager (5).
|
||||
pub inventory: Vec<crate::items::ItemStack>,
|
||||
pub tame: bool,
|
||||
pub sitting: bool,
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Pillager and WanderingTrader saved Inventory
|
||||
|
||||
Pinned Java 26.3 Pillager and AbstractVillager read/write Inventory through
|
||||
InventoryCarrier. Their SimpleContainers have five and eight slots respectively.
|
||||
The saved list contains occupied ItemStacks and is written even when empty.
|
||||
|
||||
Horizon now adopts these two species' input into the existing `extra.inventory`
|
||||
model through the existing item codec and projects that model on save. It does
|
||||
not substitute a constant empty list. Piglin code is unchanged; Villager keeps
|
||||
its separate model. Existing AI/drop consumers of this field are Piglin-gated.
|
||||
No pickup, raid, loot or trade path is changed.
|
||||
|
||||
## Actual Game proof
|
||||
|
||||
Frozen base: `41f822b659789780f58d32c63ddfb6887ea1b8bf`. Both own executables
|
||||
use this base; the old build includes only the new cfg(test) regression before
|
||||
any gameplay edit. One persistent checkout and fresh isolated game homes are used.
|
||||
|
||||
```sh
|
||||
python3 tools/test_carrier_inventory_parity.py \
|
||||
--binary /absolute/path/to/horizon-client --work-dir /fresh/output
|
||||
```
|
||||
|
||||
The predeclared 131 complete-record program has independent absent, explicit-empty
|
||||
and valid single-stack cases for each species, plus pig absence and Piglin
|
||||
empty/nonempty controls. Actual UUID/type/Health/Pos/Motion/NoAI/NoGravity,
|
||||
loaded floor/air and exact relative clock 0 are positive admissions. Inventory
|
||||
reads use fresh result/success sentinels and exact list/presence/item types.
|
||||
Stone 3 with custom_data.audit Int 137 is preserved. Trader positive Offers and
|
||||
Int 48000 DespawnDelay, and Pillager PatrolLeader1/CanJoinRaid0 remain strict.
|
||||
Fresh pinned Java runs with `_JAVA_OPTIONS=-Xmx1G` and owned prepared resources.
|
||||
|
||||
Untouched Game: **123/131**, all admissions passing. Eight differences are the
|
||||
read/success/presence/type observations of the two missing empty defaults, not
|
||||
eight independent defects. Fixed: **131/131**. Script and fresh reference bytes
|
||||
must be identical across the pair.
|
||||
|
||||
- Program SHA256: `a376c401c2ce2ceb07055217698f135879a9eb15b37778fce18c7033f1fd7317`
|
||||
- Reference SHA256: `017c5db07aae44a17735f39a4978032f6ca32e1e7a7ba4ed1ec84d4b35ba0fab`
|
||||
|
||||
The resource-free real decode/save regression
|
||||
`entity_nbt::carrier_inventory_tests::carrier_inventory_is_adopted_and_saved_from_the_model`
|
||||
fails old on absent Pillager Inventory. It checks actual model adoption, raw
|
||||
absent/present, no-raw and stale-raw saves, single-stack components, changed count4
|
||||
and cleared model output. Offers/DespawnDelay and Pillager flags are retained;
|
||||
Piglin and pig controls protect existing behavior. The identical fixed regression passes in the required Rust suite.
|
||||
|
||||
## Diagnostics and scope
|
||||
|
||||
Nine whole bodies and two absolute clock reads are predeclared raw traces,
|
||||
retained in full in java.log/rust.log separately from the 131-record contract.
|
||||
Existing Pillager CanPickUpLoot is Java1/native 0; that load-flag gap is retained,
|
||||
not repaired. The fixed raw residuals are:
|
||||
|
||||
| Raw records | Java | Native (unchanged apart from corrected Inventory defaults) |
|
||||
|---|---|---|
|
||||
| 3 Pillager bodies | CanPickUpLoot1, attributes[] | CanPickUpLoot0, movement_speed base0.3499999940395355 |
|
||||
| 3 trader bodies | attributes[] | movement_speed base0.7 |
|
||||
| pig body | attributes[] | movement_speed base0.25 |
|
||||
| 2 Piglin bodies | CannotHunt0, CanPickUpLoot1 | CannotHunt absent, CanPickUpLoot0 |
|
||||
| start/end absolute clocks | old Java305 / fixed Java269 | native 0 |
|
||||
|
||||
Both old and fixed retain all 11 raw diagnostic differences. Before the fix the
|
||||
two absent-input bodies additionally lack Inventory; the fixed bodies contain
|
||||
it. Complete styled records/callbacks are retained without normalization. No full-body or
|
||||
absolute-clock equality claim; no masks/tolerances in the complete canonical records.
|
||||
|
||||
The first Java draft expected empty Offers.Recipes to remain present; Java saves
|
||||
empty Offers as {}. Its script/reference/raws remain preserved as a rejected
|
||||
setup expectation. The revised positive-offer fixture is independently Java-admitted.
|
||||
|
||||
Runtime coverage is these vanilla species and single valid stacks below capacity.
|
||||
The existing occupied-stack representation and item codec are reused, with source
|
||||
bounds 5/8. SimpleContainer.fromItemList aggregation (H138), multiple-stack merging,
|
||||
max-stack/capacity redesign, numeric slot access, other species and lifecycle/reopen
|
||||
behavior are outside this proof. The ledger retains these limits.
|
||||
@@ -224,3 +224,5 @@ LingeringPotionItem.createPotion port crates/horizon-client/src/game/potions.rs:
|
||||
LingeringPotionItem.use inline crates/horizon-client/src/game/potions.rs::Game::use_potion_item lingering potion throw
|
||||
NameTagItem.interactLivingEntity port crates/horizon-client/src/game/names.rs::Game::name_mob a tag with a custom name names the mob under the crosshair, makes it persistent and is used up, in creative too
|
||||
PlaceOnWaterBlockItem.use inline crates/horizon-client/src/game/block_items.rs::Game::apply_placed_item placed by generic block placement; the water rule is not checked here
|
||||
Pillager.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_to_nbt modeled occupied Inventory list, including empty; inherited Raider fields separate
|
||||
Pillager.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt Inventory adoption into five-slot occupied-stack model; gap: aggregation and existing pickup-flag load remain separate
|
||||
|
@@ -19,8 +19,8 @@ CatSpawner.spawnInVillage port crates/horizon-client/src/game/cat_spawner.rs::Ga
|
||||
CatSpawner.spawnCat port crates/horizon-client/src/game/cat_spawner.rs::Game::spawn_cat black variant inside cats_spawn_as_black structures; persistence flag from the caller
|
||||
InventoryCarrier.pickUpItem port crates/horizon-client/src/game/villagers.rs::Game::villager_pickups gap: minor: villager arm only; piglins, sulfur cubes, pandas and others have their own pickup fns
|
||||
AbstractVillager.finalizeSpawn port crates/horizon-client/src/mobs/finalize.rs::Ctx::finalize villager arm: type, then the baby group
|
||||
AbstractVillager.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_to_nbt Offers in the villager arm; inventory via villager::write_nbt
|
||||
AbstractVillager.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::read_villager Offers read here; inventory via villager::read_nbt
|
||||
AbstractVillager.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_to_nbt Offers; Villager inventory via villager::write_nbt, WanderingTrader inventory via extra.inventory, including empty
|
||||
AbstractVillager.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt Offers via read_villager; Villager inventory via villager::read_nbt, WanderingTrader via eight-slot extra.inventory; aggregation gap retained
|
||||
AbstractVillager.getSlot missing minor: commands cannot address a villager inventory slot (container.N); mob slot names cover weapon, armour and saddle only
|
||||
AbstractVillager.addOffersFromItemListings port crates/horizon-client/src/merchant.rs::Trades::offers_from_set the allow_duplicates path: a picked trade stays in the pool
|
||||
AbstractVillager.addOffersFromItemListingsWithoutDuplicates port crates/horizon-client/src/merchant.rs::Trades::offers_from_set the no-duplicates path: a picked trade leaves the pool
|
||||
|
||||
|
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pillager/trader Inventory through real summon/get commands."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import test_command_exec_parity as driver
|
||||
|
||||
STACK = '{id:"minecraft:stone",count:3,components:{"minecraft:custom_data":{audit:137}}}'
|
||||
OFFER = '{buy:{id:"minecraft:emerald",count:1},sell:{id:"minecraft:dirt",count:2}}'
|
||||
CASES = [(kind, state) for kind in ['pillager', 'wandering_trader']
|
||||
for state in ['absent', 'empty', 'stack']]
|
||||
CASES += [('pig', 'absent'), ('piglin', 'absent'), ('piglin', 'stack')]
|
||||
|
||||
|
||||
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', 'owner-position', 1)
|
||||
cmd('/gamemode creative @s', 'owner-mode')
|
||||
for condition, label in [('loaded 8 200 8', 'loaded'), ('block 8 199 8 stone', 'floor-state'), ('block 8 200 8 air', 'air'), ('entity @s[type=player,name=Player,nbt={Health:20f,playerGameType:1}]', 'owner')]:
|
||||
cmd('/execute if ' + condition, label, 1)
|
||||
cmd('/scoreboard objectives add h137 dummy', 'objective')
|
||||
cmd('/execute store result score start h137 run time query gametime', 'clock-start', diagnostic=True)
|
||||
for index, (kind, state) in enumerate(CASES, 1):
|
||||
label = kind+'-'+state
|
||||
uid = f'00000089-0000-0000-0000-{index:012x}'
|
||||
x = 1.5 + 1.5 * (index - 1)
|
||||
hp = {'pillager':24, 'wandering_trader':20, 'pig':10, 'piglin':16}[kind]
|
||||
extra = '' if state == 'absent' else ',Inventory:' + ('[]' if state == 'empty' else '['+STACK+']')
|
||||
if kind == 'pillager': extra += ',PatrolLeader:1b,CanJoinRaid:0b'
|
||||
if kind == 'wandering_trader': extra += ',DespawnDelay:48000,Offers:{Recipes:['+OFFER+']}'
|
||||
if kind == 'piglin': extra += ',IsImmuneToZombification:1b'
|
||||
cmd(f'/summon {kind} {x} 200 4.5 {{UUID:[I;137,0,0,{index}],Health:{hp}f,NoAI:1b,NoGravity:1b,Motion:[0d,0d,0d],Tags:["h137_{index}"],CustomName:"h137_{index}"{extra}}}', label+'-summon', 1)
|
||||
cmd(f'/execute if entity @e[type={kind},nbt={{UUID:[I;137,0,0,{index}],Health:{hp}f,Pos:[{x}d,200d,4.5d],Motion:[0d,0d,0d],NoAI:1b,NoGravity:1b,Tags:["h137_{index}"],CustomName:"h137_{index}"}}]', label+'-actor', 1)
|
||||
cmd(f'/data get entity {uid} UUID', label+'-uuid', 4)
|
||||
cmd(f'/data get entity {uid}', label+'-body', diagnostic=True)
|
||||
for holder in ['result', 'success']:
|
||||
cmd(f'/scoreboard players set {holder} h137 -99', label+'-reset-'+holder, -99)
|
||||
cmd(f'/execute store result score result h137 store success score success h137 run data get entity {uid} Inventory', label+'-inventory-read', admission=False)
|
||||
for holder in ['result', 'success']:
|
||||
cmd(f'/scoreboard players get {holder} h137', label+'-inventory-'+holder, admission=False)
|
||||
if kind == 'pig':
|
||||
cmd(f'/execute unless data entity {uid} Inventory', label+'-inventory-absent', admission=False)
|
||||
else:
|
||||
cmd(f'/execute if data entity {uid} Inventory', label+'-inventory-present', admission=False)
|
||||
expected = '[]' if state != 'stack' else '['+STACK+']'
|
||||
cmd(f'/execute if data entity {uid} {{Inventory:{expected}}}', label+'-inventory-type', admission=False)
|
||||
if state == 'stack':
|
||||
cmd(f'/data get entity {uid} Inventory[0].id', label+'-item-id', admission=False)
|
||||
cmd(f'/data get entity {uid} Inventory[0].count', label+'-item-count', admission=False)
|
||||
cmd(f'/data get entity {uid} Inventory[0].components."minecraft:custom_data".audit', label+'-item-data', admission=False)
|
||||
else:
|
||||
cmd(f'/execute unless data entity {uid} Inventory[0]', label+'-no-item', admission=False)
|
||||
if kind == 'pillager':
|
||||
cmd(f'/execute if data entity {uid} {{PatrolLeader:1b,CanJoinRaid:0b}}', label+'-flags', 1)
|
||||
if kind == 'wandering_trader':
|
||||
cmd(f'/data get entity {uid} DespawnDelay', label+'-despawn', 48000)
|
||||
cmd(f'/execute if data entity {uid} {{DespawnDelay:48000,Offers:{{Recipes:[{OFFER}]}}}}', label+'-trader-types', 1)
|
||||
cmd(f'/data get entity {uid} Offers.Recipes[0].sell.count', label+'-offers', 2)
|
||||
if kind == 'piglin':
|
||||
cmd(f'/execute if data entity {uid} {{IsImmuneToZombification:1b}}', label+'-immune', 1)
|
||||
cmd('/execute store result score end h137 run time query gametime', 'clock-end', diagnostic=True)
|
||||
cmd('/scoreboard players operation end h137 -= start h137', 'clock-delta-operation', 0)
|
||||
cmd('/scoreboard players get end h137', '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='carrier-inventory-', 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: [('Carrier inventory', 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()
|
||||
Reference in new issue
Block a user