{"record":{"id":"208467cb12098844","repo":"louis-e/arnis","slug":"failed-to-create-tile-xzbbox","errorCode":null,"errorMessage":"Failed to create tile XZBBox","messagePattern":"Failed to create tile XZBBox","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/data_processing.rs","lineNumber":903,"sourceCode":"        let mut last_emitted_pct = 20.0_f64;\n        // Placement-side ticks so the bar moves before the first batch merges.\n        let tiles_placed = std::sync::atomic::AtomicUsize::new(0);\n        for batch in indexed_tiles.chunks(tile_batch_size) {\n            // Phase 1: process this batch of tiles in parallel\n            let place_start = std::time::Instant::now();\n            let batch_results: Vec<_> = batch\n                .par_iter()\n                .map(|&(tile_idx, tile_bounds)| {\n                    // max_* are exclusive; rect_from_min_max treats max as inclusive,\n                    // so subtract 1. Clamp to the world bbox so edge-tile halos don't\n                    // extend past world bounds.\n                    let tile_xzbbox = XZBBox::rect_from_min_max(\n                        (tile_bounds.min_x - tile::TILE_EDITOR_HALO).max(xzbbox.min_x()),\n                        (tile_bounds.min_z - tile::TILE_EDITOR_HALO).max(xzbbox.min_z()),\n                        (tile_bounds.max_x - 1 + tile::TILE_EDITOR_HALO).min(xzbbox.max_x()),\n                        (tile_bounds.max_z - 1 + tile::TILE_EDITOR_HALO).min(xzbbox.max_z()),\n                    )\n                    .expect(\"Failed to create tile XZBBox\");\n\n                    let mut tile_editor = WorldEditor::new(PathBuf::new(), &tile_xzbbox, llbbox);\n                    tile_editor.set_ground(Arc::clone(&ground));\n                    tile_editor.set_ground_origin(xzbbox.min_x(), xzbbox.min_z());\n                    // Ground generation runs on tile editors, so they need the real scale.\n                    tile_editor.set_projection_info(&args.projection.to_string(), args.scale);\n                    tile_editor.set_place_schematics(args.use_3d);\n                    tile_editor.set_map_decals(place_branding);\n                    if let Some(ref tp) = tree_pack {\n                        tile_editor.set_tree_pack(Arc::clone(tp));\n                    }\n                    tile_editor.set_sealed_surface(Arc::clone(&sealed_surface));\n                    if let Some(ctx) = &signage_ctx {\n                        tile_editor.set_signage(Arc::clone(ctx));\n                    }\n                    tile_editor.set_strict_bounds(\n                        tile_bounds.min_x,\n                        tile_bounds.min_z,","sourceCodeStart":885,"sourceCodeEnd":921,"githubUrl":"https://github.com/louis-e/arnis/blob/34048924d9365795fb0d832e76140a3fbdc413d9/src/data_processing.rs#L885-L921","documentation":"`generate_world_with_options` builds a per-tile bounding box by clamping the tile bounds (expanded by a halo) to the overall world XZ bbox and then calls `XZBBox::rect_from_min_max(...).expect(\"Failed to create tile XZBBox\")`. `rect_from_min_max` requires min_x <= max_x and min_z <= max_z; the expect fires when the clamped rectangle is empty or inverted.","triggerScenarios":"A tile whose bounds, after subtracting TILE_EDITOR_HALO on min and adding it on max, do not intersect the world `xzbbox` at all — so the `.max(min_x)`/`.min(max_x)` clamping produces min_x > max_x (or min_z > max_z). This happens when tile bounds are miscomputed, the world bbox is empty/degenerate, or tile layout arithmetic is off by more than the halo.","commonSituations":"A world extent smaller than one tile or a zero-area bbox passed on the command line; projection/scale options that shrink the computed bbox; a bug or version change in tile grid computation producing tiles outside the world bbox.","solutions":["Log tile_bounds, the halo and the world xzbbox for the failing tile and confirm the tile actually intersects the world bbox","Validate the world bbox is non-degenerate (min < max on both axes) before generating tiles","Use `XZBBox::rect_from_min_max` directly and skip tiles whose clamped rect is empty instead of panicking","Check the projection/scale arguments; wrong scale can make tile bounds not overlap the world bbox"],"exampleFix":"// before\nlet tile_xzbbox = XZBBox::rect_from_min_max(x0, z0, x1, z1)\n    .expect(\"Failed to create tile XZBBox\");\n// after\nlet Some(tile_xzbbox) = XZBBox::rect_from_min_max(x0, z0, x1, z1) else {\n    eprintln!(\"skipping tile with no overlap: {:?}\", tile_bounds);\n    continue;\n};","handlingStrategy":"validation","validationCode":"let intersects = tile_bounds.min_x - halo <= xzbbox.max_x() && tile_bounds.max_x - 1 + halo >= xzbbox.min_x() && tile_bounds.min_z - halo <= xzbbox.max_z() && tile_bounds.max_z - 1 + halo >= xzbbox.min_z();\nif !intersects { continue; }","typeGuard":"fn valid_rect(min_x: i32, min_z: i32, max_x: i32, max_z: i32) -> bool { min_x <= max_x && min_z <= max_z }","tryCatchPattern":null,"preventionTips":["Check tile/world bbox overlap before constructing the tile rect","Validate world extent args (non-zero, min<max) at startup","Handle rect_from_min_max as Result instead of expect"],"tags":["panic","bbox","world-generation"],"backgroundTag":"invalid-rectangle-bounds","analyzedSha":"34048924d9365795fb0d832e76140a3fbdc413d9","analyzedAt":"2026-09-03T14:05:17.283Z","contentChangedAt":"2026-09-03T14:05:17.283Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}