diff --git a/crates/tilewright-cli/README.md b/crates/tilewright-cli/README.md index 39e9962..1d282bc 100644 --- a/crates/tilewright-cli/README.md +++ b/crates/tilewright-cli/README.md @@ -181,18 +181,22 @@ document exits with code 1. Unrelated snapshot diagnostics remain separate and can accompany a successful summary. The command does not emit raw documents or unprojected system fields. Its map -and coordinate values are stored nonnegative scalars, not validated map -references or positions. It does not interpret party members or `versionId`, -compare titles across files, validate editor compatibility, or establish -mutation, round-trip, and write support. The snapshot resource-limit options -are available on `system`. +IDs are stored nonnegative scalars and its player-start coordinates are signed +scalars; neither is a validated map reference or position. It does not +interpret party members or `versionId`, compare titles across files, validate +editor compatibility, or establish mutation, round-trip, and write support. +The snapshot resource-limit options are available on `system`. The `validate` command loads the same bounded snapshot and delegates the player-start check to the core library. It reports the observed exact unset -triplet, a zero map ID with unevidenced nonzero coordinates, a missing positive -catalog record, and coordinates outside the selected map dimensions. Findings -are completed validation results and exit with code 0. Acquisition, loading, -and structural system, catalog, or selected-map failures exit with code 1. +triplet, an editor-preserved ambiguous zero map ID with nonzero coordinates, a +missing positive catalog record, and signed coordinates outside the selected +map dimensions. Findings are completed validation results and exit with code +0. Acquisition, loading, and structural system, catalog, or selected-map +failures exit with code 1. + +All JSON commands currently emit schema version 2. This executable-wide version +advanced when signed player-start coordinate values were introduced. A finding-free result means only that this bounded player-start check found no issue. It does not establish project validity, MZ-version compatibility, diff --git a/crates/tilewright-cli/src/main.rs b/crates/tilewright-cli/src/main.rs index 774c29d..53887cb 100644 --- a/crates/tilewright-cli/src/main.rs +++ b/crates/tilewright-cli/src/main.rs @@ -38,7 +38,7 @@ use tilewright::rpg_maker_mz::tileset_catalog::{ TilesetCatalog, TilesetCatalogError, TilesetField, tileset_catalog, }; -const OUTPUT_SCHEMA_VERSION: u8 = 1; +const OUTPUT_SCHEMA_VERSION: u8 = 2; fn parse_max_bytes(s: &str) -> Result { let val: usize = s.parse().map_err(|_| "must be a valid positive integer")?; @@ -761,8 +761,8 @@ struct SystemDetail { locale: String, edit_map_id: u32, start_map_id: u32, - start_x: u32, - start_y: u32, + start_x: i64, + start_y: i64, } #[derive(Debug, Serialize)] @@ -817,8 +817,8 @@ struct PlayerStartValidationDetail { scope: ValidationScope, finding_free: bool, start_map_id: u32, - start_x: u32, - start_y: u32, + start_x: i64, + start_y: i64, finding_count: usize, findings: Vec, } @@ -834,16 +834,16 @@ enum ValidationScope { enum PlayerStartFindingReport { MissingPlayerStart, ZeroMapIdWithCoordinates { - start_x: u32, - start_y: u32, + start_x: i64, + start_y: i64, }, MissingMapRecord { map_id: u32, }, OutOfBounds { map_id: u32, - start_x: u32, - start_y: u32, + start_x: i64, + start_y: i64, width: u32, height: u32, }, @@ -3734,7 +3734,7 @@ fn write_human_player_start_finding( ), PlayerStartFindingReport::ZeroMapIdWithCoordinates { start_x, start_y } => writeln!( writer, - " - map ID 0 has unevidenced coordinates ({start_x}, {start_y})" + " - map ID 0 has ambiguous stored coordinates ({start_x}, {start_y})" ), PlayerStartFindingReport::MissingMapRecord { map_id } => { writeln!(writer, " - map {map_id} has no map-catalog record") diff --git a/crates/tilewright-cli/tests/cli.rs b/crates/tilewright-cli/tests/cli.rs index f5b83d5..9daa5e6 100644 --- a/crates/tilewright-cli/tests/cli.rs +++ b/crates/tilewright-cli/tests/cli.rs @@ -130,7 +130,7 @@ fn discover_emits_versioned_json_for_scripts() { assert!(output.status.success()); assert!(stderr(&output).is_empty()); let report: Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["result"], "candidate"); assert_eq!(report["markers"][0]["kind"], "regular_file"); assert_eq!( @@ -228,7 +228,7 @@ fn operational_errors_have_human_and_json_forms() { assert_eq!(json.status.code(), Some(1)); assert!(stderr(&json).is_empty()); let report: Value = serde_json::from_slice(&json.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert!( report["error"]["message"] .as_str() @@ -287,7 +287,7 @@ fn inventory_emits_versioned_json_for_scripts() { assert!(output.status.success()); assert!(stderr(&output).is_empty()); let report: Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); let entries = report["entries"].as_array().unwrap(); assert_eq!(entries.len(), 2); @@ -333,7 +333,7 @@ fn inventory_refuses_non_utf8_paths_in_json() { assert_eq!(output.status.code(), Some(1)); let report: Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert!( report["error"]["message"].as_str().unwrap().contains( "JSON output cannot safely represent non-UTF-8 paths without lossy conversion" @@ -426,7 +426,7 @@ fn inventory_operational_errors_have_human_and_json_forms() { assert_eq!(json.status.code(), Some(1)); assert!(stderr(&json).is_empty()); let report: Value = serde_json::from_slice(&json.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert!( report["error"]["message"] .as_str() @@ -540,7 +540,7 @@ fn snapshot_emits_deterministic_versioned_json_without_source_contents() { assert!(output.status.success()); assert!(stderr(&output).is_empty()); let report: Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["completeness"], "complete"); assert_eq!(report["loaded_document_count"], 3); assert_eq!(report["diagnostic_count"], 0); @@ -716,7 +716,7 @@ fn snapshot_operational_errors_have_human_and_json_forms() { assert_eq!(json.status.code(), Some(1)); assert!(stderr(&json).is_empty()); let report: Value = serde_json::from_slice(&json.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert!( report["error"]["message"] .as_str() @@ -863,7 +863,7 @@ fn maps_emits_deterministic_versioned_json_without_unprojected_contents() { assert!(output.status.success()); assert!(stderr(&output).is_empty()); let report: Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["snapshot_completeness"], "complete"); assert_eq!(report["map_count"], 2); assert_eq!(report["finding_count"], 0); @@ -964,7 +964,7 @@ fn maps_projection_errors_have_human_and_json_forms() { assert_eq!(json.status.code(), Some(1)); assert!(stderr(&json).is_empty()); let report: Value = serde_json::from_slice(&json.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["error"]["category"], "missing_document"); assert_eq!(report["snapshot_completeness"], "complete"); @@ -1030,7 +1030,7 @@ fn maps_operational_errors_preserve_stream_separation() { assert_eq!(json.status.code(), Some(1)); assert!(stderr(&json).is_empty()); let report: Value = serde_json::from_slice(&json.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert!( report["error"]["message"] .as_str() @@ -1100,7 +1100,7 @@ fn tilesets_emits_deterministic_versioned_json_without_opaque_contents() { assert!(stderr(&first).is_empty()); assert_eq!(first.stdout, second.stdout); let report: Value = serde_json::from_slice(&first.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["snapshot_completeness"], "complete"); assert_eq!(report["tileset_count"], 2); assert_eq!(report["tilesets"][0]["id"], 1); @@ -1211,7 +1211,7 @@ fn map_emits_versioned_json_without_unprojected_contents() { assert!(stderr(&first).is_empty()); assert_eq!(first.stdout, second.stdout); let report: Value = serde_json::from_slice(&first.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["snapshot_completeness"], "complete"); assert_eq!(report["map"]["id"], 1); assert_eq!(report["map"]["catalog_name"], "First"); @@ -1427,7 +1427,7 @@ fn events_emits_deterministic_versioned_json_without_opaque_contents() { assert!(stderr(&first).is_empty()); assert_eq!(first.stdout, second.stdout); let report: Value = serde_json::from_slice(&first.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["snapshot_completeness"], "complete"); assert_eq!(report["map"]["id"], 1); assert_eq!(report["map"]["catalog_name"], "Town"); @@ -1528,7 +1528,7 @@ fn system_emits_versioned_json_without_unprojected_contents() { assert!(!output_text.contains("secret")); assert!(!output_text.contains("not emitted")); let report: Value = serde_json::from_str(&output_text).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["snapshot_completeness"], "complete"); assert_eq!(report["snapshot_diagnostic_count"], 0); assert_eq!(report["system"]["game_title"], "Game"); @@ -1545,6 +1545,28 @@ fn system_emits_versioned_json_without_unprojected_contents() { assert_eq!(report["snapshot_diagnostics"], Value::Array(Vec::new())); } +#[test] +fn system_reports_signed_coordinates_in_human_and_json_output() { + let temp = TempDir::new().unwrap(); + let root = write_system_project( + &temp, + br#"{"gameTitle":"Game","currencyUnit":"G","locale":"en_US","editMapId":1,"startMapId":1,"startX":-1,"startY":-2}"#, + ); + + let human = tilewright(&["system", root.to_str().unwrap()]); + assert!(human.status.success()); + assert!(stderr(&human).is_empty()); + assert!(stdout(&human).contains("Player start: map 1 at (-1, -2)")); + + let json = tilewright(&["system", root.to_str().unwrap(), "--format", "json"]); + assert!(json.status.success()); + assert!(stderr(&json).is_empty()); + let report: Value = serde_json::from_slice(&json.stdout).unwrap(); + assert_eq!(report["schema_version"], 2); + assert_eq!(report["system"]["start_x"], -1); + assert_eq!(report["system"]["start_y"], -2); +} + #[test] fn system_keeps_unrelated_snapshot_diagnostics_separate() { let temp = TempDir::new().unwrap(); @@ -1678,6 +1700,57 @@ fn validate_reports_a_clear_player_start_for_people() { assert!(output.contains("No player-start findings.")); } +#[test] +fn validate_reports_negative_coordinates_as_out_of_bounds() { + let temp = TempDir::new().unwrap(); + let root = write_validation_project( + &temp, + br#"{"gameTitle":"Game","currencyUnit":"G","locale":"en_US","editMapId":1,"startMapId":1,"startX":-1,"startY":-2}"#, + Some(br#"[null,{"id":1,"name":"One","order":1,"parentId":0}]"#), + Some( + br#"{"displayName":"One","width":10,"height":8,"tilesetId":1,"events":[]}"#, + ), + ); + + let human = tilewright(&["validate", root.to_str().unwrap()]); + assert!(human.status.success()); + assert!(stderr(&human).is_empty()); + assert!(stdout(&human).contains("(-1, -2) is outside map 1 dimensions 10 x 8")); + + let json = tilewright(&["validate", root.to_str().unwrap(), "--format", "json"]); + assert!(json.status.success()); + assert!(stderr(&json).is_empty()); + let report: Value = serde_json::from_slice(&json.stdout).unwrap(); + assert_eq!(report["schema_version"], 2); + assert_eq!(report["validation"]["start_x"], -1); + assert_eq!(report["validation"]["start_y"], -2); + assert_eq!( + report["validation"]["findings"][0]["category"], + "out_of_bounds" + ); + assert_eq!(report["validation"]["findings"][0]["start_x"], -1); + assert_eq!(report["validation"]["findings"][0]["start_y"], -2); +} + +#[test] +fn validate_describes_zero_map_signed_coordinates_as_ambiguous() { + let temp = TempDir::new().unwrap(); + let root = write_validation_project( + &temp, + br#"{"gameTitle":"Game","currencyUnit":"G","locale":"en_US","editMapId":1,"startMapId":0,"startX":-1,"startY":2}"#, + None, + None, + ); + + let output = tilewright(&["validate", root.to_str().unwrap()]); + + assert!(output.status.success()); + assert!(stderr(&output).is_empty()); + let output = stdout(&output); + assert!(output.contains("map ID 0 has ambiguous stored coordinates (-1, 2)")); + assert!(!output.contains("unevidenced")); +} + #[test] fn validate_keeps_unrelated_snapshot_diagnostics_separate() { let temp = TempDir::new().unwrap(); @@ -1750,7 +1823,7 @@ fn validate_emits_versioned_json_for_each_contextual_finding() { ); assert!(stderr(&output).is_empty()); let report: Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["validation"]["scope"], "player_start"); assert_eq!(report["validation"]["finding_free"], false); assert_eq!(report["validation"]["finding_count"], 1); @@ -1914,7 +1987,7 @@ fn validate_tilesets_emits_deterministic_versioned_json_findings() { assert!(stderr(&first).is_empty()); assert_eq!(first.stdout, second.stdout); let report: Value = serde_json::from_slice(&first.stdout).unwrap(); - assert_eq!(report["schema_version"], 1); + assert_eq!(report["schema_version"], 2); assert_eq!(report["validation"]["scope"], "map_tileset_references"); assert_eq!(report["validation"]["finding_free"], false); assert_eq!(report["validation"]["map_count"], 2); diff --git a/crates/tilewright/README.md b/crates/tilewright/README.md index f332ac2..7f8ffc7 100644 --- a/crates/tilewright/README.md +++ b/crates/tilewright/README.md @@ -311,10 +311,10 @@ fn print_system_summary(snapshot: &ProjectSnapshot) { } ``` -The map ID and coordinate values are nonnegative scalars, not validated map -references. The operation does not interpret other system settings, compare -titles across files, establish editor compatibility, or expose mutation and -serialization. +The map IDs are nonnegative scalars and the player-start coordinates are signed +scalars; neither is a validated map reference or position. The operation does +not interpret other system settings, compare titles across files, establish +editor compatibility, or expose mutation and serialization. ### Example: Player-Start Validation @@ -337,11 +337,11 @@ fn print_player_start_findings(snapshot: &ProjectSnapshot) { } ``` -The operation recognizes the observed exact zero triplet, reports a missing -positive catalog record, and checks coordinates against selected-map -dimensions. A finding-free report is not general project validity or editor -compatibility. The operation does not inspect passability, events, vehicles, or -write behavior. +The operation recognizes the observed exact zero triplet, reports an ambiguous +zero-map/mixed-coordinate state or a missing positive catalog record, and +checks signed coordinates against selected-map dimensions. A finding-free +report is not general project validity or editor compatibility. The operation +does not inspect passability, events, vehicles, or write behavior. ## Responsibilities diff --git a/crates/tilewright/src/rpg_maker_mz/player_start_validation.rs b/crates/tilewright/src/rpg_maker_mz/player_start_validation.rs index 275f67d..117e99d 100644 --- a/crates/tilewright/src/rpg_maker_mz/player_start_validation.rs +++ b/crates/tilewright/src/rpg_maker_mz/player_start_validation.rs @@ -13,8 +13,8 @@ use std::fmt; #[non_exhaustive] pub struct PlayerStartValidation { start_map_id: u32, - start_x: u32, - start_y: u32, + start_x: i64, + start_y: i64, findings: Vec, } @@ -24,13 +24,13 @@ impl PlayerStartValidation { self.start_map_id } - /// Returns the stored nonnegative starting X-coordinate scalar. - pub fn start_x(&self) -> u32 { + /// Returns the stored signed starting X-coordinate scalar. + pub fn start_x(&self) -> i64 { self.start_x } - /// Returns the stored nonnegative starting Y-coordinate scalar. - pub fn start_y(&self) -> u32 { + /// Returns the stored signed starting Y-coordinate scalar. + pub fn start_y(&self) -> i64 { self.start_y } @@ -61,10 +61,10 @@ pub enum PlayerStartFinding { MissingPlayerStart, /// The map ID is zero while one or both stored coordinates are nonzero. /// - /// This state is reported without interpreting it as set or unset because - /// it has not been observed in the editor. + /// MZ 1.10.0 was observed to preserve this ambiguous state. It is reported + /// without interpreting it as set, unset, valid, or runnable. #[non_exhaustive] - ZeroMapIdWithCoordinates { start_x: u32, start_y: u32 }, + ZeroMapIdWithCoordinates { start_x: i64, start_y: i64 }, /// The positive starting-map ID has no record in the coherent map catalog. #[non_exhaustive] MissingMapRecord { map_id: MapId }, @@ -72,8 +72,8 @@ pub enum PlayerStartFinding { #[non_exhaustive] OutOfBounds { map_id: MapId, - start_x: u32, - start_y: u32, + start_x: i64, + start_y: i64, width: u32, height: u32, }, @@ -89,7 +89,7 @@ impl fmt::Display for PlayerStartFinding { start_x, start_y, .. } => write!( formatter, - "player start has map ID 0 with unevidenced coordinates ({start_x}, {start_y})" + "player start has map ID 0 with ambiguous stored coordinates ({start_x}, {start_y})" ), Self::MissingMapRecord { map_id, .. } => write!( formatter, @@ -156,13 +156,14 @@ impl std::error::Error for PlayerStartValidationError { /// This pure operation composes [`system_summary`], [`map_catalog`], and /// [`map_summary`] over `snapshot`. It recognizes the directly observed /// `startMapId = 0`, `startX = 0`, `startY = 0` unset triplet, reports an -/// absent positive map-catalog reference, and checks zero-based coordinates -/// against the selected map's positive width and height. +/// absent positive map-catalog reference, and checks signed zero-based +/// coordinates against the selected map's positive width and height. /// /// It does not perform filesystem I/O, mutate the snapshot, establish that MZ /// accepts every finding-free state, validate passability or event placement, /// inspect vehicle starts, assign severities, or persist changes. A zero map ID -/// with nonzero coordinates is reported as unevidenced rather than interpreted. +/// with nonzero coordinates is reported as an editor-preserved ambiguous state +/// rather than interpreted. /// /// # Errors /// @@ -208,7 +209,11 @@ pub fn validate_player_start( let map = map_summary(snapshot, map_id) .map_err(|source| PlayerStartValidationError::Map { source })?; - if start_x >= map.width() || start_y >= map.height() { + if start_x < 0 + || start_y < 0 + || start_x >= i64::from(map.width()) + || start_y >= i64::from(map.height()) + { findings.push(PlayerStartFinding::OutOfBounds { map_id, start_x, @@ -263,7 +268,7 @@ mod tests { (temp, snapshot) } - fn system(start_map_id: u32, start_x: u32, start_y: u32) -> Vec { + fn system(start_map_id: u32, start_x: i64, start_y: i64) -> Vec { format!( r#"{{"gameTitle":"Game","currencyUnit":"G","locale":"en_US","editMapId":1,"startMapId":{start_map_id},"startX":{start_x},"startY":{start_y},"unknown":true}}"# ) @@ -306,19 +311,18 @@ mod tests { } #[test] - fn reports_zero_map_with_nonzero_coordinates_without_interpreting_it() { - let system = system(0, 2, 3); - let (_temp, snapshot) = snapshot(&system, None, None); + fn reports_signed_zero_map_coordinates_without_interpreting_them() { + for (start_x, start_y) in [(2, 3), (-1, -2)] { + let system = system(0, start_x, start_y); + let (_temp, snapshot) = snapshot(&system, None, None); - let report = validate_player_start(&snapshot).unwrap(); + let report = validate_player_start(&snapshot).unwrap(); - assert_eq!( - report.findings(), - &[PlayerStartFinding::ZeroMapIdWithCoordinates { - start_x: 2, - start_y: 3, - }] - ); + assert_eq!( + report.findings(), + &[PlayerStartFinding::ZeroMapIdWithCoordinates { start_x, start_y }] + ); + } } #[test] @@ -338,7 +342,7 @@ mod tests { #[test] fn reports_each_coordinate_boundary_as_out_of_bounds() { - for (start_x, start_y) in [(10, 0), (0, 8), (u32::MAX, u32::MAX)] { + for (start_x, start_y) in [(-1, 0), (0, -1), (10, 0), (0, 8), (i64::MAX, i64::MAX)] { let system = system(1, start_x, start_y); let (_temp, snapshot) = snapshot(&system, Some(MAP_INFOS), Some(MAP)); @@ -354,6 +358,10 @@ mod tests { height: 8, }] ); + assert_eq!( + snapshot.documents()[Path::new("data/System.json")].source_bytes(), + system + ); } } diff --git a/crates/tilewright/src/rpg_maker_mz/system_summary.rs b/crates/tilewright/src/rpg_maker_mz/system_summary.rs index 5a0a0f1..1a363d9 100644 --- a/crates/tilewright/src/rpg_maker_mz/system_summary.rs +++ b/crates/tilewright/src/rpg_maker_mz/system_summary.rs @@ -20,8 +20,8 @@ pub struct SystemSummary { locale: String, edit_map_id: u32, start_map_id: u32, - start_x: u32, - start_y: u32, + start_x: i64, + start_y: i64, } impl SystemSummary { @@ -61,13 +61,13 @@ impl SystemSummary { self.start_map_id } - /// Returns the stored nonnegative player-start X-coordinate scalar. - pub fn start_x(&self) -> u32 { + /// Returns the stored signed player-start X-coordinate scalar. + pub fn start_x(&self) -> i64 { self.start_x } - /// Returns the stored nonnegative player-start Y-coordinate scalar. - pub fn start_y(&self) -> u32 { + /// Returns the stored signed player-start Y-coordinate scalar. + pub fn start_y(&self) -> i64 { self.start_y } } @@ -147,7 +147,7 @@ pub enum SystemSummaryError { field: SystemSummaryField, actual: JsonValueKind, }, - /// A required number is not a supported nonnegative unsigned integer. + /// A required number is outside its field-specific integer contract. #[non_exhaustive] UnsupportedInteger { path: PathBuf, @@ -200,8 +200,9 @@ impl fmt::Display for SystemSummaryError { ), Self::UnsupportedInteger { path, field, .. } => write!( formatter, - "system document {} field {field} is not a supported nonnegative unsigned integer", - path.display() + "system document {} field {field} is not {}", + path.display(), + expected_integer_description(*field) ), Self::InvalidString { path, field, .. } => write!( formatter, @@ -217,9 +218,10 @@ impl std::error::Error for SystemSummaryError {} /// Projects selected `data/System.json` fields into a bounded read-only summary. /// /// This function reads only lossless documents and diagnostics already present -/// in `snapshot`. Strings are decoded without normalization, while numeric map -/// and coordinate values remain nonnegative `u32` scalars. Unknown fields and -/// exact source bytes remain untouched in the raw snapshot. +/// in `snapshot`. Strings are decoded without normalization. Map IDs remain +/// nonnegative `u32` scalars, while player-start coordinates are signed `i64` +/// scalars. Unknown fields and exact source bytes remain untouched in the raw +/// snapshot. /// /// The result does not require a map catalog, validate map references or /// coordinate bounds, interpret party members or `versionId`, compare titles @@ -259,10 +261,10 @@ pub fn system_summary(snapshot: &ProjectSnapshot) -> Result Result { + required_number_text(object, path, field)? + .parse::() + .map_err(|_| unsupported_integer(path, field)) +} + +fn required_coordinate( + object: &CstObject, + path: &Path, + field: SystemSummaryField, +) -> Result { + required_number_text(object, path, field)? + .parse::() + .map_err(|_| unsupported_integer(path, field)) +} + +fn required_number_text( + object: &CstObject, + path: &Path, + field: SystemSummaryField, +) -> Result { let node = required_node(object, path, field)?; let number = node .as_number_lit() @@ -320,13 +342,26 @@ fn required_integer( field, actual: json_kind(&node), })?; - number - .to_string() - .parse::() - .map_err(|_| SystemSummaryError::UnsupportedInteger { - path: path.to_owned(), - field, - }) + Ok(number.to_string()) +} + +fn unsupported_integer(path: &Path, field: SystemSummaryField) -> SystemSummaryError { + SystemSummaryError::UnsupportedInteger { + path: path.to_owned(), + field, + } +} + +fn expected_integer_description(field: SystemSummaryField) -> &'static str { + match field { + SystemSummaryField::EditMapId | SystemSummaryField::StartMapId => { + "a supported nonnegative unsigned integer" + } + SystemSummaryField::StartX | SystemSummaryField::StartY => "a supported signed integer", + SystemSummaryField::GameTitle + | SystemSummaryField::CurrencyUnit + | SystemSummaryField::Locale => "an integer field", + } } fn required_string( @@ -450,15 +485,15 @@ mod tests { } #[test] - fn accepts_empty_escaped_unicode_and_unsigned_boundaries() { + fn accepts_strings_unsigned_map_ids_and_signed_coordinate_boundaries() { let source = complete_source(&[ ("gameTitle", "\"\""), ("currencyUnit", r#""line\nunit""#), ("locale", r#""日本語""#), ("editMapId", "0"), ("startMapId", &u32::MAX.to_string()), - ("startX", "0"), - ("startY", &u32::MAX.to_string()), + ("startX", &i64::MIN.to_string()), + ("startY", &i64::MAX.to_string()), ]); let (_temp, snapshot) = load_test_snapshot(Some(&source)); @@ -469,8 +504,8 @@ mod tests { assert_eq!(summary.locale(), "日本語"); assert_eq!(summary.edit_map_id(), 0); assert_eq!(summary.start_map_id(), u32::MAX); - assert_eq!(summary.start_x(), 0); - assert_eq!(summary.start_y(), u32::MAX); + assert_eq!(summary.start_x(), i64::MIN); + assert_eq!(summary.start_y(), i64::MAX); } #[test] @@ -568,7 +603,8 @@ mod tests { ("editMapId", "-1", SystemSummaryField::EditMapId), ("startMapId", "1.0", SystemSummaryField::StartMapId), ("startX", "1e0", SystemSummaryField::StartX), - ("startY", "4294967296", SystemSummaryField::StartY), + ("startX", "-9223372036854775809", SystemSummaryField::StartX), + ("startY", "9223372036854775808", SystemSummaryField::StartY), ]; for (name, value, expected_field) in cases { diff --git a/docs/capability-roadmap.md b/docs/capability-roadmap.md index bd933b6..cc679e3 100644 --- a/docs/capability-roadmap.md +++ b/docs/capability-roadmap.md @@ -181,14 +181,12 @@ Supported. The bounded `System.json` orientation summary is accepted in [ADR 0009](decisions/0009-experimental-system-summary.md) and implemented -experimentally. It projects only the seven accepted string and nonnegative -integer fields, retains raw bytes and unknown settings in the snapshot, and -does not validate map references or coordinate bounds. A controlled MZ 1.10.0 -save preserved negative player-start coordinates, exposing a known gap in that -unsigned contract. Proposed -[ADR 0014](decisions/0014-signed-player-start-coordinates.md) defines the -correction. Neither acceptance nor implementation makes the capability -Supported. +experimentally. It projects only the seven accepted string and integer fields, +using nonnegative map-ID scalars and signed player-start coordinates. It retains +raw bytes and unknown settings in the snapshot and does not validate map +references or coordinate bounds. The evidence-driven correction is accepted in +[ADR 0014](decisions/0014-signed-player-start-coordinates.md). Neither +acceptance nor implementation makes the capability Supported. A bounded tileset identity/name catalog is implemented experimentally under accepted [ADR 0012](decisions/0012-experimental-tileset-catalog.md). It leaves @@ -223,8 +221,8 @@ decision. in [ADR 0010](decisions/0010-experimental-player-start-validation.md) composes the system summary, coherent map catalog, and selected-map dimensions. It reports only the evidenced zero triplet, a distinct zero-map/mixed-coordinate -state, a missing positive catalog record, and coordinates outside the map -rectangle. +state, a missing positive catalog record, and signed coordinates outside the +map rectangle. Structural projection failures remain errors. A finding-free result is not a general project-validity or compatibility claim, and the general severity and diagnostic model remains open. @@ -340,7 +338,7 @@ system summary. Controlled MZ 1.10.0 experiments also establish same-map start relocation, Delete-generated zero-triplet serialization, and preservation of mixed-zero, exact-boundary, dangling-map, negative-coordinate, and upper out-of-bounds states. The negative case returns the system and player-start -contracts to the contract stage under proposed ADR 0014. Independent +contracts to the contract stage under accepted ADR 0014. Independent differential verification also matched the bounded tileset and event projections and the map-to-tileset validation relationship against the four-project corpus. A controlled tileset rename changed only the name field; diff --git a/docs/compatibility.md b/docs/compatibility.md index eb4dec7..c189b38 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -31,7 +31,7 @@ not use “supported” to mean only that one file happened to parse. | Capability | Status | Notes | | --- | --- | --- | | Core `tilewright` package | Experimental | Exposes its package version, candidate discovery, capability-relative read-only project inventory, immutable strict lossless JSON syntax representation, read-only raw project snapshot loader, typed map and tileset catalog projections, selected-map summary and event catalog, system summary, and bounded player-start and map-to-tileset validation. | -| `tilewright` CLI executable | Experimental | Provides help/version output and human- or versioned JSON-formatted access to the core library's experimental explicit-root candidate discovery, capability-relative project inventory, bounded raw snapshot loader, typed map and tileset catalogs, selected-map summary and event catalog, system summary, player-start and map-to-tileset validation, and strict lossless JSON syntax inspection. Initial `open_ambient_dir` acquisition for inventory, snapshot loading, map, tileset, event, and system inspection, and validation may resolve root or ancestor symlinks and does not prove root identity. The `inspect-json` command explicitly opens the provided path and makes no project-containment claim. Raw snapshot output omits document contents; typed output includes only the requested projections, contextual information, and separate snapshot diagnostics. The CLI does not establish general semantic understanding, general project validity, MZ-version compatibility, round-trip support, or writes. | +| `tilewright` CLI executable | Experimental | Provides help/version output and human- or versioned JSON-formatted access to the core library's experimental explicit-root candidate discovery, capability-relative project inventory, bounded raw snapshot loader, typed map and tileset catalogs, selected-map summary and event catalog, system summary, player-start and map-to-tileset validation, and strict lossless JSON syntax inspection. All JSON commands currently emit executable-wide schema version 2. Initial `open_ambient_dir` acquisition for inventory, snapshot loading, map, tileset, event, and system inspection, and validation may resolve root or ancestor symlinks and does not prove root identity. The `inspect-json` command explicitly opens the provided path and makes no project-containment claim. Raw snapshot output omits document contents; typed output includes only the requested projections, contextual information, and separate snapshot diagnostics. The CLI does not establish general semantic understanding, general project validity, MZ-version compatibility, round-trip support, or writes. | | `tilewright-mcp` server | Scaffold | Not yet an MCP server; it prints its package version. | | RPG Maker MZ explicit-root candidate recognition | Experimental | Path-level recognition is based on the documented marker role and recorded MZ 1.10.0 observations from the tested macOS environment. CI on Ubuntu, macOS, and Windows is implementation regression coverage, not editor-compatibility evidence. Newer MZ versions and unobserved editor/filesystem combinations remain unknown. Discovery does not validate project contents, infer a version, parse JSON, or guarantee compatibility. It uses `std::fs` and does not provide race-free sandbox containment or complete root symlink rejection. | | RPG Maker MZ capability-relative project inventory | Experimental | Recursively reports deterministically ordered, exact project-relative native paths, entry kinds, and conservative known/extension-candidate/unknown pathname classifications beneath a caller-authorized `cap_std::fs::Dir`. Symlinks are reported without traversal, and file contents are not read. Known paths are limited to evidenced immediate-root names and immediate standard `data` filename families; other immediate `data/*.json` names are extension candidates, not proven plugin content. The API does not acquire or verify the initial root capability, validate entry kinds or contents, infer compatibility, provide an atomic snapshot during concurrent mutation, or return partial results after an I/O error. | @@ -42,8 +42,8 @@ not use “supported” to mean only that one file happened to parse. | RPG Maker MZ typed tileset catalog | Experimental | Projects exact `data/Tilesets.json` into positive catalog-scoped IDs and decoded editor-facing names in ID order while retaining exact bytes and every unprojected field. It accepts null holes and refuses ambiguous required structure or ID/index mismatches. Aggregate evidence covers 24 records and 196 resolving map references across four MZ 1.10.0 projects, and an independent differential audit matched all 72 bounded comparisons. Modes, image slots, flags, notes, map-reference validation, assets, tile behavior, lifecycle operations, malformed-input editor behavior, mutation, persistence, and later versions remain unknown or unimplemented, so the capability remains Experimental. | | RPG Maker MZ selected-map summary | Experimental | Requires a coherent typed map catalog and matching catalog-scoped ID, then projects one evidenced three-digit map document into its exact path, catalog and display names, positive dimensions, positive tileset ID scalar, and count of opaque event objects. Unknown fields and exact bytes remain in the raw snapshot. The operation refuses ambiguous required structure and does not interpret tile data or event bodies, validate tileset references or editor compatibility, support IDs above 999, mutate, serialize, or persist data. Direct shape evidence covers 196 map documents from four MZ 1.10.0 projects, and an independent differential audit matched every bounded summary field across the corpus. Later versions and malformed-input editor behavior remain unknown, so the capability remains Experimental. | | RPG Maker MZ selected-map event catalog | Experimental | Requires a coherent map catalog, matching catalog-scoped ID, and evidenced three-digit map document, then projects map-scoped positive event IDs, decoded names, nonnegative coordinates, and opaque page counts in ID order. Coordinates outside the map dimensions are contextual findings, not editor-rejection claims. Exact bytes, notes, page bodies, commands, and unknown fields remain in the raw snapshot. Aggregate evidence covers 1,555 events and 1,576 pages across 196 MZ 1.10.0 map documents, and an independent differential audit matched all 10,323 bounded comparisons. Page semantics, commands, mutation, persistence, malformed-input editor behavior, and later versions remain unknown or unimplemented, so the capability remains Experimental. | -| RPG Maker MZ system summary | Experimental | Projects exact `data/System.json` from an existing snapshot into its exact path, decoded game title, currency unit, and locale, plus nonnegative editor-map and player-start map/X/Y scalars. Unknown settings and exact bytes remain in the raw snapshot. The current unsigned-coordinate contract rejects an MZ 1.10.0 saved negative-coordinate state; proposed ADR 0014 defines a correction but is not implemented. Aggregate shape evidence covers four projects, an independent differential audit matched all 28 field comparisons and four output envelopes, and controlled saves cover deletion, mixed zero, exact bounds, dangling map IDs, negative coordinates, and upper out-of-bounds coordinates. The operation remains Experimental and does not establish general editor validity, runtime behavior, mutation, persistence, or later-version compatibility. | -| RPG Maker MZ player-start validation | Experimental | Composes the system summary, coherent map catalog, and selected-map dimensions over one existing snapshot. It reports the exact zero triplet as an unset player start, a zero map ID with nonzero coordinates as a distinct ambiguous state, a missing positive catalog record, and coordinates outside the map rectangle. Structural projection failures remain operation errors. MZ 1.10.0 generated the zero triplet through Delete and saved prepared mixed-zero, exact-boundary, dangling-map, negative-coordinate, and upper-out-of-bounds states. The current unsigned system projection prevents validation of the observed negative case; proposed ADR 0014 defines a correction. Findings do not imply general project validity, editor rejection, passability, runtime success, mutation safety, write support, or later-version behavior. | +| RPG Maker MZ system summary | Experimental | Projects exact `data/System.json` from an existing snapshot into its exact path, decoded game title, currency unit, and locale, plus nonnegative editor/start map-ID scalars and signed player-start X/Y scalars. Unknown settings and exact bytes remain in the raw snapshot. Aggregate shape evidence covers four projects, an independent differential audit matched all 28 field comparisons and four output envelopes, and controlled saves cover deletion, mixed zero, exact bounds, dangling map IDs, negative coordinates, and upper out-of-bounds coordinates. The operation remains Experimental and does not establish general editor validity, runtime behavior, mutation, persistence, or later-version compatibility. | +| RPG Maker MZ player-start validation | Experimental | Composes the system summary, coherent map catalog, and selected-map dimensions over one existing snapshot. It reports the exact zero triplet as an unset player start, a zero map ID with nonzero signed coordinates as a distinct ambiguous state, a missing positive catalog record, and negative or upper-bound coordinates outside the map rectangle. Structural projection failures remain operation errors. MZ 1.10.0 generated the zero triplet through Delete and saved prepared mixed-zero, exact-boundary, dangling-map, negative-coordinate, and upper-out-of-bounds states. Findings do not imply general project validity, editor rejection, passability, runtime success, mutation safety, write support, or later-version behavior. | | RPG Maker MZ map-to-tileset reference validation | Experimental | Composes coherent map and tileset catalogs with every selected-map summary and deterministically reports positive map tileset IDs that have no catalog record. Structural prerequisite failures remain operation errors. A finding-free report does not establish project validity, editor acceptance, asset existence, tile behavior, runtime success, compatibility, mutation safety, or write support. Aggregate evidence covers 196 resolving references across four MZ 1.10.0 projects, and an independent differential audit matched all 220 bounded comparisons. Malformed-reference editor behavior and later versions remain unknown. | | General project validation | Not implemented | Only bounded experimental player-start and map-to-tileset relationships are implemented; no general validity, severity, compatibility, repair, or write-time validation contract exists. | | Lossless project round trips | Not implemented | The immutable syntax representation has an exact accepted-input no-op contract, but no project round-trip, typed edit, or supported mutation exists. | diff --git a/docs/decisions/0014-signed-player-start-coordinates.md b/docs/decisions/0014-signed-player-start-coordinates.md index 2271f64..0a72486 100644 --- a/docs/decisions/0014-signed-player-start-coordinates.md +++ b/docs/decisions/0014-signed-player-start-coordinates.md @@ -1,6 +1,6 @@ # ADR 0014: Signed Player-Start Coordinates -- **Status:** Proposed +- **Status:** Accepted - **Date:** 2026-08-09 - **Supersedes in part:** [ADR 0009](0009-experimental-system-summary.md) and @@ -27,8 +27,8 @@ evidence and retain their existing zero and positive-reference behavior. ## Decision -If accepted, the experimental system-summary and player-start contracts will -change as follows: +The experimental system-summary and player-start contracts will change as +follows: 1. `editMapId` and `startMapId` remain stored `u32` scalars. Their zero, dangling, and catalog-reference semantics remain separate validation diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 61b02cb..b04c6b1 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -5,9 +5,7 @@ implementation context has changed. ## Proposed decisions -| ADR | Status | Summary | -| --- | --- | --- | -| [0014: Signed player-start coordinates](0014-signed-player-start-coordinates.md) | Proposed | Correct the experimental system and player-start projections to retain observed signed coordinates and report their map relationship contextually. | +No decisions are currently proposed. ## Accepted decisions @@ -26,6 +24,7 @@ implementation context has changed. | [0011: Experimental selected-map event catalog](0011-experimental-map-event-catalog.md) | Accepted | Add a read-only owned projection of map-scoped event IDs, names, positions, and opaque page counts for one catalog-selected map. | | [0012: Experimental tileset catalog](0012-experimental-tileset-catalog.md) | Accepted | Add a read-only owned projection of catalog-scoped tileset IDs and editor-facing names while leaving modes, images, flags, and notes opaque. | | [0013: Experimental map-tileset reference validation](0013-experimental-map-tileset-validation.md) | Accepted | Compose map summaries and the tileset catalog into deterministic missing-reference findings without implying general validity. | +| [0014: Signed player-start coordinates](0014-signed-player-start-coordinates.md) | Accepted | Correct the experimental system and player-start projections to retain observed signed coordinates and report their map relationship contextually. | ## Adding or changing a decision diff --git a/docs/formats/rpg-maker-mz/player-start-validation.md b/docs/formats/rpg-maker-mz/player-start-validation.md index 50f59c2..11a6022 100644 --- a/docs/formats/rpg-maker-mz/player-start-validation.md +++ b/docs/formats/rpg-maker-mz/player-start-validation.md @@ -30,6 +30,7 @@ behavior remain outside this contract. | A signed coordinate below zero or a nonnegative coordinate at or beyond a selected map's positive width or height is outside that map's zero-origin rectangular index range. | Relocation observation, typed map dimensions, and `MZ-1.10.0-PLAYER-START-TOLERANCE-MATRIX-2026-08-09` | Inferred relationship over observed stored values | High as arithmetic contextual validation | Editor validity, passability, and runtime behavior remain unknown. | | MZ 1.10.0 saved prepared mixed-zero, exact-boundary, dangling-map, negative-coordinate, and upper-out-of-bounds start states without normalizing the triplets. | `MZ-1.10.0-PLAYER-START-TOLERANCE-MATRIX-2026-08-09` | Observed persistence tolerance | High for the five exact states and version | Persistence does not establish whether the states are semantically set, unset, valid, or runnable. | | Tilewright's merged player-start validator matches an independent reconstruction of the bounded relationship across the four-project MZ 1.10.0 corpus. | `MZ-1.10.0-PLAYER-START-DIFFERENTIAL-2026-08-09` | Observed implementation behavior | High for the exact implementation and corpus | All observed source states were finding-free; synthetic tests cover negative categories, while editor behavior for those states remains unknown. | +| Tilewright's signed-coordinate implementation reproduces all six retained controlled triplets and expected finding-category arrays with schema version 2. | `MZ-1.10.0-SIGNED-PLAYER-START-DIFFERENTIAL-2026-08-09` | Observed implementation behavior | High for the exact implementation and controlled cases | This does not establish semantic validity, runtime behavior, or later-version compatibility. | ## Controlled editor observations @@ -131,24 +132,44 @@ behavior remain outside this contract. coordinate, dimension, raw document, excerpt, digest, or CLI report is retained. +### `MZ-1.10.0-SIGNED-PLAYER-START-DIFFERENTIAL-2026-08-09` + +- **Kind:** Read-only differential implementation audit. +- **Version/environment:** Signed-coordinate implementation based on Tilewright + `13bec1b`; the six retained controlled MZ 1.10.0 cases; `jq` 1.8.2 on the + recorded arm64 macOS environment. +- **Procedure:** Independently projected each stored player-start triplet with + `jq`, invoked `tilewright system --format json` and + `tilewright validate --format json`, and compared the three scalars, global + schema version, and ordered finding-category array. +- **Observed:** All six triplets matched exactly. Every report used schema + version 2. Delete produced `missing_player_start`; mixed zero produced + `zero_map_id_with_coordinates`; the exact upper in-bounds case was + finding-free; the dangling map produced `missing_map_record`; and negative + plus exact upper out-of-bounds cases produced `out_of_bounds`. +- **Limits:** This verifies implementation behavior, not editor validity, + runtime success, broader numeric limits, or later-version behavior. +- **Redistribution:** Only per-case pass/fail results and aggregate `6/6` were + retained. No project path, raw document, excerpt, digest, or CLI report is + retained in tracked material. + ## Bounded validation contract The experimental operation accepts an existing `ProjectSnapshot` and composes the existing system-summary, map-catalog, and selected-map projections. It: - reports the exact zero triplet as a missing player start; -- reports a zero map ID with nonzero coordinates as unevidenced, without - deciding whether it is set or unset; +- reports a zero map ID with nonzero signed coordinates as an editor-preserved + ambiguous state without deciding whether it is set or unset; - reports a positive map ID missing from a coherent catalog; -- checks a catalog-selected map using `x < width` and `y < height`; and +- checks a catalog-selected map using `0 <= x < width` and + `0 <= y < height`; and - preserves all raw documents and unknown fields without filesystem I/O. -The current implementation cannot reach those relationship checks when a -coordinate is negative because the system summary still projects coordinates -as `u32`. Proposed -[ADR 0014](../../decisions/0014-signed-player-start-coordinates.md) corrects -that evidence conflict by using signed stored coordinates and applying the same -rectangular out-of-bounds relationship to negative values. +The signed-coordinate correction accepted in +[ADR 0014](../../decisions/0014-signed-player-start-coordinates.md) allows the +same rectangular out-of-bounds relationship to report observed negative stored +values. An unavailable structural projection is an operation error, not a contextual finding. Findings have no public severity and do not generally claim editor @@ -162,6 +183,7 @@ Tests generate minimal strict JSON in temporary project trees. No vendor project is needed. The matrix covers the exact unset triplet, a zero map ID with nonzero coordinates, a missing positive catalog record, each rectangular boundary, positive in-bounds coordinates, structural projection errors, +negative and upper out-of-bounds coordinates, signed mixed-zero states, raw-byte preservation, deterministic findings, and separate snapshot diagnostics. diff --git a/docs/formats/rpg-maker-mz/research-ledger.md b/docs/formats/rpg-maker-mz/research-ledger.md index 1bdd30f..295a8b7 100644 --- a/docs/formats/rpg-maker-mz/research-ledger.md +++ b/docs/formats/rpg-maker-mz/research-ledger.md @@ -1984,18 +1984,35 @@ and `MZ-1.10.0-PLAYER-START-TOLERANCE-MATRIX-2026-08-09`. - **Redistribution:** Only controlled triplets, map dimensions, changed property names, and equality results are retained. +### Evidence record: `MZ-1.10.0-SIGNED-PLAYER-START-DIFFERENTIAL-2026-08-09` + +- **Kind:** Read-only differential implementation audit. +- **Version/environment:** Signed-coordinate implementation based on Tilewright + `13bec1b`; the six retained controlled MZ 1.10.0 cases; `jq` 1.8.2 on the + recorded arm64 macOS environment. +- **Procedure:** Independently projected each stored player-start triplet with + `jq`, invoked the versioned JSON `system` and `validate` commands, and + compared stored scalars, schema version, and ordered finding categories. +- **Observed:** All six cases matched. Every report used schema version 2, and + the expected finding arrays covered unset, mixed-zero, finding-free boundary, + dangling-map, negative, and upper out-of-bounds states. +- **Limits:** This verifies implementation behavior, not editor validity, + runtime success, broader numeric limits, or later-version behavior. +- **Redistribution:** Only per-case pass/fail results and aggregate `6/6` were + retained. No project path, raw document, excerpt, digest, or CLI report is + retained in tracked material. + ### Implementation implications -The implemented experimental projection decodes only the three selected strings -and four nonnegative integer scalars while retaining every other property in the -raw lossless document. It refuses ambiguous required structure, avoids -normalizing strings or treating map scalars as stable identifiers, and makes no -validation, mutation, or editor-compatibility claim. The accepted architecture -is recorded in -[ADR 0009](../../decisions/0009-experimental-system-summary.md). The negative -coordinate observation contradicts the implemented unsigned-coordinate bound; -proposed [ADR 0014](../../decisions/0014-signed-player-start-coordinates.md) -defines the correction. +The implemented experimental projection decodes only the three selected +strings, two nonnegative map-ID scalars, and two signed coordinate scalars while +retaining every other property in the raw lossless document. It refuses +ambiguous required structure, avoids normalizing strings or treating map +scalars as stable identifiers, and makes no validation, mutation, or +editor-compatibility claim. The original architecture is recorded in +[ADR 0009](../../decisions/0009-experimental-system-summary.md); the +evidence-driven signed-coordinate correction is recorded in accepted +[ADR 0014](../../decisions/0014-signed-player-start-coordinates.md). ### Next experiment @@ -2026,7 +2043,8 @@ editor results are recorded by `MZ-1.10.0-PLAYER-START-ZERO-TRIPLET-2026-08-09`, with deletion and edge-state tolerance recorded by `MZ-1.10.0-PLAYER-START-DELETE-2026-08-09` and `MZ-1.10.0-PLAYER-START-TOLERANCE-MATRIX-2026-08-09`. Implementation parity is -recorded by `MZ-1.10.0-PLAYER-START-DIFFERENTIAL-2026-08-09`. +recorded by `MZ-1.10.0-PLAYER-START-DIFFERENTIAL-2026-08-09` and the signed +correction by `MZ-1.10.0-SIGNED-PLAYER-START-DIFFERENTIAL-2026-08-09`. ### Evidence record: `MZ-1.10.0-PLAYER-START-DIFFERENTIAL-2026-08-09` @@ -2053,14 +2071,13 @@ recorded by `MZ-1.10.0-PLAYER-START-DIFFERENTIAL-2026-08-09`. ### Implementation implications The implemented experimental operation composes existing owned projections. It -can report the observed zero triplet, a zero-map/mixed-coordinate state, a -missing positive catalog record, and nonnegative coordinates outside positive -map dimensions. Structural projection failures remain errors. It currently -cannot report the observed negative-coordinate state because the system summary -rejects it first. Findings do not claim general editor rejection, validity, -compatibility, or write safety. Accepted +can report the observed zero triplet, an ambiguous zero-map/mixed-coordinate +state, a missing positive catalog record, and signed coordinates outside +positive map dimensions. Structural projection failures remain errors. +Findings do not claim general editor rejection, validity, compatibility, or +write safety. Accepted [ADR 0010](../../decisions/0010-experimental-player-start-validation.md) defines -the original operation; proposed +the original operation; accepted [ADR 0014](../../decisions/0014-signed-player-start-coordinates.md) defines the evidence-driven correction. diff --git a/docs/formats/rpg-maker-mz/system-summary.md b/docs/formats/rpg-maker-mz/system-summary.md index 21884f5..cda7511 100644 --- a/docs/formats/rpg-maker-mz/system-summary.md +++ b/docs/formats/rpg-maker-mz/system-summary.md @@ -31,6 +31,7 @@ the established scope. | Observed edit and start map IDs are positive and resolve to map-catalog records; observed start coordinates are nonnegative and within the referenced map dimensions. | Shape and cross-file audit | Observed | High across all four projects | This is not evidence that the editor rejects zero, missing, dangling, or out-of-bounds values. | | `versionId` changes during several otherwise unrelated saves. | Existing controlled save and map lifecycle records | Observed | High for those workflows | Its generation rule and stable meaning are unknown, so it is excluded from the summary. | | Tilewright's merged system-summary implementation matches an independent projection of all seven bounded fields across the four-project corpus. | `MZ-1.10.0-SYSTEM-SUMMARY-DIFFERENTIAL-2026-08-07` | Observed implementation behavior | High for the exact implementation and corpus | Later versions, converted projects, malformed inputs, and editor acceptance remain untested. | +| Tilewright's signed-coordinate implementation reproduces all six retained controlled triplets and emits schema version 2. | `MZ-1.10.0-SIGNED-PLAYER-START-DIFFERENTIAL-2026-08-09` | Observed implementation behavior | High for the exact implementation and controlled cases | This does not broaden editor-validity, runtime, or version claims. | ## Aggregate shape audit @@ -74,6 +75,20 @@ establish behavior for malformed or unavailable documents beyond synthetic tests, human-output presentation beyond adapter tests, editor acceptance, converted projects, later MZ versions, or broader system semantics. +## Signed-coordinate differential audit + +On 2026-08-09, the signed-coordinate implementation based on `13bec1b` was run +read-only against the retained Delete case and five-case tolerance matrix. A +separate `jq` projection supplied each stored triplet. Tilewright reproduced all +six triplets exactly, including `-1, -1`, and every `system` and `validate` JSON +report emitted schema version 2. All six expected finding-category arrays also +matched. Only per-case pass/fail results and the aggregate `6/6` result were +retained; no project content or path was retained in tracked material. + +This verifies the correction against the controlled evidence. It does not +establish semantic validity, runtime behavior, broader numeric limits, or +later-version compatibility. + ## Controlled player-start tolerance observations ### `MZ-1.10.0-PLAYER-START-DELETE-2026-08-09` @@ -120,24 +135,17 @@ exact `data/System.json`. It exposes: - the exact project-relative document path; - decoded game-title, currency-unit, and locale strings; -- the nonnegative editor-map ID scalar; and -- the nonnegative player-start map ID, X, and Y scalars. +- the nonnegative editor-map ID scalar; +- the nonnegative player-start map ID scalar; and +- the signed player-start X and Y scalars. The projection refuses an absent or unavailable document, a non-object root, -and missing, duplicate, wrong-kind, undecodable, negative, fractional, or -out-of-`u32` required values. Strings remain unnormalized and may be empty. -Numeric map fields remain `u32` scalars rather than catalog-scoped `MapId` -values because the summary reports stored values without contextual validation. -The separate player-start validation recognizes only the directly observed -zero triplet and does not change this projection contract. - -This implemented unsigned-coordinate contract now has a known evidence gap: -it rejects the directly observed saved `-1, -1` coordinate state before -contextual validation can run. Proposed -[ADR 0014](../../decisions/0014-signed-player-start-coordinates.md) defines a -signed-coordinate correction. Until that proposal is accepted and implemented, -negative stored coordinates remain outside Tilewright's system-summary -capability even though MZ 1.10.0 was observed to preserve one such state. +and missing, duplicate, wrong-kind, undecodable, fractional, exponent-form, or +field-range-exceeding required values. Strings remain unnormalized and may be +empty. Numeric map fields remain `u32` scalars rather than catalog-scoped +`MapId` values, while coordinates use the `i64` representation bound accepted +in [ADR 0014](../../decisions/0014-signed-player-start-coordinates.md). These +types report stored values without contextual validation. Unknown properties and all exact source bytes remain in the untouched raw snapshot. The operation does not parse party members or other system settings, @@ -150,11 +158,12 @@ version, or expose mutation and serialization. Tests can generate minimal strict JSON in temporary project trees. No vendor project is needed. The fixture matrix should cover: -- all seven selected fields with zero and positive numeric boundaries; +- all seven selected fields with unsigned map-ID and signed-coordinate + boundaries; - empty, escaped, Unicode, and ordinary strings; - unknown top-level and nested fields retained in the raw document; -- missing, duplicate decoded, wrong-kind, negative, fractional, and overflow - values for every required field family; +- missing, duplicate decoded, wrong-kind, fractional, exponent-form, and + field-specific overflow values; - missing, unavailable, and non-file `System.json` cases; and - proof that party members, `versionId`, map catalogs, and unrelated settings are not required or interpreted. diff --git a/docs/open-questions.md b/docs/open-questions.md index b89c8ea..1d7149f 100644 --- a/docs/open-questions.md +++ b/docs/open-questions.md @@ -62,7 +62,7 @@ unknown and plugin-defined content. The following details remain open: `TilesetId` scoped to one catalog. ADR 0009 deliberately retains system map fields as unvalidated `u32` scalars. - What numeric representation should stored editor coordinates use as broader - versions and data areas are observed? Proposed + versions and data areas are observed? Accepted [ADR 0014](decisions/0014-signed-player-start-coordinates.md) selects `i64` for the experimental player-start correction without claiming an editor limit or settling a general coordinate type.