{"record":{"id":"c45f4bf290767823","repo":"louis-e/arnis","slug":"component-pushed-above","errorCode":null,"errorMessage":"component pushed above","messagePattern":"component pushed above","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/elevation/postprocess.rs","lineNumber":418,"sourceCode":"                if iqr > max_flowing_iqr {\n                    max_flowing_iqr = iqr;\n                }\n                flowing_cells.push(Vec::new());\n                for &(cx, cy) in &component {\n                    let orig = heights_snapshot[cy][cx];\n                    if !orig.is_finite() {\n                        continue;\n                    }\n                    let local_surface = local_water_median(\n                        &heights_snapshot,\n                        lc_grid,\n                        cx,\n                        cy,\n                        LOCAL_SURFACE_RADIUS,\n                        MIN_LOCAL_SAMPLES,\n                    )\n                    .unwrap_or(fallback_median);\n                    let last = flowing_cells.last_mut().expect(\"component pushed above\");\n                    last.push((cx as u32, cy as u32, local_surface as f32));\n                }\n            } else {\n                // ── Still water (lake / fjord / ocean) ─────────────────\n                // Estimate a single surface for the whole component via\n                // histogram mode (robust to both upper and lower tails),\n                // then clamp by adjacent land p25 so the body can't sit\n                // above its own shore (Arnis Baltic fjord case).\n                still_components += 1;\n                let raw_surface = if values.len() >= MIN_MODE_SAMPLES {\n                    histogram_mode(&values, MODE_BIN_SIZE_M)\n                } else {\n                    fallback_median\n                };\n                let surface =\n                    clamp_by_adjacent_land(raw_surface, &component, &heights_snapshot, lc_grid);\n\n                for &(cx, cy) in &component {","sourceCodeStart":400,"sourceCodeEnd":436,"githubUrl":"https://github.com/louis-e/arnis/blob/34048924d9365795fb0d832e76140a3fbdc413d9/src/elevation/postprocess.rs#L400-L436","documentation":"In `level_water_surfaces` (postprocess.rs:418), the code appends to the flowing-water component list with `last.push(...)` guarded by `.expect(\"component pushed above\")` on `flowing_cells.last_mut()`. The invariant is that the cell being processed was just pushed to `flowing_cells` earlier in the same iteration, so `last_mut()` must succeed; the expect fires only if that invariant was broken (the push was skipped or moved).","triggerScenarios":"A code change that makes the branch reachable without a prior `flowing_cells.push(...)` (e.g. refactoring component allocation, adding an early-continue after allocation, or merging branches); not reachable in an unmodified build.","commonSituations":"Regression after refactoring the water-surface leveling pass; changing the component-allocation logic so some cells are processed with no active component.","solutions":["Inspect the code path between the component push and line 418 and restore the invariant that every processed cell belongs to a pushed component","Replace the expect with explicit handling: if `last_mut()` is None, start a new component and push the cell into it","Add a debug_assert right after the push so regressions surface closer to the cause","Run the guarded tests (`flowing_surface_*` tests call this function) after any refactor of this pass"],"exampleFix":"// before\nlet last = flowing_cells.last_mut().expect(\"component pushed above\");\nlast.push((cx as u32, cy as u32, local_surface as f32));\n// after\nlet cell = (cx as u32, cy as u32, local_surface as f32);\nmatch flowing_cells.last_mut() {\n    Some(c) => c.push(cell),\n    None => flowing_cells.push(vec![cell]),\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Use Option handling instead of expect at the mutation site:\nif let Some(c) = flowing_cells.last_mut() { c.push(cell); } else { flowing_cells.push(vec![cell]); }","preventionTips":["After refactoring the component-allocation flow, run the flowing_surface_* tests","debug_assert!(!flowing_cells.is_empty()) immediately after the push","Keep the push and the last_mut() use adjacent in the same branch"],"tags":["panic","invariant","postprocess","water"],"backgroundTag":"broken-internal-invariant","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"}