mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Load animal age and love with NumericTag integer conversion
Co-Authored-By: Codex <[email protected]>
This commit is contained in:
1 parent
e5875b0b19
commit
97d414cc4d
6 files changed
+373
-6
No files matched your search
@@ -81,7 +81,7 @@ fn elements(tag: &Tag) -> Option<Vec<Tag>> {
|
||||
}
|
||||
/// `DoubleTag.intValue` / `FloatTag.intValue` floor before the i32 conversion;
|
||||
/// integral tags use their intValue narrowing. Byte arrays then keep the low byte.
|
||||
fn int_value(t: &Tag) -> Option<i32> {
|
||||
pub fn int_value(t: &Tag) -> Option<i32> {
|
||||
Some(match t {
|
||||
Tag::Byte(v) => i32::from(*v),
|
||||
Tag::Short(v) => i32::from(*v),
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::{
|
||||
arrows::Pickup,
|
||||
entities::{ArmorStand, Cushion, ExperienceOrb, ItemEntity, ItemFrame, Painting},
|
||||
mobs::{Mob, MobType, Who},
|
||||
nbt_path::int_value,
|
||||
projectiles::{Projectile, ProjectileKind},
|
||||
save::{
|
||||
doubles, float, floats, item_from_nbt, item_to_nbt, number, put, read_doubles, text,
|
||||
@@ -1689,12 +1690,12 @@ pub fn mob_from_nbt(
|
||||
m.extra.drop_chances = Some(Box::new(chances));
|
||||
}
|
||||
if saves_age(m.species()) {
|
||||
m.extra.age = number(tag.get("Age")).unwrap_or(0) as i32;
|
||||
m.extra.forced_age = number(tag.get("ForcedAge")).unwrap_or(0) as i32;
|
||||
m.extra.age = tag.get("Age").and_then(int_value).unwrap_or(0);
|
||||
m.extra.forced_age = tag.get("ForcedAge").and_then(int_value).unwrap_or(0);
|
||||
m.extra.age_locked = flag(tag, "AgeLocked");
|
||||
}
|
||||
if is_animal(&m.kind.id) {
|
||||
m.extra.in_love = number(tag.get("InLove")).unwrap_or(0) as i32;
|
||||
m.extra.in_love = tag.get("InLove").and_then(int_value).unwrap_or(0);
|
||||
// Animal.readAdditionalSaveData: the player who fed it into love.
|
||||
m.extra.love_cause = crate::save::uuid_from_tag(tag.get("LoveCause"));
|
||||
}
|
||||
@@ -4309,6 +4310,81 @@ mod tests {
|
||||
let cleared = item_entity_to_nbt(&cleared, [1, 2, 3, 4], Some(&tag));
|
||||
assert!(cleared.get("Thrower").is_none());
|
||||
}
|
||||
#[test]
|
||||
fn animal_integer_loads_save_modeled_values_without_raw_source() {
|
||||
let mut kind = crate::mobs::sensor_scan_tests::kind();
|
||||
let def = Arc::make_mut(&mut kind);
|
||||
def.id = "minecraft:pig".into();
|
||||
def.species = crate::mobs::Species::Pig;
|
||||
let types = [("minecraft:pig".into(), kind)].into();
|
||||
for (input, expected) in [
|
||||
(None, 0),
|
||||
(Some(Tag::Int(0)), 0),
|
||||
(Some(Tag::Byte(-7)), -7),
|
||||
(Some(Tag::Short(13)), 13),
|
||||
(Some(Tag::Int(i32::MIN)), i32::MIN),
|
||||
(Some(Tag::Int(i32::MAX)), i32::MAX),
|
||||
(Some(Tag::Long(2147483648)), i32::MIN),
|
||||
(Some(Tag::Long(-2147483649)), i32::MAX),
|
||||
(Some(Tag::Long(4294967296)), 0),
|
||||
(Some(Tag::Long(i64::MAX)), -1),
|
||||
(Some(Tag::Long(i64::MIN)), 0),
|
||||
(Some(Tag::Float(2.5f32.to_bits())), 2),
|
||||
(Some(Tag::Float((-2.5f32).to_bits())), -3),
|
||||
(Some(Tag::Double(2.5f64.to_bits())), 2),
|
||||
(Some(Tag::Double((-2.5f64).to_bits())), -3),
|
||||
(Some(Tag::Float(2147483520f32.to_bits())), 2147483520),
|
||||
(Some(Tag::Float(2147483648f32.to_bits())), i32::MAX),
|
||||
(Some(Tag::Float((-2147483648f32).to_bits())), i32::MIN),
|
||||
(Some(Tag::Float((-2147483904f32).to_bits())), i32::MIN),
|
||||
(Some(Tag::Double(2147483647.75f64.to_bits())), i32::MAX),
|
||||
(Some(Tag::Double(2147483648f64.to_bits())), i32::MAX),
|
||||
(Some(Tag::Double((-2147483647.5f64).to_bits())), i32::MIN),
|
||||
(Some(Tag::Double((-2147483648.5f64).to_bits())), i32::MIN),
|
||||
(Some(Tag::Double(1e20f64.to_bits())), i32::MAX),
|
||||
(Some(Tag::Double((-1e20f64).to_bits())), i32::MIN),
|
||||
(Some(Tag::Float(1e20f32.to_bits())), i32::MAX),
|
||||
(Some(Tag::Float((-1e20f32).to_bits())), i32::MIN),
|
||||
] {
|
||||
let mut raw = Tag::compound([
|
||||
("id", string("minecraft:pig")),
|
||||
("Pos", doubles(&[4.5, 200.0, 4.5])),
|
||||
("NoAI", Tag::Byte(1)),
|
||||
("NoGravity", Tag::Byte(1)),
|
||||
("AgeLocked", Tag::Byte(1)),
|
||||
("Unrelated", string("keep")),
|
||||
]);
|
||||
if let Some(value) = input.as_ref() {
|
||||
for key in ["Age", "ForcedAge", "InLove"] {
|
||||
put(&mut raw, key, value.clone());
|
||||
}
|
||||
}
|
||||
let mob = mob_from_nbt(&raw, &types, 1, None).unwrap();
|
||||
assert_eq!(
|
||||
(mob.extra.age, mob.extra.forced_age, mob.extra.in_love),
|
||||
(expected, expected, expected),
|
||||
"{input:?}"
|
||||
);
|
||||
assert!(mob.no_ai && mob.no_gravity && mob.extra.age_locked);
|
||||
for source in [None, Some(&raw)] {
|
||||
let saved = mob_to_nbt(&mob, [0, 0, 0, 55], source, None);
|
||||
for key in ["Age", "ForcedAge", "InLove"] {
|
||||
assert_eq!(
|
||||
saved.get(key),
|
||||
Some(&Tag::Int(expected)),
|
||||
"{key}: {input:?}"
|
||||
);
|
||||
}
|
||||
assert_eq!(saved.get("NoAI"), Some(&Tag::Byte(1)));
|
||||
assert_eq!(saved.get("AgeLocked"), Some(&Tag::Byte(1)));
|
||||
assert_eq!(
|
||||
saved.get("Unrelated"),
|
||||
source.and_then(|tag| tag.get("Unrelated"))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn animal_nbt_preserves_signed_integer_in_love_and_defaults_after_removal() {
|
||||
let mut kind = crate::mobs::sensor_scan_tests::kind();
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# Animal integer NBT loads
|
||||
|
||||
Only `Age`, `ForcedAge` and `InLove` load conversion is changed. Pinned Java26.3
|
||||
`AgeableMob.readAdditionalSaveData` and `Animal.readAdditionalSaveData` call
|
||||
`ValueInput.getIntOr`. `TagValueInput` invokes `NumericTag.intValue`: Float/Double
|
||||
use `Mth.floor` (Math.floor followed by JVM d2i), while Long narrows to32 bits.
|
||||
Both finite overflow signs are measured; no guessed cast-then-decrement formula.
|
||||
|
||||
The existing `nbt_path::int_value` has precisely those semantics. Make that
|
||||
unchanged helper available to the loader instead of going through an i64 view.
|
||||
No global `number`/boolean helper, save clamp, NoAI, growth, breeding, ItemEntity
|
||||
Health or other codec repair. Signed InLove saving is already independently fixed
|
||||
by merged #7609; its saver remains untouched and negative controls stay strict.
|
||||
Pending #10344 (ItemEntity Health) and #9991 (baby eligibility) are not imported.
|
||||
|
||||
## Frozen actual Game and fresh Java
|
||||
|
||||
Base: `e5875b0b192e64c2de121e30940d39bdd4c7728b`.
|
||||
Existing authoritative command harness, no network observer or world steps.
|
||||
27 fresh fixed-UUID NoAI/NoGravity pigs cover absent/default, Byte/Short/Int,
|
||||
Long narrowing, Float/Double positive/negative fractions, adjacent int bounds and
|
||||
large finite overflow. Actual UUID/type/tag/Health10/Pos/flags, owner identity,
|
||||
loaded stone/air geometry and relative world-clock0 positively admit every case.
|
||||
Age is allowed to be negative. Every field read resets result/success to-99 first.
|
||||
Fresh Java independently verifies all expected callbacks and result/success stores.
|
||||
All three fields save as Int, including defaults and negative values.
|
||||
|
||||
Predeclared strict **666** complete records; **695** executable full diagnostic.
|
||||
Only the27 whole-body rows and clock-start/end are diagnostic; all commands remain
|
||||
raw traced. Scalar messages/styles/field types/callbacks/stores and every setup
|
||||
control remain strict. No masks/tolerances or after-capture selection changes.
|
||||
|
||||
```
|
||||
python3 tools/test_ageable_integer_load_parity.py --binary /path/to/old-game --work-dir /scratch/old
|
||||
python3 tools/test_ageable_integer_load_parity.py --binary /path/to/fixed-game --work-dir /scratch/fixed --reference /scratch/old/vanilla.jsonl
|
||||
python3 tools/test_ageable_integer_load_parity.py --full-diagnostic --binary /path/to/game --work-dir /scratch/full
|
||||
```
|
||||
|
||||
Emitted core script SHA256: `e5a6bcb1cae0853480fd72ce9675bdd0123d3d629c9d38af40f229d9c969d08d`.
|
||||
Emitted full script SHA256: `f7fe07b37956beb8bf22143d50dd76e26961359df625c3bf8dc59ce130cbefd9`.
|
||||
|
||||
Old current: **606/666**. Exactly60 differences: scalar read and result-store for
|
||||
these ten cases across EACH of Age/ForcedAge/InLove. Every success remains true/1;
|
||||
these are numeric state failures, not parse/callback-admission failures. All other
|
||||
controls pass, including signed integer InLove, Long narrowing and default types.
|
||||
Full old diagnostic: **606/695**, retaining all27 bodies and raw absolute clocks.
|
||||
|
||||
| Exact case prefix | Java saved Int (each field) | Old native saved Int |
|
||||
|---|---|---|
|
||||
| `float_neg` | `-3` | `-2` |
|
||||
| `double_neg` | `-3` | `-2` |
|
||||
| `float_above` | `2147483647` | `-2147483648` |
|
||||
| `float_below` | `-2147483648` | `2147483392` |
|
||||
| `double_above` | `2147483647` | `-2147483648` |
|
||||
| `double_neg_inside` | `-2147483648` | `-2147483647` |
|
||||
| `double_huge` | `2147483647` | `-1` |
|
||||
| `double_neg_huge` | `-2147483648` | `0` |
|
||||
| `float_huge` | `2147483647` | `-1` |
|
||||
| `float_neg_huge` | `-2147483648` | `0` |
|
||||
|
||||
## Reachable regression and retained evidence
|
||||
|
||||
The resource-free unit calls actual `mob_from_nbt`, checks all three modeled
|
||||
fields, then `mob_to_nbt` with NO raw source and with the original compound. It
|
||||
checks exact Int types/values, absent defaults, NoAI/AgeLocked and unrelated raw
|
||||
field preservation. Expected constants are independently checked against Java;
|
||||
no copied cast algorithm or direct model-field setter substitutes for decoding.
|
||||
|
||||
Owner-local `.local/h055` retains complete raw styled records, every program and
|
||||
reference, source/bytecode/ownership evidence, logs and old/fixed executables.
|
||||
Available timestamps/stage logs remain retained; unknown event times stay unknown.
|
||||
No game files are published, and no broad whole-body/default-attribute/order or
|
||||
startup-clock parity is claimed.
|
||||
|
||||
The predeclared input specifications (compact JSON hashes3769488f… /31f2a24d…)
|
||||
include expected-result assertions for Java admission. The existing driver strips
|
||||
only `expect_result` before execution and emits ordinary JSON spacing. The hashes
|
||||
above identify those actual execution bytes; all commands, labels and comparison
|
||||
selection are unchanged. Both specification and execution forms remain retained.
|
||||
|
||||
## Fixed paired result
|
||||
|
||||
**606/666 old →666/666 fixed**, using byte-identical emitted input scripts and
|
||||
fresh Java reference bytes. All81 individual state reads and their independently
|
||||
reset result/success stores pass. The resource-free decode-to-save regression also
|
||||
fails old at the three wrong values and passes fixed across27 inputs. The shared
|
||||
NBT-path helper body is byte-for-byte unchanged (visibility only), and ledger
|
||||
mapping validation reports249 rows with no problems.
|
||||
|
||||
## Complete diagnostic residuals
|
||||
|
||||
Full diagnostic **606/695 old →666/695 fixed**. Same emitted full script bytes;
|
||||
the independent fresh full Java references differ only at absolute clocks. All29
|
||||
remaining complete raw mismatches are listed below; excerpts only describe the
|
||||
difference and never replace whole-record comparison. Full styles/callbacks/body
|
||||
values remain retained. No695-green or whole-body/raw-clock equality claim.
|
||||
|
||||
| Exact label | Java expected | Fixed native actual |
|
||||
|---|---|---|
|
||||
| `clock-start` | `old fresh: 309; fixed fresh: 293` | `0` |
|
||||
| `absent-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `zero-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `byte-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `short-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `int_min-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `int_max-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `long_above-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `long_below-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `long_wrap-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `long_max-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `long_min-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `float_pos-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `float_neg-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `double_pos-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `double_neg-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `float_inside-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `float_above-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `float_min-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `float_below-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `double_inside-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `double_above-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `double_neg_inside-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `double_below-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `double_huge-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `double_neg_huge-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `float_huge-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `float_neg_huge-body` | `attributes: []` | `attributes: [{id: "minecraft:movement_speed", base: 0.25d}]` |
|
||||
| `clock-end` | `old fresh: 309; fixed fresh: 293` | `0` |
|
||||
|
||||
Core fresh reference SHA256: `cb1532bb798fbfbc3f24794bacc0fb6e8192fbd9eb2120eb2ad3c1c72640da70`.
|
||||
|
||||
## Required checks and later source checkpoint
|
||||
|
||||
Final `pr_ready.py --quick --base e5875b0b192e64c2de121e30940d39bdd4c7728b`:
|
||||
2024 Rust tests pass, one fails. The existing `drops_match_vanillas_motion_bit_for_bit`
|
||||
Z last-bit difference at yaw37.5/pitch-21.25 is actual4597448471913462162 versus
|
||||
expected4597448471913462163. This task's OWN archived old executable reproduces
|
||||
exactly the same failure. No drop-motion repair. The resource-free H055 test passes
|
||||
both explicitly and in the full run; the core suite has no failures.
|
||||
|
||||
Python:696 tests, four existing macOS packaging failures, one skip. The installer
|
||||
signature fixture supplies x86_64 assets on this ARM64 host; three optional-release
|
||||
archive combinations hit Bash3.2 unset arrays. A narrow installer rerun confirms
|
||||
its prerequisite. The readiness command exits nonzero; no fully green claim.
|
||||
Rustfmt, clippy with warnings denied, doc tests and public-tree checks pass. Broad
|
||||
parity is skipped by `--quick`; the scoped/full actual Game pairs ran separately.
|
||||
The new driver is discovered by the ordinary parity runner without extra flags.
|
||||
|
||||
Latest actual main pull: `2993ef0b205e7f6fb9478a1af9fd5084b31a5e5a`.
|
||||
The integer loader/helpers, command harness/corpus and pinned lock have no delta
|
||||
from frozen e5875b0b1. Later changes add opt-in legacy combat/rendering, unrelated
|
||||
to these zero-tick reads. No combined-latest runtime claim or repeat of unaffected
|
||||
proof. Evidence-only documentation completion is followed by whitespace/public
|
||||
source checks; no production edits follow the required run.
|
||||
@@ -2,7 +2,7 @@ AbstractFish.addAdditionalSaveData inline crates/horizon-client/src/entity_nbt.r
|
||||
AbstractFish.readAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::mob_from_nbt FromBucket via read_variant defaults false; tadpole ignores loaded input
|
||||
AgeableWaterCreature.handleAirSupply inline crates/horizon-client/src/mobs/ai.rs::base_tick the needs_water branch of base_tick; dolphins override it to nothing and refill air in aquatic.rs
|
||||
AgeableWaterCreature.checkSurfaceAgeableWaterCreatureSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::surface_water
|
||||
Animal.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt InLove is read; LoveCause is not kept
|
||||
Animal.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt InLove through NumericTag.intValue; existing LoveCause UUID loading retained
|
||||
Animal.canMate port crates/horizon-client/src/mobs/animal_brain.rs::can_mate_with brain breeding; the Breed goal in goals.rs does the same for goal-driven animals
|
||||
Animal.handleEntityEvent port crates/horizon-client-render/src/particles.rs::entity_event_burst event 18 love hearts
|
||||
Animal.getDismountLocationForPassenger port crates/horizon-client/src/game/mounts.rs::Game::dismount_mount gap: tries both sides by the mob's yaw, then on top; not DismountHelper's offsets and pose boxes
|
||||
|
||||
|
@@ -44,7 +44,7 @@ AbstractInterpolationHandler.setLastPositionAndRotation skip:other only feeds t
|
||||
AgeableMob.defineSynchedData port crates/horizon-client/src/game/lan.rs::mob_meta the baby flag (baby_index) and AGE_LOCKED after it; a kind outside baby_index (sulfur cube, nautilus, happy ghast) sends neither
|
||||
AgeableMob.getAge inline crates/horizon-client/src/game/remote_mob_data.rs::apply m.extra.age on the host; a remote client only learns baby or not (-1 or 0)
|
||||
AgeableMob.addAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_to_nbt modeled Age, ForcedAge and AgeLocked for baby-capable creatures; zombie nautiluses omit ignored raw age keys
|
||||
AgeableMob.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt Age, ForcedAge and AgeLocked
|
||||
AgeableMob.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt Age/ForcedAge through NumericTag.intValue; AgeLocked unchanged
|
||||
AnimationState.getTimeInMillis port crates/horizon-client/src/mobs/keyframes.rs::AnimationState::millis
|
||||
AreaEffectCloud.defineSynchedData port crates/horizon-client/src/game/lan_entities.rs::Game::lan_projectile_states radius, waiting and particle for dragon breath and lingering clouds; read back in remote_projectiles.rs::remote_projectile_data
|
||||
AreaEffectCloud.setRadius inline crates/horizon-client/src/game/potions.rs::Game::tick_potion_cloud loaded radius and nonzero per-tick/per-use changes clamp to 0..32
|
||||
|
||||
|
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Ageable/Animal integer NBT loading through actual Game and fresh pinned Java.
|
||||
|
||||
Zero observation ticks. Full bodies and absolute startup clocks remain executable
|
||||
strict diagnostics; all scalar values/types, feedback/callbacks/stores and actor
|
||||
admissions are compared without masks or tolerances.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import test_command_exec_parity as driver
|
||||
|
||||
# Independently source-derived expectations, required to pass fresh Java before
|
||||
# the native capture. All floating inputs are finite; no NaN/boolean expansion.
|
||||
CASES = [
|
||||
('absent', None, 0), ('zero', '0', 0), ('byte', '-7b', -7), ('short', '13s', 13),
|
||||
('int_min', '-2147483648', -2147483648), ('int_max', '2147483647', 2147483647),
|
||||
('long_above', '2147483648L', -2147483648), ('long_below', '-2147483649L', 2147483647),
|
||||
('long_wrap', '4294967296L', 0), ('long_max', '9223372036854775807L', -1),
|
||||
('long_min', '-9223372036854775808L', 0),
|
||||
('float_pos', '2.5f', 2), ('float_neg', '-2.5f', -3),
|
||||
('double_pos', '2.5d', 2), ('double_neg', '-2.5d', -3),
|
||||
('float_inside', '2147483520f', 2147483520), ('float_above', '2147483648f', 2147483647),
|
||||
('float_min', '-2147483648f', -2147483648), ('float_below', '-2147483904f', -2147483648),
|
||||
('double_inside', '2147483647.75d', 2147483647), ('double_above', '2147483648d', 2147483647),
|
||||
('double_neg_inside', '-2147483647.5d', -2147483648),
|
||||
('double_below', '-2147483648.5d', -2147483648),
|
||||
('double_huge', '1e20d', 2147483647), ('double_neg_huge', '-1e20d', -2147483648),
|
||||
('float_huge', '1e20f', 2147483647), ('float_neg_huge', '-1e20f', -2147483648),
|
||||
]
|
||||
FIELDS = ['Age', 'ForcedAge', 'InLove']
|
||||
|
||||
|
||||
def program(full=False):
|
||||
ops = []
|
||||
|
||||
def cmd(text, label, result=None, admission=True, diagnostic=False):
|
||||
op = {'op': 'cmd', 'cmd': text, 'label': label, 'admission': admission}
|
||||
if result is not None:
|
||||
op['expect_result'] = result
|
||||
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)
|
||||
ops.append({'op': 'settle', 'required_chunks': [[0, 0]]})
|
||||
cmd('/execute if loaded 4 200 4', 'loaded', 1)
|
||||
cmd('/execute if block 4 199 4 stone', 'actor-support', 1)
|
||||
cmd('/execute if block 4 200 4 air', 'actor-air', 1)
|
||||
cmd('/tp @s 12.5 200 12.5 0 0', 'owner-position', 1)
|
||||
cmd('/gamemode creative @s', 'owner-mode')
|
||||
cmd('/execute if entity @s[type=player,name=Player]', 'owner-type', 1)
|
||||
for key, expected in [('UUID', 4), ('Health', 20), ('playerGameType', 1), ('Pos', 3)]:
|
||||
cmd('/data get entity @s ' + key, 'owner-' + key, expected)
|
||||
cmd('/scoreboard objectives add h055 dummy', 'objective')
|
||||
cmd('/execute store result score start h055 run time query gametime', 'clock-start', diagnostic=True)
|
||||
for n, (case, source, expected) in enumerate(CASES, 1):
|
||||
uuid = f'00000037-0000-0000-0000-{n:012x}'
|
||||
values = '' if source is None else ',' + ','.join(k + ':' + source for k in FIELDS)
|
||||
cmd(f'/summon pig 4.5 200 4.5 {{UUID:[I;55,0,0,{n}],Health:10f,NoAI:1b,NoGravity:1b,'
|
||||
f'PersistenceRequired:1b,AgeLocked:1b,Tags:["h055"]{values}}}', case + '-summon', 1)
|
||||
cmd(f'/execute as {uuid} if entity @s[type=pig,tag=h055]', case + '-type', 1)
|
||||
for key, result in [('UUID', 4), ('Health', 10), ('Pos', 3), ('NoAI', 1),
|
||||
('NoGravity', 1), ('PersistenceRequired', 1), ('AgeLocked', 1)]:
|
||||
cmd(f'/data get entity {uuid} {key}', case + '-' + key, result)
|
||||
for key in FIELDS:
|
||||
label = case + '-' + key
|
||||
for holder in ['result', 'success']:
|
||||
cmd(f'/scoreboard players set {holder} h055 -99', label + '-reset-' + holder, -99)
|
||||
cmd(f'/execute store result score result h055 store success score success h055 '
|
||||
f'run data get entity {uuid} {key}', label + '-read', expected, admission=False)
|
||||
cmd('/scoreboard players get result h055', label + '-result', expected, admission=False)
|
||||
cmd('/scoreboard players get success h055', label + '-success', 1, admission=False)
|
||||
cmd(f'/data get entity {uuid}', case + '-body', diagnostic=True)
|
||||
cmd('/execute store result score end h055 run time query gametime', 'clock-end', diagnostic=True)
|
||||
cmd('/scoreboard players operation end h055 -= start h055', 'clock-delta-operation', 0)
|
||||
cmd('/scoreboard players get end h055', '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 / 'ageable-integer-load-parity')]
|
||||
build = Path(sys.argv[sys.argv.index('--work-dir') + 1]).resolve()
|
||||
reference_bytes = reference.read_bytes() if reference else None
|
||||
assert reference is None or reference != build / 'vanilla.jsonl', 'reference must remain separate and immutable'
|
||||
ops = program(full)
|
||||
driver.corpus_module.groups = lambda: [('ageable integer NBT load', 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()]
|
||||
assert len(java) == len(recorded)
|
||||
by_label = {op['label']: row for op, row in zip(recorded, java)}
|
||||
for case, _, expected in CASES:
|
||||
for key in FIELDS:
|
||||
for suffix, value in [('read', expected), ('result', expected), ('success', 1)]:
|
||||
assert by_label[f'{case}-{key}-{suffix}']['results'] == [[True, value]], 'fresh Java admission'
|
||||
if '--reference-only' in sys.argv:
|
||||
return
|
||||
if reference_bytes:
|
||||
assert reference_bytes == (build / 'vanilla.jsonl').read_bytes(), 'fresh reference bytes changed'
|
||||
expected = java
|
||||
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), 'differences': differences,
|
||||
'comparison': 'complete raw records; no masks or tolerances',
|
||||
'reference_sha256': hashlib.sha256((build / 'vanilla.jsonl').read_bytes()).hexdigest()}
|
||||
(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, ('fixture admission', op, a, b)
|
||||
print(f'Strict raw records: {report["passed"]}/{report["records"]}')
|
||||
if differences:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user