Edit

    // --- Stabilize write order for patchability ---
    // Reorder data records by geographic S2 cell so that the same data
    // lands at approximately the same file offset across builds.
    // Combined with --previous-build (stable string offsets), this enables
    // efficient binary delta patches (zstd --patch-from).
    // Node coordinate arrays are NOT reordered — they naturally patch well
    // in PBF order since most coordinates don't change between builds.
    {
        std::cerr << "Stabilizing write order for patchability..." << std::endl;
        auto _st = std::chrono::steady_clock::now();
        auto _sc = CpuTicks::now();

        // Step 1: Reorder addr_points by S2 cell.
        // sorted_addr_cells is [(cell_id, old_addr_id), ...] sorted by cell_id.
        // Each addr maps to exactly one cell, so this is a complete permutation.
        if (!data.sorted_addr_cells.empty()) {
            std::vector<AddrPoint> new_points(data.sorted_addr_cells.size());
            for (size_t i = 0; i < data.sorted_addr_cells.size(); i++) {
                new_points[i] = data.addr_points[data.sorted_addr_cells[i].item_id];
                data.sorted_addr_cells[i].item_id = static_cast<uint32_t>(i);
            }
            data.addr_points = std::move(new_points);
            data.cell_to_addrs.clear();
            std::cerr << "  Addr points reordered: " << data.addr_points.size() << " points" << std::endl;
        }
        log_phase("  Stabilize: reorder addr_points", _st, _sc);

        // Step 2: Reorder way headers by primary S2 cell + name.
        // Node arrays stay in original order (PBF order is naturally stable).
        // Way headers get updated node_offsets pointing to original positions.
        if (!data.ways.empty()) {
            size_t nways = data.ways.size();

            // Compute primary cell for each way (cell of first node)
            std::vector<uint64_t> primary_cells(nways);
            for (size_t i = 0; i < nways; i++) {
                auto& w = data.ways[i];
                if (w.node_count > 0) {
                    auto& n = data.street_nodes[w.node_offset];
                    primary_cells[i] = S2CellId(S2LatLng::FromDegrees(n.lat, n.lng)).parent(kStreetCellLevel).id();
                }
            }

            // Sort by (primary_cell, name)
            const auto& sp = data.string_pool.data();
            std::vector<uint32_t> order(nways);
            std::iota(order.begin(), order.end(), 0);
            std::sort(order.begin(), order.end(), [&](uint32_t a, uint32_t b) {
                if (primary_cells[a] != primary_cells[b]) return primary_cells[a] < primary_cells[b];
                return strcmp(sp.data() + data.ways[a].name_id, sp.data() + data.ways[b].name_id) < 0;
            });

            // Build old→new mapping and reorder headers (node_offsets unchanged)
            std::vector<uint32_t> old_to_new(nways);
            std::vector<WayHeader> new_ways(nways);
            for (uint32_t new_id = 0; new_id < nways; new_id++) {
                old_to_new[order[new_id]] = new_id;
                new_ways[new_id] = data.ways[order[new_id]]; // keeps original node_offset
            }
            data.ways = std::move(new_ways);

            // Update sorted_way_cells with new IDs
            for (auto& pair : data.sorted_way_cells)
                pair.item_id = old_to_new[pair.item_id];
            auto cmp = [](const CellItemPair& a, const CellItemPair& b) {
                return a.cell_id < b.cell_id || (a.cell_id == b.cell_id && a.item_id < b.item_id);
            };
            std::sort(data.sorted_way_cells.begin(), data.sorted_way_cells.end(), cmp);

            data.cell_to_ways.clear();
            std::cerr << "  Ways reordered: " << nways << " ways (nodes kept in place)" << std::endl;
        }
        log_phase("  Stabilize: reorder ways", _st, _sc);

        // Step 3: Reorder interpolation headers (same approach, nodes stay in place).
        if (!data.interp_ways.empty()) {
            size_t ninterps = data.interp_ways.size();
            std::vector<uint64_t> primary_cells(ninterps);
            for (size_t i = 0; i < ninterps; i++) {
                auto& iw = data.interp_ways[i];
                if (iw.node_count > 0) {
                    auto& n = data.interp_nodes[iw.node_offset];
                    primary_cells[i] = S2CellId(S2LatLng::FromDegrees(n.lat, n.lng)).parent(kStreetCellLevel).id();
                }
            }

            const auto& sp = data.string_pool.data();
            std::vector<uint32_t> order(ninterps);
            std::iota(order.begin(), order.end(), 0);
            std::sort(order.begin(), order.end(), [&](uint32_t a, uint32_t b) {
                if (primary_cells[a] != primary_cells[b]) return primary_cells[a] < primary_cells[b];
                return strcmp(sp.data() + data.interp_ways[a].street_id,
                              sp.data() + data.interp_ways[b].street_id) < 0;
            });

            std::vector<uint32_t> old_to_new(ninterps);
            std::vector<InterpWay> new_interps(ninterps);
            for (uint32_t new_id = 0; new_id < ninterps; new_id++) {
                old_to_new[order[new_id]] = new_id;
                new_interps[new_id] = data.interp_ways[order[new_id]];
            }
            data.interp_ways = std::move(new_interps);

            for (auto& pair : data.sorted_interp_cells)
                pair.item_id = old_to_new[pair.item_id];
            auto cmp = [](const CellItemPair& a, const CellItemPair& b) {
                return a.cell_id < b.cell_id || (a.cell_id == b.cell_id && a.item_id < b.item_id);
            };
            std::sort(data.sorted_interp_cells.begin(), data.sorted_interp_cells.end(), cmp);

            data.cell_to_interps.clear();
            std::cerr << "  Interps reordered: " << ninterps << " interps (nodes kept in place)" << std::endl;
        }
        log_phase("  Stabilize: reorder interps", _st, _sc);
    }

    // --- Write index files ---
    log_phase("Deduplication + stabilization", _pt, _cpu);

    // --- Write index files ---
    log_phase("Deduplication", _pt, _cpu);