diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs index a95d17803..c03c63e70 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs @@ -710,6 +710,11 @@ fn validate_node( { return Err("UI 节点 Transform 必须是有限数值".to_string()); } + if !node.offset.min.iter().all(|value| value.is_finite()) + || !node.offset.max.iter().all(|value| value.is_finite()) + { + return Err("UI 节点 offset 必须是有限数值".to_string()); + } if node .layout .transform @@ -1321,6 +1326,62 @@ mod tests { ); } + #[test] + fn rejects_non_finite_node_offset() { + let state: State = serde_json::from_value(serde_json::json!({ + "ui_trees": [{ + "src_ui_design": "page", + "root": { + "id": "root", + "layout": { + "transform": { + "anchor_min": [0.0, 0.0], + "anchor_max": [1.0, 1.0], + "offset_min": [0.0, 0.0], + "offset_max": [0.0, 0.0] + }, + "custom_minimum_size": [0.0, 0.0], + "size_flags_horizontal": 1, + "size_flags_vertical": 1, + "size_flags_stretch_ratio": 1.0, + "container": "None" + }, + "metadata": { + "name": "根节点", + "description": "", + "layout_status": "NoProblem", + "component_status": "NoProblem", + "allow_llm_edit_layout": true, + "allow_llm_edit_component": true, + "source": "System" + }, + "component": null, + "children_display_mode": "Stack", + "children": [], + "offset": { "min": [0.0, 0.0], "max": [1280.0, 720.0] } + } + }], + "ui_design_images": { + "page": { + "path": "missing.png", + "pixel_size": [1280.0, 720.0], + "pixels_per_unit": 1.0 + } + }, + "sprite_assets": {}, + "font_assets": {} + })) + .expect("deserialize state with ordered offset"); + assert_eq!(validate_state(&state), Ok(())); + + let mut invalid = state; + invalid.ui_trees[0].root.offset.min[1] = f32::INFINITY; + assert_eq!( + validate_state(&invalid), + Err("UI 节点 offset 必须是有限数值".to_string()) + ); + } + #[test] fn recovers_last_valid_state_when_primary_is_corrupt() { let (directory, asset_id) = fixture();