mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Canonicalize floating zero when parsing text tags
Co-Authored-By: Codex <[email protected]>
This commit is contained in:
1 parent
41f822b659
commit
1d800a7655
4 files changed
+287
-7
No files matched your search
@@ -257,6 +257,7 @@ impl IntLit {
|
||||
}
|
||||
}
|
||||
|
||||
/// `FloatTag.valueOf` / `DoubleTag.valueOf`: either zero sign uses the zero tag.
|
||||
fn float_tag(text: &str, ty: Option<TypeSuffix>, state: &mut State<SV>) -> Option<Tag> {
|
||||
match ty {
|
||||
Some(TypeSuffix::Float) => {
|
||||
@@ -265,7 +266,7 @@ fn float_tag(text: &str, ty: Option<TypeSuffix>, state: &mut State<SV>) -> Optio
|
||||
state.store_at_mark(delayed("snbt.parser.infinity_not_allowed"));
|
||||
return None;
|
||||
}
|
||||
Some(Tag::Float(v.to_bits()))
|
||||
Some(Tag::Float(if v == 0.0 { 0 } else { v.to_bits() }))
|
||||
}
|
||||
None | Some(TypeSuffix::Double) => {
|
||||
let v: f64 = text.parse().unwrap_or(f64::NAN);
|
||||
@@ -273,7 +274,7 @@ fn float_tag(text: &str, ty: Option<TypeSuffix>, state: &mut State<SV>) -> Optio
|
||||
state.store_at_mark(delayed("snbt.parser.infinity_not_allowed"));
|
||||
return None;
|
||||
}
|
||||
Some(Tag::Double(v.to_bits()))
|
||||
Some(Tag::Double(if v == 0.0 { 0 } else { v.to_bits() }))
|
||||
}
|
||||
_ => {
|
||||
state.store_at_mark(delayed("snbt.parser.expected_float_type"));
|
||||
@@ -1029,3 +1030,82 @@ mod tests {
|
||||
assert!(parse_fully(&item).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod floating_zero_tests {
|
||||
use super::*;
|
||||
use horizon_world::nbt::{Limits, read_network};
|
||||
|
||||
fn cases() -> Vec<(&'static str, Tag)> {
|
||||
vec![
|
||||
("0.0f", Tag::Float(0)),
|
||||
("-0.0f", Tag::Float(0)),
|
||||
("0e0f", Tag::Float(0)),
|
||||
("-0e0f", Tag::Float(0)),
|
||||
("0.0d", Tag::Double(0)),
|
||||
("-0.0d", Tag::Double(0)),
|
||||
("0e0d", Tag::Double(0)),
|
||||
("-0e0d", Tag::Double(0)),
|
||||
("0.0", Tag::Double(0)),
|
||||
("-0.0", Tag::Double(0)),
|
||||
("0e0", Tag::Double(0)),
|
||||
("-0e0", Tag::Double(0)),
|
||||
("1.25f", Tag::Float(0x3fa0_0000)),
|
||||
("-1.25f", Tag::Float(0xbfa0_0000)),
|
||||
("1.25d", Tag::Double(0x3ff4_0000_0000_0000)),
|
||||
("-1.25d", Tag::Double(0xbff4_0000_0000_0000)),
|
||||
("1e-45f", Tag::Float(1)),
|
||||
("-1e-45f", Tag::Float(0x8000_0001)),
|
||||
("5e-324d", Tag::Double(1)),
|
||||
("-5e-324d", Tag::Double(0x8000_0000_0000_0001)),
|
||||
("-0", Tag::Int(0)),
|
||||
]
|
||||
}
|
||||
#[test]
|
||||
fn command_text_keeps_tag_factory_zero_and_nonzero_bits() {
|
||||
for (text, expected) in cases() {
|
||||
assert_eq!(parse_fully(text).unwrap(), expected, "{text}");
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn world_text_keeps_tag_factory_zero_and_nonzero_bits() {
|
||||
for (text, expected) in cases() {
|
||||
let (actual, used) = horizon_world::nbt::parse_snbt(text).unwrap();
|
||||
assert_eq!(used, text.len(), "{text}");
|
||||
assert_eq!(actual, expected, "{text}");
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn binary_decode_already_canonicalizes_only_zero() {
|
||||
for (bits, expected) in [
|
||||
(0u32, 0),
|
||||
(0x8000_0000, 0),
|
||||
(1, 1),
|
||||
(0x8000_0001, 0x8000_0001),
|
||||
(0x3fa0_0000, 0x3fa0_0000),
|
||||
(0xbfa0_0000, 0xbfa0_0000),
|
||||
] {
|
||||
let mut bytes = vec![5];
|
||||
bytes.extend(bits.to_be_bytes());
|
||||
assert_eq!(
|
||||
read_network(&bytes, Limits::default()).unwrap(),
|
||||
(Tag::Float(expected), bytes.len())
|
||||
);
|
||||
}
|
||||
for (bits, expected) in [
|
||||
(0u64, 0),
|
||||
(0x8000_0000_0000_0000, 0),
|
||||
(1, 1),
|
||||
(0x8000_0000_0000_0001, 0x8000_0000_0000_0001),
|
||||
(0x3ff4_0000_0000_0000, 0x3ff4_0000_0000_0000),
|
||||
(0xbff4_0000_0000_0000, 0xbff4_0000_0000_0000),
|
||||
] {
|
||||
let mut bytes = vec![6];
|
||||
bytes.extend(bits.to_be_bytes());
|
||||
assert_eq!(
|
||||
read_network(&bytes, Limits::default()).unwrap(),
|
||||
(Tag::Double(expected), bytes.len())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -836,6 +836,7 @@ impl Snbt<'_> {
|
||||
})
|
||||
}
|
||||
/// A bare word: a typed number, a boolean, or else a string.
|
||||
/// `FloatTag.valueOf` / `DoubleTag.valueOf` canonicalize either zero sign.
|
||||
fn scalar(word: &str) -> Tag {
|
||||
match word {
|
||||
"true" => return Tag::Byte(1),
|
||||
@@ -859,11 +860,18 @@ impl Snbt<'_> {
|
||||
Some(b'b') => body.parse::<i8>().ok().map(Tag::Byte),
|
||||
Some(b's') => body.parse::<i16>().ok().map(Tag::Short),
|
||||
Some(b'l') => body.parse::<i64>().ok().map(Tag::Long),
|
||||
Some(b'f') => body.parse::<f32>().ok().map(|v| Tag::Float(v.to_bits())),
|
||||
Some(_) => body.parse::<f64>().ok().map(|v| Tag::Double(v.to_bits())),
|
||||
None if body.contains('.') || body.contains('e') || body.contains('E') => {
|
||||
body.parse::<f64>().ok().map(|v| Tag::Double(v.to_bits()))
|
||||
}
|
||||
Some(b'f') => body
|
||||
.parse::<f32>()
|
||||
.ok()
|
||||
.map(|v| Tag::Float(if v == 0.0 { 0 } else { v.to_bits() })),
|
||||
Some(_) => body
|
||||
.parse::<f64>()
|
||||
.ok()
|
||||
.map(|v| Tag::Double(if v == 0.0 { 0 } else { v.to_bits() })),
|
||||
None if body.contains('.') || body.contains('e') || body.contains('E') => body
|
||||
.parse::<f64>()
|
||||
.ok()
|
||||
.map(|v| Tag::Double(if v == 0.0 { 0 } else { v.to_bits() })),
|
||||
None => body.parse::<i32>().ok().map(Tag::Int),
|
||||
};
|
||||
parsed.unwrap_or_else(|| Tag::String(JavaString::from(word)))
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# Text floating-zero tag creation (H190)
|
||||
|
||||
Frozen source `41f822b659789780f58d32c63ddfb6887ea1b8bf`. Own actual Game/fresh pinned Java26.3 via `tools/test_text_floating_zero_parity.py`. All120 complete command records compare exact typed scalar text, styles, callbacks, copy/equality/no-write results and fresh result/success sentinels without masks/tolerances. Five separately declared raw traces retain full storage compounds and absolute startup clocks; no whole-compound ordering equality is claimed.
|
||||
|
||||
The20-field matrix includes Float/Double ±zero in suffix/exponent forms, ordinary/exponent unsuffixed doubles, nonzero ±1.25 in both types and signed subnormals1e-40f/1e-310d. Storage anchors independently prove real source/copy existence. Each initial/copy field is read exactly and checked against its typed expected tag. A duplicate copy is a no-write control; setting the equivalent canonical compound must also report no change. Loaded floor/player identity and actual relative-clock0 remain strict. Original negative literals enter the real commands unchanged; the second positive-zero compound is a separate equality test, not input/output normalization.
|
||||
|
||||
Untouched Game: **93/120**; fixed Game: **120/120**, same program and byte-identical fresh Java reference; all control admissions pass. The27 residuals below are six negative-zero variants' source/copy reads and typed predicates, plus three canonical no-write action/store observations. This proves semantic tag equality, not a formatter-only difference.
|
||||
|
||||
## Exact untouched residuals
|
||||
|
||||
| Label | Java expected | Untouched Game actual | Callback expected / actual |
|
||||
|---|---|---|---|
|
||||
| f_plain_neg-source | `Storage h190:source has the following contents: 0.0f` | `Storage h190:source has the following contents: -0.0f` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| f_plain_neg-source-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| f_exp_neg-source | `Storage h190:source has the following contents: 0.0f` | `Storage h190:source has the following contents: -0.0f` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| f_exp_neg-source-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| d_plain_neg-source | `Storage h190:source has the following contents: 0.0d` | `Storage h190:source has the following contents: -0.0d` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| d_plain_neg-source-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| d_exp_neg-source | `Storage h190:source has the following contents: 0.0d` | `Storage h190:source has the following contents: -0.0d` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| d_exp_neg-source-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| bare_plain_neg-source | `Storage h190:source has the following contents: 0.0d` | `Storage h190:source has the following contents: -0.0d` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| bare_plain_neg-source-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| bare_exp_neg-source | `Storage h190:source has the following contents: 0.0d` | `Storage h190:source has the following contents: -0.0d` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| bare_exp_neg-source-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| f_plain_neg-copy | `Storage h190:copy has the following contents: 0.0f` | `Storage h190:copy has the following contents: -0.0f` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| f_plain_neg-copy-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| f_exp_neg-copy | `Storage h190:copy has the following contents: 0.0f` | `Storage h190:copy has the following contents: -0.0f` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| f_exp_neg-copy-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| d_plain_neg-copy | `Storage h190:copy has the following contents: 0.0d` | `Storage h190:copy has the following contents: -0.0d` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| d_plain_neg-copy-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| d_exp_neg-copy | `Storage h190:copy has the following contents: 0.0d` | `Storage h190:copy has the following contents: -0.0d` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| d_exp_neg-copy-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| bare_plain_neg-copy | `Storage h190:copy has the following contents: 0.0d` | `Storage h190:copy has the following contents: -0.0d` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| bare_plain_neg-copy-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| bare_exp_neg-copy | `Storage h190:copy has the following contents: 0.0d` | `Storage h190:copy has the following contents: -0.0d` | `[[True, 0]]` / `[[True, 0]]` |
|
||||
| bare_exp_neg-copy-type | `Test passed. Count: 1` | `Test failed` | `[[True, 1]]` / `[[False, 0]]` |
|
||||
| canonical-no-write | `Nothing changed. The specified properties already have these values` | `Modified storage h190:source` | `[[False, 0]]` / `[[True, 1]]` |
|
||||
| canonical-no-write-result | `result has 0 [h190]` | `result has 1 [h190]` | `[[True, 0]]` / `[[True, 1]]` |
|
||||
| canonical-no-write-success | `success has 0 [h190]` | `success has 1 [h190]` | `[[True, 0]]` / `[[True, 1]]` |
|
||||
|
||||
## Source and scope
|
||||
|
||||
Pinned FloatTag.valueOf and DoubleTag.valueOf compare numerically with zero and return ZERO for either sign. Their ZERO initializers use positive fconst_0/dconst_0. Both text parser paths directly retained parsed sign bits. The repair canonicalizes only numeric zero at their Float/Double construction sites; nonzero and subnormal bits pass through unchanged.
|
||||
|
||||
Resource-free units exercise the real modern command parser and world text parser independently with exact bit/type expectations, including minimum signed subnormals and an unchanged Int control. Both fail old at -0.0f (0x80000000 instead of0). A separate binary-decode control already passes old and covers both zero signs, nonzero signs and signed minimum subnormals. Binary read/write code is unchanged.
|
||||
|
||||
No primitive formatting, arithmetic, global numeric coercion, grammar, compound order or data-command route change. H189 modern execution parsing remains independent; its grammar/routing changes must preserve these factory guards. Historical52-route and358-format diagnostics remain historical/unmodified, not this own current denominator. No root code/resource/reference payload is imported.
|
||||
|
||||
## Reproduce and retain
|
||||
|
||||
```sh
|
||||
python3 tools/test_text_floating_zero_parity.py --reference-only --work-dir /tmp/zero-java/run
|
||||
python3 tools/test_text_floating_zero_parity.py --binary /absolute/path/to/horizon-client --reference /tmp/zero-java/run/vanilla.jsonl --work-dir /tmp/zero-game/run
|
||||
```
|
||||
|
||||
Fresh output directories are required. Complete scripts/declarations, references, raw JSONL/logs, immutable binaries and hashes are retained under `.local/h190/`.
|
||||
|
||||
- Program SHA256: `acdb6aab09c2c1d7865b4bb932c894fe0f218d1ed3dc1020e3dce69f6ab68284`
|
||||
- Fresh Java reference SHA256: `f6ae6637b0352fea2417d80ca87ef59697c9064b189beb80932694004701ff1d`
|
||||
- Untouched Game SHA256: `e45a7e8023e21869d10c1b010f3c3617bb092f62e3b941a94bbd986619d1e131`
|
||||
|
||||
## Raw diagnostic limits
|
||||
|
||||
All120 typed/equality records match. The five separately declared raw records retain compound display-order and absolute startup-clock differences. Complete expected/actual text follows; full styles/callbacks remain in retained logs. No formatting or compound-history repair, raw-clock normalization or whole-body equality claim.
|
||||
|
||||
```text
|
||||
/execute store result score start h190 run time query gametime
|
||||
JAVA: The game time is 301 tick(s)
|
||||
GAME: The game time is 0 tick(s)
|
||||
/data get storage h190:source
|
||||
JAVA: Storage h190:source has the following contents: {anchor: 1b, samples: {f_sub_neg: -1.0E-40f, bare_plain_pos: 0.0d, f_sub_pos: 1.0E-40f, d_sub_neg: -1.0E-310d, f_value_neg: -1.25f, d_plain_neg: 0.0d, d_exp_pos: 0.0d, d_sub_pos: 1.0E-310d, f_value_pos: 1.25f, d_plain_pos: 0.0d, d_exp_neg: 0.0d, f_exp_neg: 0.0f, f_plain_neg: 0.0f, d_value_pos: 1.25d, f_plain_pos: 0.0f, bare_plain_neg: 0.0d, bare_exp_pos: 0.0d, f_exp_pos: 0.0f, bare_exp_neg: 0.0d, d_value_neg: -1.25d}}
|
||||
GAME: Storage h190:source has the following contents: {anchor: 1b, samples: {f_sub_neg: -1.0E-40f, bare_plain_pos: 0.0d, f_sub_pos: 1.0E-40f, d_sub_neg: -1.0E-310d, f_value_neg: -1.25f, d_exp_pos: 0.0d, d_plain_neg: 0.0d, d_sub_pos: 1.0E-310d, f_value_pos: 1.25f, d_exp_neg: 0.0d, d_plain_pos: 0.0d, f_exp_neg: 0.0f, f_plain_neg: 0.0f, d_value_pos: 1.25d, f_plain_pos: 0.0f, bare_plain_neg: 0.0d, bare_exp_pos: 0.0d, f_exp_pos: 0.0f, bare_exp_neg: 0.0d, d_value_neg: -1.25d}}
|
||||
/data get storage h190:source
|
||||
JAVA: Storage h190:source has the following contents: {anchor: 1b, samples: {f_sub_neg: -1.0E-40f, bare_plain_pos: 0.0d, f_sub_pos: 1.0E-40f, d_sub_neg: -1.0E-310d, f_value_neg: -1.25f, d_plain_neg: 0.0d, d_exp_pos: 0.0d, d_sub_pos: 1.0E-310d, f_value_pos: 1.25f, d_plain_pos: 0.0d, d_exp_neg: 0.0d, f_exp_neg: 0.0f, f_plain_neg: 0.0f, d_value_pos: 1.25d, f_plain_pos: 0.0f, bare_plain_neg: 0.0d, bare_exp_pos: 0.0d, f_exp_pos: 0.0f, bare_exp_neg: 0.0d, d_value_neg: -1.25d}}
|
||||
GAME: Storage h190:source has the following contents: {anchor: 1b, samples: {f_sub_neg: -1.0E-40f, bare_plain_pos: 0.0d, f_sub_pos: 1.0E-40f, d_sub_neg: -1.0E-310d, f_value_neg: -1.25f, d_exp_pos: 0.0d, d_plain_neg: 0.0d, d_sub_pos: 1.0E-310d, f_value_pos: 1.25f, d_exp_neg: 0.0d, d_plain_pos: 0.0d, f_exp_neg: 0.0f, f_plain_neg: 0.0f, d_value_pos: 1.25d, f_plain_pos: 0.0f, bare_plain_neg: 0.0d, bare_exp_pos: 0.0d, f_exp_pos: 0.0f, bare_exp_neg: 0.0d, d_value_neg: -1.25d}}
|
||||
/data get storage h190:copy
|
||||
JAVA: Storage h190:copy has the following contents: {anchor: 1b, samples: {f_sub_neg: -1.0E-40f, bare_plain_pos: 0.0d, f_sub_pos: 1.0E-40f, d_sub_neg: -1.0E-310d, f_value_neg: -1.25f, d_plain_neg: 0.0d, d_exp_pos: 0.0d, d_sub_pos: 1.0E-310d, f_value_pos: 1.25f, d_plain_pos: 0.0d, d_exp_neg: 0.0d, f_exp_neg: 0.0f, f_plain_neg: 0.0f, d_value_pos: 1.25d, f_plain_pos: 0.0f, bare_plain_neg: 0.0d, bare_exp_pos: 0.0d, f_exp_pos: 0.0f, bare_exp_neg: 0.0d, d_value_neg: -1.25d}}
|
||||
GAME: Storage h190:copy has the following contents: {anchor: 1b, samples: {f_sub_neg: -1.0E-40f, bare_plain_pos: 0.0d, f_sub_pos: 1.0E-40f, d_sub_neg: -1.0E-310d, f_value_neg: -1.25f, d_exp_pos: 0.0d, d_plain_neg: 0.0d, d_sub_pos: 1.0E-310d, f_value_pos: 1.25f, d_exp_neg: 0.0d, d_plain_pos: 0.0d, f_exp_neg: 0.0f, f_plain_neg: 0.0f, d_value_pos: 1.25d, f_plain_pos: 0.0f, bare_plain_neg: 0.0d, bare_exp_pos: 0.0d, f_exp_pos: 0.0f, bare_exp_neg: 0.0d, d_value_neg: -1.25d}}
|
||||
/execute store result score end h190 run time query gametime
|
||||
JAVA: The game time is 301 tick(s)
|
||||
GAME: The game time is 0 tick(s)
|
||||
```
|
||||
|
||||
## Local validation
|
||||
|
||||
Required pr_ready ran once final: **516 Rust tests passed**, including both text-path bit/type regressions and the already-correct binary controls. Rustfmt, clippy (including server features), doc tests and public-tree checks passed. Overall exit1 is the same three pre-existing macOS optional-release-archive Python subtests (masks0/1 musl[@], mask2 arm64[@] unbound Bash arrays);708 ran, one skipped. The436 broad affected harnesses were listed for CI by --quick; the focused actual Game pair was run locally.
|
||||
|
||||
Ledger refresh initially found no default jar; that failed tooling attempt is retained. Rebuilding with the actual pinned server-game.jar succeeds, but the indexer unconditionally excludes valueOf methods (tools/port_ledger.py:612). Its four stale-citation warnings are therefore false positives: both factories and positive-zero initializers are directly confirmed in that jar’s javap output. No ledger-engine fix is included. An unrelated rest-010 map-check read_loop row is also stale; the attempted note was reverted, leaving mapping files unchanged.
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Floating zero factories through actual typed storage commands."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import test_command_exec_parity as driver
|
||||
|
||||
CASES = [('f_plain_pos', '0.0f', '0.0f', False), ('f_plain_neg', '-0.0f', '0.0f', True), ('f_exp_pos', '0e0f', '0.0f', False), ('f_exp_neg', '-0e0f', '0.0f', True), ('d_plain_pos', '0.0d', '0.0d', False), ('d_plain_neg', '-0.0d', '0.0d', True), ('d_exp_pos', '0e0d', '0.0d', False), ('d_exp_neg', '-0e0d', '0.0d', True), ('bare_plain_pos', '0.0', '0.0d', False), ('bare_plain_neg', '-0.0', '0.0d', True), ('bare_exp_pos', '0e0', '0.0d', False), ('bare_exp_neg', '-0e0', '0.0d', True), ('f_value_pos', '1.25f', '1.25f', False), ('f_value_neg', '-1.25f', '-1.25f', False), ('d_value_pos', '1.25d', '1.25d', False), ('d_value_neg', '-1.25d', '-1.25d', False), ('f_sub_pos', '1.0e-40f', '1.0e-40f', False), ('f_sub_neg', '-1.0e-40f', '-1.0e-40f', False), ('d_sub_pos', '1.0e-310d', '1.0e-310d', False), ('d_sub_neg', '-1.0e-310d', '-1.0e-310d', False)]
|
||||
|
||||
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)
|
||||
def stored(command,label,admission=False):
|
||||
cmd('/scoreboard players set result h190 -99',label+'-reset-result',-99)
|
||||
cmd('/scoreboard players set success h190 -99',label+'-reset-success',-99)
|
||||
cmd('/execute store result score result h190 store success score success h190 run '+command,label,admission=admission)
|
||||
cmd('/scoreboard players get result h190',label+'-result',admission=admission)
|
||||
cmd('/scoreboard players get success h190',label+'-success',admission=admission)
|
||||
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 14 200 14','loaded',1)
|
||||
cmd('/execute if block 14 199 14 stone','support',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 h190 dummy','objective')
|
||||
cmd('/execute store result score start h190 run time query gametime','clock-start',diagnostic=True)
|
||||
for storage in ['source','copy']:
|
||||
cmd('/data merge storage h190:'+storage+' {anchor:1b}',storage+'-anchor',1)
|
||||
cmd('/execute if data storage h190:'+storage+' {anchor:1b}',storage+'-exists',1)
|
||||
original='{'+','.join(key+':'+text for key,text,canonical,target in CASES)+'}'
|
||||
canonical='{'+','.join(key+':'+value for key,text,value,target in CASES)+'}'
|
||||
stored('data modify storage h190:source samples set value '+original,'initial-write',True)
|
||||
cmd('/data get storage h190:source','initial-full',diagnostic=True)
|
||||
for key,text,value,target in CASES:
|
||||
cmd('/data get storage h190:source samples.'+key,key+'-source',admission=not target)
|
||||
cmd('/execute if data storage h190:source {samples:{'+key+':'+value+'}}',key+'-source-type',None if target else 1,admission=not target)
|
||||
stored('data modify storage h190:copy samples set from storage h190:source samples','copy-write',True)
|
||||
for key,text,value,target in CASES:
|
||||
cmd('/data get storage h190:copy samples.'+key,key+'-copy',admission=not target)
|
||||
cmd('/execute if data storage h190:copy {samples:{'+key+':'+value+'}}',key+'-copy-type',None if target else 1,admission=not target)
|
||||
stored('data modify storage h190:copy samples set from storage h190:source samples','duplicate-copy',True)
|
||||
stored('data modify storage h190:source samples set value '+canonical,'canonical-no-write')
|
||||
cmd('/data get storage h190:source','source-final-full',diagnostic=True)
|
||||
cmd('/data get storage h190:copy','copy-final-full',diagnostic=True)
|
||||
for storage in ['source','copy']:
|
||||
cmd('/execute if data storage h190:'+storage+' {anchor:1b}',storage+'-retained',1)
|
||||
cmd('/execute store result score end h190 run time query gametime','clock-end',diagnostic=True)
|
||||
cmd('/scoreboard players operation end h190 -= start h190','clock-delta-operation',0)
|
||||
cmd('/scoreboard players get end h190','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='floating-zero-', 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: [('Text floating zero', 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