The file /root/traccar-geocoder/builder/tools/geocoder_patch.cpp has been updated successfully.

        std::cerr << "Entry pipeline (streaming, one type at a time)..." << std::endl;

        auto get_rm = [&](PatchFileId fid) -> const std::vector<uint32_t>& {
            static std::vector<uint32_t> empty;
            auto it = id_remaps.find((uint32_t)fid); return it != id_remaps.end() ? it->second : empty;
        };

        // Load old geo_cells
        auto old_geo_data = read_file(cur_dir + "/geo_cells.bin");
        size_t n_old_cells = old_geo_data.size() / 20;

        // Build derived cell list (old - removed + added, sorted)
        std::unordered_set<uint64_t> rm_set(geo_removed.begin(), geo_removed.end());
        struct CellRef { uint64_t cid; int32_t old_idx; };
        std::vector<CellRef> cells;
        cells.reserve(n_old_cells + geo_added.size());
        for (size_t i = 0; i < n_old_cells; i++) {
            uint64_t cid; memcpy(&cid, old_geo_data.data() + i * 20, 8);
            if (!rm_set.count(cid)) cells.push_back({cid, (int32_t)i});
        }
        { std::unordered_set<uint64_t>().swap(rm_set); } // free
        for (uint64_t cid : geo_added) cells.push_back({cid, -1});
        std::sort(cells.begin(), cells.end(), [](const CellRef& a, const CellRef& b) { return a.cid < b.cid; });
        log_phase("  Cell list built", t_entry);

        // Process each entry type SEPARATELY to keep memory low
        // For each type: load old entries → walk cells → remap + correct → write via fwrite
        // geo_cells offsets are recorded per-type and assembled at the end

        // Per-cell output offsets for geo_cells (0xFFFFFFFF = no data)
        std::vector<uint32_t> se_offsets(cells.size(), 0xFFFFFFFF);
        std::vector<uint32_t> ae_offsets(cells.size(), 0xFFFFFFFF);
        std::vector<uint32_t> ie_offsets(cells.size(), 0xFFFFFFFF);

        auto process_entry_type = [&](PatchFileId data_fid, PatchFileId entry_fid,
                                       const std::string& entry_fname, size_t geo_off_pos,
                                       std::vector<uint32_t>& out_offsets) {
            auto& rm = get_rm(data_fid);
            auto old_entries = read_file(cur_dir + "/" + entry_fname);

            // Build correction map for this type
            std::unordered_map<uint64_t, const std::vector<uint32_t>*> corr_map;
            auto ecit = entry_corrections.find((uint32_t)entry_fid);
            if (ecit != entry_corrections.end())
                for (auto& c : ecit->second) corr_map[c.cell_id] = &c.ids;

            FILE* outf = fopen((out_dir + "/" + entry_fname).c_str(), "wb");
            std::vector<uint32_t> ids; // reusable per-cell buffer
            ids.reserve(256);

            for (size_t ci = 0; ci < cells.size(); ci++) {
                uint64_t cid = cells[ci].cid;
                int32_t oi = cells[ci].old_idx;

                // Determine if this cell has entries for this type
                bool has_entry = false;
                if (oi >= 0) {
                    uint32_t off; memcpy(&off, old_geo_data.data() + oi * 20 + geo_off_pos, 4);
                    has_entry = (off != 0xFFFFFFFF);
                }
                // Flag corrections can change has_entry
                auto fc = flag_corrections.find(cid);
                if (fc != flag_corrections.end()) {
                    uint8_t bit = (geo_off_pos == 8) ? 1 : (geo_off_pos == 12) ? 2 : 4;
                    has_entry = (fc->second & bit) != 0;
                }
                // Corrections can introduce entries
                if (corr_map.count(cid)) has_entry = true;

                if (!has_entry) continue;

                // Check for correction
                auto cit = corr_map.find(cid);
                if (cit != corr_map.end()) {
                    // Write correction data directly
                    if (!cit->second->empty()) {
                        out_offsets[ci] = (uint32_t)ftell(outf);
                        uint16_t count = cit->second->size();
                        fwrite(&count, 2, 1, outf);
                        fwrite(cit->second->data(), 4, count, outf);
                    }
                    continue;
                }

                // No correction: read old entries, remap, write
                if (oi < 0) continue; // added cell, no old data
                uint32_t off; memcpy(&off, old_geo_data.data() + oi * 20 + geo_off_pos, 4);
                if (off == 0xFFFFFFFF || off + 2 > old_entries.size()) continue;
                uint16_t count; memcpy(&count, old_entries.data() + off, 2);
                if (count == 0 || off + 2 + count * 4 > old_entries.size()) continue;

                ids.resize(count);
                memcpy(ids.data(), old_entries.data() + off + 2, count * 4);
                // Remap
                for (auto& id : ids)
                    if (id < rm.size() && rm[id] != 0xFFFFFFFF) id = rm[id];
                std::sort(ids.begin(), ids.end());

                out_offsets[ci] = (uint32_t)ftell(outf);
                fwrite(&count, 2, 1, outf);
                fwrite(ids.data(), 4, count, outf);
            }
            fclose(outf);
        };

        process_entry_type(PatchFileId::STREET_WAYS, PatchFileId::STREET_ENTRIES,
                          "street_entries.bin", 8, se_offsets);
        log_phase("  street_entries", t_entry);
        malloc_trim(0);

        process_entry_type(PatchFileId::ADDR_POINTS, PatchFileId::ADDR_ENTRIES,
                          "addr_entries.bin", 12, ae_offsets);
        log_phase("  addr_entries", t_entry);
        malloc_trim(0);

        process_entry_type(PatchFileId::INTERP_WAYS, PatchFileId::INTERP_ENTRIES,
                          "interp_entries.bin", 16, ie_offsets);
        log_phase("  interp_entries", t_entry);

        // Write geo_cells using the recorded offsets
        {
            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");
            for (size_t ci = 0; ci < cells.size(); ci++) {
                fwrite(&cells[ci].cid, 8, 1, fg);
                fwrite(&se_offsets[ci], 4, 1, fg);
                fwrite(&ae_offsets[ci], 4, 1, fg);
                fwrite(&ie_offsets[ci], 4, 1, fg);
            }
            fclose(fg);
        }
        log_phase("  geo_cells written", t_entry);

// geocoder-patch v4: Low-memory streaming patch application.
//
// Key design: mmap old files (zero copy), stream output via fwrite (never accumulate),
// process entry pipeline cell-by-cell. Target: <1 GiB peak RSS for planet.
//
// Usage: geocoder-patch <current-dir> <patch-file> -o <output-dir>

#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fcntl.h>
#include <iomanip>
#include <iostream>
#include <malloc.h>
#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unordered_map>
#include <unordered_set>
#include <unistd.h>
#include <vector>

#include "patch_format.h"

enum MergeOp : uint8_t { OP_MATCH_RUN = 0, OP_INSERT_RUN = 1, OP_DELETE_RUN = 2 };

// --- Helpers ---
static double now_ms() {
    struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6;
}
static size_t get_rss_mb() {
    FILE* f = fopen("/proc/self/statm", "r");
    if (!f) return 0;
    size_t dummy, rss; fscanf(f, "%zu %zu", &dummy, &rss); fclose(f);
    return rss * 4096 / (1024*1024);
}
static void log_phase(const char* label, double start) {
    std::cerr << "  [" << std::fixed << std::setprecision(1) << (now_ms() - start) / 1000.0
              << "s, " << get_rss_mb() << " MiB] " << label << std::endl;
}

// mmap a file read-only. Returns {pointer, size}. Caller must munmap.
struct MappedFile { const char* data; size_t size; };
static MappedFile mmap_file(const std::string& path) {
    int fd = open(path.c_str(), O_RDONLY);
    if (fd < 0) return {nullptr, 0};
    struct stat st; fstat(fd, &st);
    size_t sz = st.st_size;
    if (sz == 0) { close(fd); return {nullptr, 0}; }
    void* p = mmap(nullptr, sz, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    if (p == MAP_FAILED) return {nullptr, 0};
    return {static_cast<const char*>(p), sz};
}
static void unmap_file(MappedFile& f) {
    if (f.data) { munmap(const_cast<char*>(f.data), f.size); f.data = nullptr; f.size = 0; }
}

// Detect stride from file size
static size_t detect_stride(const std::string& path, std::initializer_list<size_t> candidates) {
    struct stat st; if (stat(path.c_str(), &st) != 0) return *candidates.begin();
    for (size_t s : candidates) if (st.st_size % s == 0 && st.st_size > 0) return s;
    return *candidates.begin();
}

int main(int argc, char* argv[]) {
    if (argc < 5 || std::string(argv[3]) != "-o") {
        std::cerr << "Usage: geocoder-patch <current-dir> <patch-file> -o <output-dir>" << std::endl;
        return 1;
    }
    std::string cur_dir = argv[1], patch_path = argv[2], out_dir = argv[4];
    ensure_dir(out_dir);
    std::string tmpdir = "/tmp/geocoder-patch-" + std::to_string(getpid());
    ensure_dir(tmpdir);
    double t_start = now_ms();

    // --- Phase 1: Decompress + mmap patch ---
    std::string raw_path = tmpdir + "/patch.raw";
    {
        std::string cmd = "zstd -d '" + patch_path + "' -o '" + raw_path + "' -f --quiet 2>/dev/null";
        if (system(cmd.c_str()) != 0) { std::cerr << "Failed to decompress" << std::endl; return 1; }
    }
    MappedFile patch_map = mmap_file(raw_path);
    if (!patch_map.data) { std::cerr << "Failed to mmap patch" << std::endl; return 1; }
    remove(raw_path.c_str());
    const char* P = patch_map.data;
    size_t patch_size = patch_map.size;
    std::cerr << "Patch: " << patch_size << " bytes" << std::endl;
    log_phase("Decompress", t_start);

    size_t pos = 0;
    auto ru32 = [&]() -> uint32_t { uint32_t v; memcpy(&v, P+pos, 4); pos += 4; return v; };
    auto ru64 = [&]() -> uint64_t { uint64_t v; memcpy(&v, P+pos, 8); pos += 8; return v; };

    // Header
    if (memcmp(P, GCPATCH_MAGIC, 8) != 0) { std::cerr << "Bad magic" << std::endl; return 1; }
    pos = 8;
    uint32_t ver = ru32(); if (ver != 2) { std::cerr << "Bad version" << std::endl; return 1; }
    ru32(); // flags

    // --- Phase 2: String rebuild ---
    std::unordered_map<uint32_t, uint32_t> str_remap;
    {
        uint32_t marker = ru32();
        if (marker == 0xFFFFFFF7) {
            uint32_t n_added = ru32(), n_deleted = ru32();
            std::vector<std::string> added;
            for (uint32_t i = 0; i < n_added; i++) { const char* s = P+pos; added.push_back(s); pos += strlen(s)+1; }
            std::vector<uint32_t> del_idx(n_deleted);
            for (uint32_t i = 0; i < n_deleted; i++) del_idx[i] = ru32();
            std::unordered_set<uint32_t> del_set(del_idx.begin(), del_idx.end());

            MappedFile old_pool = mmap_file(cur_dir + "/strings.bin");
            std::vector<std::pair<uint32_t, std::string>> old_strs;
            size_t sp = 0;
            while (sp < old_pool.size) {
                old_strs.push_back({(uint32_t)sp, std::string(old_pool.data + sp)});
                sp += strlen(old_pool.data + sp) + 1;
            }
            std::vector<std::string> merged;
            for (size_t i = 0; i < old_strs.size(); i++)
                if (!del_set.count(i)) merged.push_back(old_strs[i].second);
            for (auto& s : added) merged.push_back(s);
            std::sort(merged.begin(), merged.end());

            // Write new pool and build remap
            FILE* fp = fopen((out_dir + "/strings.bin").c_str(), "wb");
            std::unordered_map<std::string, uint32_t> new_offs;
            uint32_t wpos = 0;
            for (auto& s : merged) {
                new_offs[s] = wpos;
                fwrite(s.c_str(), 1, s.size() + 1, fp);
                wpos += s.size() + 1;
            }
            fclose(fp);
            for (auto& [old_off, s] : old_strs) {
                auto it = new_offs.find(s);
                if (it != new_offs.end() && it->second != old_off) str_remap[old_off] = it->second;
            }
            unmap_file(old_pool);
            std::cerr << "  Strings: +" << n_added << " -" << n_deleted << " → " << merged.size()
                      << ", " << str_remap.size() << " remapped" << std::endl;
            marker = ru32();
        }
        if (marker == 0xFFFFFFFE) { uint32_t c = ru32(); for (uint32_t i = 0; i < c; i++) { uint32_t a = ru32(), b = ru32(); str_remap[a] = b; } }
        else pos -= 4;
    }
    log_phase("Strings", t_start);

    // Detect strides
    size_t way_stride = detect_stride(cur_dir + "/street_ways.bin", {12, 9});
    size_t interp_stride = detect_stride(cur_dir + "/interp_ways.bin", {24, 20, 18});
    size_t admin_stride = detect_stride(cur_dir + "/admin_polygons.bin", {24, 20, 19});

    // String remap applier (works on mutable copy)
    auto apply_str_remap = [&](std::vector<char>& data, uint32_t fid, size_t stride) {
        if (str_remap.empty()) return;
        std::vector<size_t> offs;
        if (fid == (uint32_t)PatchFileId::ADDR_POINTS) offs = {8, 12};
        else if (fid == (uint32_t)PatchFileId::STREET_WAYS) offs = {(stride == 12) ? 8ul : 5ul};
        else if (fid == (uint32_t)PatchFileId::INTERP_WAYS) offs = {(stride >= 20) ? 8ul : 5ul};
        else if (fid == (uint32_t)PatchFileId::ADMIN_POLYGONS) offs = {8};
        else return;
        for (size_t i = 0; i + stride <= data.size(); i += stride)
            for (size_t o : offs) { uint32_t v; memcpy(&v, data.data()+i+o, 4); auto it = str_remap.find(v); if (it != str_remap.end()) memcpy(data.data()+i+o, &it->second, 4); }
    };

    // --- Phase 3: Merge replays (streaming output) ---
    // ID remaps: file_id → vector<uint32_t> where remap[old_idx] = new_idx
    std::unordered_map<uint32_t, std::vector<uint32_t>> id_remaps;
    std::vector<uint64_t> geo_added, geo_removed, admin_added, admin_removed;
    std::unordered_map<uint64_t, uint8_t> flag_corrections;
    struct CellCorr { uint64_t cell_id; std::vector<uint32_t> ids; };
    std::unordered_map<uint32_t, std::vector<CellCorr>> entry_corrections;

    while (pos < patch_size) {
        uint32_t file_id = ru32();
        if (file_id == 0xFFFFFFFF) break;

        // --- Metadata sections ---
        if (file_id == 0xFFFFFFF7) {
            // String diff in main loop — process it (may be the only occurrence)
            uint32_t n_added = ru32(), n_deleted = ru32();
            std::vector<std::string> added;
            for (uint32_t i = 0; i < n_added; i++) { const char* s = P+pos; added.push_back(s); pos += strlen(s)+1; }
            std::vector<uint32_t> del_idx(n_deleted);
            for (uint32_t i = 0; i < n_deleted; i++) del_idx[i] = ru32();
            std::unordered_set<uint32_t> del_set(del_idx.begin(), del_idx.end());
            MappedFile old_pool = mmap_file(cur_dir + "/strings.bin");
            std::vector<std::pair<uint32_t, std::string>> old_strs;
            size_t sp = 0;
            while (sp < old_pool.size) { old_strs.push_back({(uint32_t)sp, std::string(old_pool.data+sp)}); sp += strlen(old_pool.data+sp)+1; }
            std::vector<std::string> merged;
            for (size_t i = 0; i < old_strs.size(); i++) if (!del_set.count(i)) merged.push_back(old_strs[i].second);
            for (auto& s : added) merged.push_back(s);
            std::sort(merged.begin(), merged.end());
            FILE* fp = fopen((out_dir + "/strings.bin").c_str(), "wb");
            std::unordered_map<std::string, uint32_t> new_offs;
            uint32_t wpos = 0;
            for (auto& s : merged) { new_offs[s] = wpos; fwrite(s.c_str(), 1, s.size()+1, fp); wpos += s.size()+1; }
            fclose(fp);
            for (auto& [old_off, s] : old_strs) { auto it = new_offs.find(s); if (it != new_offs.end() && it->second != old_off) str_remap[old_off] = it->second; }
            unmap_file(old_pool);
            std::cerr << "  Strings (loop): +" << n_added << " -" << n_deleted << " → " << merged.size() << std::endl;
            continue;
        }
        if (file_id == CELL_CHANGES_GEO_MARKER) {
            uint32_t na = ru32(), nr = ru32();
            geo_added.resize(na); geo_removed.resize(nr);
            for (uint32_t i = 0; i < na; i++) { memcpy(&geo_added[i], P+pos, 8); pos += 8; }
            for (uint32_t i = 0; i < nr; i++) { memcpy(&geo_removed[i], P+pos, 8); pos += 8; }
            std::cerr << "  Geo cells: +" << na << " -" << nr << std::endl;
            continue;
        }
        if (file_id == CELL_CHANGES_ADMIN_MARKER) {
            uint32_t na = ru32(), nr = ru32();
            admin_added.resize(na); admin_removed.resize(nr);
            for (uint32_t i = 0; i < na; i++) { memcpy(&admin_added[i], P+pos, 8); pos += 8; }
            for (uint32_t i = 0; i < nr; i++) { memcpy(&admin_removed[i], P+pos, 8); pos += 8; }
            std::cerr << "  Admin cells: +" << na << " -" << nr << std::endl;
            continue;
        }
        if (file_id == CELL_FLAGS_MARKER) {
            uint32_t c = ru32();
            for (uint32_t i = 0; i < c; i++) { uint64_t cid; memcpy(&cid, P+pos, 8); pos += 8; flag_corrections[cid] = P[pos++]; }
            std::cerr << "  Flag corrections: " << c << std::endl;
            continue;
        }
        if (file_id == SECONDARY_REMAP_MARKER) {
            uint32_t nf = ru32();
            for (uint32_t f = 0; f < nf; f++) {
                uint32_t fid = ru32(), np = ru32();
                auto& rm = id_remaps[fid];
                for (uint32_t i = 0; i < np; i++) { uint32_t o = ru32(), n = ru32(); if (o < rm.size()) rm[o] = n; }
                std::cerr << "  Secondary remap " << fid << ": " << np << " pairs" << std::endl;
            }
            continue;
        }
        if (file_id == ENTRY_CORRECTION_MARKER) {
            uint32_t fid = ru32(), c = ru32();
            auto& list = entry_corrections[fid];
            for (uint32_t i = 0; i < c; i++) {
                uint64_t cid; memcpy(&cid, P+pos, 8); pos += 8;
                uint16_t ec; memcpy(&ec, P+pos, 2); pos += 2;
                std::vector<uint32_t> ids(ec);
                if (ec > 0) { memcpy(ids.data(), P+pos, ec*4); pos += ec*4; }
                list.push_back({cid, std::move(ids)});
            }
            std::cerr << "  Entry corrections " << fid << ": " << c << std::endl;
            continue;
        }

        // --- Merge sequence replay (streaming) ---
        uint32_t stride = ru32();
        uint64_t old_size = ru64(), new_size = ru64();
        if (file_id >= (uint32_t)PatchFileId::COUNT) { std::cerr << "Unknown file " << file_id << std::endl; break; }
        const char* fname = patch_file_names[file_id];

        if (stride == 0) {
            // Full replacement — write directly from patch mmap
            uint32_t nf = ru32(); (void)nf; uint64_t ds = ru64();
            FILE* fp = fopen((out_dir + "/" + fname).c_str(), "wb");
            fwrite(P+pos, 1, ds, fp); fclose(fp);
            pos += ds;
            std::cerr << "  " << fname << ": full replace " << ds << " bytes" << std::endl;
            continue;
        }
        if (stride == 0xFE) { uint32_t nf = ru32(); (void)nf; uint64_t ds = ru64(); pos += ds; continue; }

        size_t actual_stride = stride;
        if (file_id == (uint32_t)PatchFileId::STREET_WAYS) actual_stride = way_stride;
        else if (file_id == (uint32_t)PatchFileId::INTERP_WAYS) actual_stride = interp_stride;
        else if (file_id == (uint32_t)PatchFileId::ADMIN_POLYGONS) actual_stride = admin_stride;

        // Load old file into mutable buffer (need to apply remap + fixups)
        // For large files (nodes/vertices), this is the peak memory per file.
        // Files are processed sequentially, so only one is in memory at a time.
        auto old_data = read_file(cur_dir + "/" + std::string(fname));

        // Zero padding
        if (file_id == (uint32_t)PatchFileId::ADMIN_POLYGONS && actual_stride == 24)
            for (size_t i = 0; i + actual_stride <= old_data.size(); i += actual_stride) memset(old_data.data()+i+13, 0, 3);
        if (file_id == (uint32_t)PatchFileId::INTERP_WAYS && actual_stride == 24)
            for (size_t i = 0; i + actual_stride <= old_data.size(); i += actual_stride) { memset(old_data.data()+i+5, 0, 3); memset(old_data.data()+i+21, 0, 3); }

        apply_str_remap(old_data, file_id, actual_stride);

        // Apply fixups
        uint32_t n_fixups = ru32();
        if (n_fixups > 0) {
            uint32_t delta_size = ru32();
            uint32_t pi = 0, pv = 0; size_t fp = pos;
            for (uint32_t i = 0; i < n_fixups; i++) {
                uint32_t idx = pi + read_varint(P, fp);
                uint32_t val = pv + read_varint(P, fp);
                if ((size_t)idx * actual_stride + 4 <= old_data.size())
                    memcpy(old_data.data() + (size_t)idx * actual_stride, &val, 4);
                pi = idx; pv = val;
            }
            pos = fp;
        }

        // Replay merge sequence — stream output directly to file
        uint64_t seq_size = ru64();
        size_t seq_end = pos + seq_size;
        FILE* outf = fopen((out_dir + "/" + fname).c_str(), "wb");

        // Track ID remap (only for data files that need entry reconstruction)
        bool track = (file_id == (uint32_t)PatchFileId::ADDR_POINTS ||
                      file_id == (uint32_t)PatchFileId::STREET_WAYS ||
                      file_id == (uint32_t)PatchFileId::INTERP_WAYS ||
                      file_id == (uint32_t)PatchFileId::ADMIN_POLYGONS);
        std::vector<uint32_t> id_map;
        if (track) id_map.assign(old_data.size() / actual_stride, 0xFFFFFFFF);

        size_t old_rec = 0, new_rec = 0, old_bytes = 0, written = 0;
        while (pos < seq_end) {
            uint8_t op = P[pos++];
            uint32_t count; memcpy(&count, P+pos, 4); pos += 4;
            if (op == OP_MATCH_RUN) {
                size_t bytes = count * actual_stride;
                fwrite(old_data.data() + old_bytes, 1, bytes, outf);
                written += bytes;
                if (track) for (uint32_t k = 0; k < count; k++) if (old_rec+k < id_map.size()) id_map[old_rec+k] = new_rec+k;
                old_rec += count; new_rec += count; old_bytes += bytes;
            } else if (op == OP_INSERT_RUN) {
                size_t bytes = count * actual_stride;
                fwrite(P+pos, 1, bytes, outf);
                written += bytes; pos += bytes; new_rec += count;
            } else if (op == OP_DELETE_RUN) {
                old_rec += count; old_bytes += count * actual_stride;
            }
        }
        fclose(outf);

        if (track) {
            id_remaps[file_id] = std::move(id_map);
            size_t matched = 0;
            for (auto v : id_remaps[file_id]) if (v != 0xFFFFFFFF) matched++;
            std::cerr << "  " << fname << ": " << written << " bytes (mapped " << matched << "/" << id_remaps[file_id].size() << ")" << std::endl;
        } else {
            std::cerr << "  " << fname << ": " << written << " bytes" << std::endl;
        }

        // Free old data immediately — don't let glibc hold the pages
        { std::vector<char>().swap(old_data); }
        malloc_trim(0);
    }
    log_phase("Merge replays", t_start);

    // Free string remap (no longer needed)
    { std::unordered_map<uint32_t,uint32_t>().swap(str_remap); }
    malloc_trim(0);

    // --- Phase 4: Streaming entry pipeline ---
    // Walk old geo_cells, remap IDs, apply corrections, write output directly to files.
    {
        double t_entry = now_ms();
        std::cerr << "Entry pipeline (proven rebuild approach)..." << std::endl;

        auto get_rm = [&](PatchFileId fid) -> const std::vector<uint32_t>& {
            static std::vector<uint32_t> empty;
            auto it = id_remaps.find((uint32_t)fid); return it != id_remaps.end() ? it->second : empty;
        };
        auto& w_rm = get_rm(PatchFileId::STREET_WAYS);
        auto& a_rm = get_rm(PatchFileId::ADDR_POINTS);
        auto& i_rm = get_rm(PatchFileId::INTERP_WAYS);

        // Rebuild entries using proven rebuild_geo_from_remap_vec
        auto old_geo_data = read_file(cur_dir + "/geo_cells.bin");
        auto old_se = read_file(cur_dir + "/street_entries.bin");
        auto old_ae = read_file(cur_dir + "/addr_entries.bin");
        auto old_ie = read_file(cur_dir + "/interp_entries.bin");
        auto geo = rebuild_geo_from_remap_vec(old_geo_data, old_se, old_ae, old_ie,
                                                w_rm, a_rm, i_rm, geo_added, geo_removed);
        // Free inputs
        { std::vector<char>().swap(old_se); std::vector<char>().swap(old_ae); std::vector<char>().swap(old_ie); }
        log_phase("  Geo rebuild", t_entry);

        // Apply corrections sequentially (one file at a time to limit memory)
        // Build correction maps
        auto apply_corrections = [&](uint32_t fid, const std::string& fname,
                                      std::vector<char>& derived_entries,
                                      const std::vector<char>& derived_cells, size_t cell_stride,
                                      size_t off_pos) {
            auto ecit = entry_corrections.find(fid);
            if (ecit == entry_corrections.end() || ecit->second.empty()) {
                write_file(out_dir + "/" + fname, derived_entries);
                return;
            }
            std::unordered_map<uint64_t, const std::vector<uint32_t>*> cm;
            for (auto& c : ecit->second) cm[c.cell_id] = &c.ids;

            // Walk cells, apply corrections, write output via FILE*
            size_t n = derived_cells.size() / cell_stride;
            FILE* fe = fopen((out_dir + "/" + fname).c_str(), "wb");
            FILE* fc_out = nullptr;
            std::vector<char> new_cells;
            uint32_t no_data_v = 0xFFFFFFFF;
            uint32_t fe_pos = 0;

            for (size_t i = 0; i < n; i++) {
                uint64_t cid; memcpy(&cid, derived_cells.data() + i * cell_stride, 8);
                uint32_t off; memcpy(&off, derived_cells.data() + i * cell_stride + off_pos, 4);

                auto cit = cm.find(cid);
                if (cit != cm.end()) {
                    // Use correction data
                    uint32_t new_off = cit->second->empty() ? no_data_v : fe_pos;
                    if (!cit->second->empty()) {
                        uint16_t c = cit->second->size();
                        fwrite(&c, 2, 1, fe);
                        fwrite(cit->second->data(), 4, c, fe);
                        fe_pos += 2 + c * 4;
                    }
                    // Update offset in cells data (in memory)
                    memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
                } else if (off != no_data_v) {
                    // Copy original entry data
                    if (off + 2 <= derived_entries.size()) {
                        uint16_t c; memcpy(&c, derived_entries.data() + off, 2);
                        uint32_t new_off = fe_pos;
                        fwrite(derived_entries.data() + off, 1, 2 + c * 4, fe);
                        fe_pos += 2 + c * 4;
                        memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
                    }
                }
            }
            fclose(fe);
            std::cerr << "  " << fname << ": " << cm.size() << " corrections applied" << std::endl;
        };

        // Apply corrections for each geo entry type
        apply_corrections((uint32_t)PatchFileId::STREET_ENTRIES, "street_entries.bin",
                         geo.street_entries_data, geo.geo_cells_data, 20, 8);
        apply_corrections((uint32_t)PatchFileId::ADDR_ENTRIES, "addr_entries.bin",
                         geo.addr_entries_data, geo.geo_cells_data, 20, 12);
        apply_corrections((uint32_t)PatchFileId::INTERP_ENTRIES, "interp_entries.bin",
                         geo.interp_entries_data, geo.geo_cells_data, 20, 16);

        // Write the corrected geo_cells (offsets updated in-place by apply_corrections)
        // Rebuild offsets using flag corrections
        {
            size_t n = geo.geo_cells_data.size() / 20;
            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");
            // Scan corrected entry files to build offset arrays
            auto scan_offsets = [](const std::string& path) -> std::vector<uint32_t> {
                auto data = read_file(path);
                std::vector<uint32_t> offsets;
                uint32_t pos = 0;
                while (pos + 2 <= data.size()) {
                    offsets.push_back(pos);
                    uint16_t c; memcpy(&c, data.data() + pos, 2);
                    pos += 2 + c * 4;
                }
                return offsets;
            };
            auto s_offs = scan_offsets(out_dir + "/street_entries.bin");
            auto a_offs = scan_offsets(out_dir + "/addr_entries.bin");
            auto i_offs = scan_offsets(out_dir + "/interp_entries.bin");

            uint32_t no_data_v = 0xFFFFFFFF;
            size_t si = 0, ai = 0, ii = 0;
            for (size_t c = 0; c < n; c++) {
                uint64_t cid; memcpy(&cid, geo.geo_cells_data.data() + c*20, 8);
                uint32_t old_s, old_a, old_i;
                memcpy(&old_s, geo.geo_cells_data.data()+c*20+8, 4);
                memcpy(&old_a, geo.geo_cells_data.data()+c*20+12, 4);
                memcpy(&old_i, geo.geo_cells_data.data()+c*20+16, 4);
                bool hs = (old_s != no_data_v), ha = (old_a != no_data_v), hi = (old_i != no_data_v);
                auto fc = flag_corrections.find(cid);
                if (fc != flag_corrections.end()) { hs = fc->second & 1; ha = fc->second & 2; hi = fc->second & 4; }
                uint32_t ns = (hs && si < s_offs.size()) ? s_offs[si++] : no_data_v;
                uint32_t na = (ha && ai < a_offs.size()) ? a_offs[ai++] : no_data_v;
                uint32_t ni = (hi && ii < i_offs.size()) ? i_offs[ii++] : no_data_v;
                fwrite(&cid, 8, 1, fg); fwrite(&ns, 4, 1, fg); fwrite(&na, 4, 1, fg); fwrite(&ni, 4, 1, fg);
            }
            fclose(fg);
        }
        geo = RebuiltGeo{}; // free
        malloc_trim(0);
        log_phase("  Geo entries + corrections", t_entry);

        // Admin: small, use existing rebuild + corrections
        {
            uint32_t no_data = 0xFFFFFFFF;
            std::unordered_map<uint32_t,uint32_t> ad_rm;
            auto ait = id_remaps.find((uint32_t)PatchFileId::ADMIN_POLYGONS);
            if (ait != id_remaps.end()) {
                for (uint32_t i = 0; i < ait->second.size(); i++)
                    if (ait->second[i] != 0xFFFFFFFF) ad_rm[i] = ait->second[i];
            }
            auto old_ac = read_file(cur_dir + "/admin_cells.bin");
            auto old_adme = read_file(cur_dir + "/admin_entries.bin");
            auto admin = rebuild_admin_from_remap(old_ac, old_adme, ad_rm, admin_added, admin_removed);

            auto ecit = entry_corrections.find((uint32_t)PatchFileId::ADMIN_ENTRIES);
            if (ecit != entry_corrections.end()) {
                std::unordered_map<uint64_t, const std::vector<uint32_t>*> ac_corr;
                for (auto& c : ecit->second) ac_corr[c.cell_id] = &c.ids;
                size_t n = admin.admin_cells_data.size() / 12;
                FILE* fac = fopen((out_dir + "/admin_cells.bin").c_str(), "wb");
                FILE* fae = fopen((out_dir + "/admin_entries.bin").c_str(), "wb");
                uint32_t ae_wpos = 0;
                for (size_t i = 0; i < n; i++) {
                    uint64_t cid; memcpy(&cid, admin.admin_cells_data.data()+i*12, 8);
                    fwrite(&cid, 8, 1, fac);
                    auto cit = ac_corr.find(cid);
                    if (cit != ac_corr.end()) {
                        uint32_t off = cit->second->empty() ? no_data : ae_wpos;
                        fwrite(&off, 4, 1, fac);
                        if (!cit->second->empty()) {
                            uint16_t c = cit->second->size();
                            fwrite(&c, 2, 1, fae); fwrite(cit->second->data(), 4, c, fae);
                            ae_wpos += 2 + c*4;
                        }
                    } else {
                        uint32_t old_off; memcpy(&old_off, admin.admin_cells_data.data()+i*12+8, 4);
                        if (old_off != no_data && old_off+2 <= admin.admin_entries_data.size()) {
                            uint32_t new_off = ae_wpos; fwrite(&new_off, 4, 1, fac);
                            uint16_t c; memcpy(&c, admin.admin_entries_data.data()+old_off, 2);
                            fwrite(admin.admin_entries_data.data()+old_off, 1, 2+c*4, fae);
                            ae_wpos += 2+c*4;
                        } else {
                            fwrite(&no_data, 4, 1, fac);
                        }
                    }
                }
                fclose(fac); fclose(fae);
                std::cerr << "  Admin: " << n << " cells, " << ac_corr.size() << " corrections" << std::endl;
            } else {
                write_file(out_dir + "/admin_cells.bin", admin.admin_cells_data);
                write_file(out_dir + "/admin_entries.bin", admin.admin_entries_data);
            }
            log_phase("  Admin", t_entry);
        }
    }

    // Cleanup
    unmap_file(patch_map);
    { std::string cmd = "rm -rf '" + tmpdir + "'"; system(cmd.c_str()); }
    log_phase("Total", t_start);
    std::cerr << "Patch applied. Output in " << out_dir << std::endl;
    return 0;
}

-        std::cerr << "Entry pipeline (proven rebuild approach)..." << std::endl;

+        std::cerr << "Entry pipeline (streaming, one type at a time)..." << std::endl;

-        auto& w_rm = get_rm(PatchFileId::STREET_WAYS);

-        auto& a_rm = get_rm(PatchFileId::ADDR_POINTS);

-        auto& i_rm = get_rm(PatchFileId::INTERP_WAYS);

-        // Rebuild entries using proven rebuild_geo_from_remap_vec

-        auto old_se = read_file(cur_dir + "/street_entries.bin");

-        auto old_ae = read_file(cur_dir + "/addr_entries.bin");

-        auto old_ie = read_file(cur_dir + "/interp_entries.bin");

-        auto geo = rebuild_geo_from_remap_vec(old_geo_data, old_se, old_ae, old_ie,

-                                                w_rm, a_rm, i_rm, geo_added, geo_removed);

-        { std::vector<char>().swap(old_se); std::vector<char>().swap(old_ae); std::vector<char>().swap(old_ie); }

-        log_phase("  Geo rebuild", t_entry);

+        size_t n_old_cells = old_geo_data.size() / 20;

+        // Build derived cell list (old - removed + added, sorted)

+        std::unordered_set<uint64_t> rm_set(geo_removed.begin(), geo_removed.end());

+        struct CellRef { uint64_t cid; int32_t old_idx; };

+        cells.reserve(n_old_cells + geo_added.size());

+        for (size_t i = 0; i < n_old_cells; i++) {

+            uint64_t cid; memcpy(&cid, old_geo_data.data() + i * 20, 8);

+            if (!rm_set.count(cid)) cells.push_back({cid, (int32_t)i});

+        { std::unordered_set<uint64_t>().swap(rm_set); } // free

+        for (uint64_t cid : geo_added) cells.push_back({cid, -1});

+        std::sort(cells.begin(), cells.end(), [](const CellRef& a, const CellRef& b) { return a.cid < b.cid; });

+        log_phase("  Cell list built", t_entry);

+        // Process each entry type SEPARATELY to keep memory low

+        // For each type: load old entries → walk cells → remap + correct → write via fwrite

+        // geo_cells offsets are recorded per-type and assembled at the end

+        // Per-cell output offsets for geo_cells (0xFFFFFFFF = no data)

+        std::vector<uint32_t> se_offsets(cells.size(), 0xFFFFFFFF);

+        std::vector<uint32_t> ae_offsets(cells.size(), 0xFFFFFFFF);

+        std::vector<uint32_t> ie_offsets(cells.size(), 0xFFFFFFFF);

+        auto process_entry_type = [&](PatchFileId data_fid, PatchFileId entry_fid,

+                                       const std::string& entry_fname, size_t geo_off_pos,

+                                       std::vector<uint32_t>& out_offsets) {

+            auto& rm = get_rm(data_fid);

+            auto old_entries = read_file(cur_dir + "/" + entry_fname);

+            // Build correction map for this type

+            std::unordered_map<uint64_t, const std::vector<uint32_t>*> corr_map;

+            auto ecit = entry_corrections.find((uint32_t)entry_fid);

+            if (ecit != entry_corrections.end())

+                for (auto& c : ecit->second) corr_map[c.cell_id] = &c.ids;

+            FILE* outf = fopen((out_dir + "/" + entry_fname).c_str(), "wb");

+            std::vector<uint32_t> ids; // reusable per-cell buffer

+            for (size_t ci = 0; ci < cells.size(); ci++) {

+                uint64_t cid = cells[ci].cid;

+                int32_t oi = cells[ci].old_idx;

+                // Determine if this cell has entries for this type

+                bool has_entry = false;

+                    uint32_t off; memcpy(&off, old_geo_data.data() + oi * 20 + geo_off_pos, 4);

+                    has_entry = (off != 0xFFFFFFFF);

+                // Flag corrections can change has_entry

+                auto fc = flag_corrections.find(cid);

+                if (fc != flag_corrections.end()) {

+                    has_entry = (fc->second & bit) != 0;

+                // Corrections can introduce entries

+                if (corr_map.count(cid)) has_entry = true;

+                if (!has_entry) continue;

+                // Check for correction

+                auto cit = corr_map.find(cid);

+                if (cit != corr_map.end()) {

+                    // Write correction data directly

+                    if (!cit->second->empty()) {

+                        out_offsets[ci] = (uint32_t)ftell(outf);

+                        uint16_t count = cit->second->size();

+                        fwrite(&count, 2, 1, outf);

+                        fwrite(cit->second->data(), 4, count, outf);

+                // No correction: read old entries, remap, write

+                if (oi < 0) continue; // added cell, no old data

+                if (off == 0xFFFFFFFF || off + 2 > old_entries.size()) continue;

+                uint16_t count; memcpy(&count, old_entries.data() + off, 2);

+                if (count == 0 || off + 2 + count * 4 > old_entries.size()) continue;

+                memcpy(ids.data(), old_entries.data() + off + 2, count * 4);

+                    if (id < rm.size() && rm[id] != 0xFFFFFFFF) id = rm[id];

+                std::sort(ids.begin(), ids.end());

+                out_offsets[ci] = (uint32_t)ftell(outf);

+                fwrite(&count, 2, 1, outf);

+                fwrite(ids.data(), 4, count, outf);

+        process_entry_type(PatchFileId::STREET_WAYS, PatchFileId::STREET_ENTRIES,

+                          "street_entries.bin", 8, se_offsets);

+        log_phase("  street_entries", t_entry);

+        process_entry_type(PatchFileId::ADDR_POINTS, PatchFileId::ADDR_ENTRIES,

+                          "addr_entries.bin", 12, ae_offsets);

+        log_phase("  addr_entries", t_entry);

+        process_entry_type(PatchFileId::INTERP_WAYS, PatchFileId::INTERP_ENTRIES,

+                          "interp_entries.bin", 16, ie_offsets);

+        log_phase("  interp_entries", t_entry);

+        // Write geo_cells using the recorded offsets

+            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");

+                fwrite(&cells[ci].cid, 8, 1, fg);

+                fwrite(&se_offsets[ci], 4, 1, fg);

+                fwrite(&ae_offsets[ci], 4, 1, fg);

+                fwrite(&ie_offsets[ci], 4, 1, fg);

+        log_phase("  geo_cells written", t_entry);


Now I need to update the rest — remove the old correction application and geo_cells rebuild code that followed, since the streaming approach handles everything:


   465→            fclose(outf);
   466→        };
   467→
   468→        process_entry_type(PatchFileId::STREET_WAYS, PatchFileId::STREET_ENTRIES,
   469→                          "street_entries.bin", 8, se_offsets);
   470→        log_phase("  street_entries", t_entry);
   471→        malloc_trim(0);
   472→
   473→        process_entry_type(PatchFileId::ADDR_POINTS, PatchFileId::ADDR_ENTRIES,
   474→                          "addr_entries.bin", 12, ae_offsets);
   475→        log_phase("  addr_entries", t_entry);
   476→        malloc_trim(0);
   477→
   478→        process_entry_type(PatchFileId::INTERP_WAYS, PatchFileId::INTERP_ENTRIES,
   479→                          "interp_entries.bin", 16, ie_offsets);
   480→        log_phase("  interp_entries", t_entry);
   481→
   482→        // Write geo_cells using the recorded offsets
   483→        {
   484→            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");

            fclose(outf);
        };

        process_entry_type(PatchFileId::STREET_WAYS, PatchFileId::STREET_ENTRIES,
                          "street_entries.bin", 8, se_offsets);
        log_phase("  street_entries", t_entry);
        malloc_trim(0);

        process_entry_type(PatchFileId::ADDR_POINTS, PatchFileId::ADDR_ENTRIES,
                          "addr_entries.bin", 12, ae_offsets);
        log_phase("  addr_entries", t_entry);
        malloc_trim(0);

        process_entry_type(PatchFileId::INTERP_WAYS, PatchFileId::INTERP_ENTRIES,
                          "interp_entries.bin", 16, ie_offsets);
        log_phase("  interp_entries", t_entry);

        // Write geo_cells using the recorded offsets
        {
            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");


   490→            }
   491→            fclose(fg);
   492→        }
   493→        log_phase("  geo_cells written", t_entry);
   494→
   495→        // Apply corrections sequentially (one file at a time to limit memory)
   496→        // Build correction maps
   497→        auto apply_corrections = [&](uint32_t fid, const std::string& fname,
   498→                                      std::vector<char>& derived_entries,
   499→                                      const std::vector<char>& derived_cells, size_t cell_stride,
   500→                                      size_t off_pos) {
   501→            auto ecit = entry_corrections.find(fid);
   502→            if (ecit == entry_corrections.end() || ecit->second.empty()) {
   503→                write_file(out_dir + "/" + fname, derived_entries);
   504→                return;
   505→            }
   506→            std::unordered_map<uint64_t, const std::vector<uint32_t>*> cm;
   507→            for (auto& c : ecit->second) cm[c.cell_id] = &c.ids;
   508→
   509→            // Walk cells, apply corrections, write output via FILE*
   510→            size_t n = derived_cells.size() / cell_stride;
   511→            FILE* fe = fopen((out_dir + "/" + fname).c_str(), "wb");
   512→            FILE* fc_out = nullptr;
   513→            std::vector<char> new_cells;
   514→            uint32_t no_data_v = 0xFFFFFFFF;
   515→            uint32_t fe_pos = 0;
   516→
   517→            for (size_t i = 0; i < n; i++) {
   518→                uint64_t cid; memcpy(&cid, derived_cells.data() + i * cell_stride, 8);
   519→                uint32_t off; memcpy(&off, derived_cells.data() + i * cell_stride + off_pos, 4);
   520→
   521→                auto cit = cm.find(cid);
   522→                if (cit != cm.end()) {
   523→                    // Use correction data
   524→                    uint32_t new_off = cit->second->empty() ? no_data_v : fe_pos;
   525→                    if (!cit->second->empty()) {
   526→                        uint16_t c = cit->second->size();
   527→                        fwrite(&c, 2, 1, fe);
   528→                        fwrite(cit->second->data(), 4, c, fe);
   529→                        fe_pos += 2 + c * 4;
   530→                    }
   531→                    // Update offset in cells data (in memory)
   532→                    memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
   533→                } else if (off != no_data_v) {
   534→                    // Copy original entry data
   535→                    if (off + 2 <= derived_entries.size()) {
   536→                        uint16_t c; memcpy(&c, derived_entries.data() + off, 2);
   537→                        uint32_t new_off = fe_pos;
   538→                        fwrite(derived_entries.data() + off, 1, 2 + c * 4, fe);
   539→                        fe_pos += 2 + c * 4;
   540→                        memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
   541→                    }
   542→                }
   543→            }
   544→            fclose(fe);
   545→            std::cerr << "  " << fname << ": " << cm.size() << " corrections applied" << std::endl;
   546→        };
   547→
   548→        // Apply corrections for each geo entry type
   549→        apply_corrections((uint32_t)PatchFileId::STREET_ENTRIES, "street_entries.bin",
   550→                         geo.street_entries_data, geo.geo_cells_data, 20, 8);
   551→        apply_corrections((uint32_t)PatchFileId::ADDR_ENTRIES, "addr_entries.bin",
   552→                         geo.addr_entries_data, geo.geo_cells_data, 20, 12);
   553→        apply_corrections((uint32_t)PatchFileId::INTERP_ENTRIES, "interp_entries.bin",
   554→                         geo.interp_entries_data, geo.geo_cells_data, 20, 16);
   555→
   556→        // Write the corrected geo_cells (offsets updated in-place by apply_corrections)
   557→        // Rebuild offsets using flag corrections
   558→        {
   559→            size_t n = geo.geo_cells_data.size() / 20;
   560→            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");
   561→            // Scan corrected entry files to build offset arrays
   562→            auto scan_offsets = [](const std::string& path) -> std::vector<uint32_t> {
   563→                auto data = read_file(path);
   564→                std::vector<uint32_t> offsets;
   565→                uint32_t pos = 0;
   566→                while (pos + 2 <= data.size()) {
   567→                    offsets.push_back(pos);
   568→                    uint16_t c; memcpy(&c, data.data() + pos, 2);
   569→                    pos += 2 + c * 4;

            }
            fclose(fg);
        }
        log_phase("  geo_cells written", t_entry);

        // Apply corrections sequentially (one file at a time to limit memory)
        // Build correction maps
        auto apply_corrections = [&](uint32_t fid, const std::string& fname,
                                      std::vector<char>& derived_entries,
                                      const std::vector<char>& derived_cells, size_t cell_stride,
                                      size_t off_pos) {
            auto ecit = entry_corrections.find(fid);
            if (ecit == entry_corrections.end() || ecit->second.empty()) {
                write_file(out_dir + "/" + fname, derived_entries);
                return;
            }
            std::unordered_map<uint64_t, const std::vector<uint32_t>*> cm;
            for (auto& c : ecit->second) cm[c.cell_id] = &c.ids;

            // Walk cells, apply corrections, write output via FILE*
            size_t n = derived_cells.size() / cell_stride;
            FILE* fe = fopen((out_dir + "/" + fname).c_str(), "wb");
            FILE* fc_out = nullptr;
            std::vector<char> new_cells;
            uint32_t no_data_v = 0xFFFFFFFF;
            uint32_t fe_pos = 0;

            for (size_t i = 0; i < n; i++) {
                uint64_t cid; memcpy(&cid, derived_cells.data() + i * cell_stride, 8);
                uint32_t off; memcpy(&off, derived_cells.data() + i * cell_stride + off_pos, 4);

                auto cit = cm.find(cid);
                if (cit != cm.end()) {
                    // Use correction data
                    uint32_t new_off = cit->second->empty() ? no_data_v : fe_pos;
                    if (!cit->second->empty()) {
                        uint16_t c = cit->second->size();
                        fwrite(&c, 2, 1, fe);
                        fwrite(cit->second->data(), 4, c, fe);
                        fe_pos += 2 + c * 4;
                    }
                    // Update offset in cells data (in memory)
                    memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
                } else if (off != no_data_v) {
                    // Copy original entry data
                    if (off + 2 <= derived_entries.size()) {
                        uint16_t c; memcpy(&c, derived_entries.data() + off, 2);
                        uint32_t new_off = fe_pos;
                        fwrite(derived_entries.data() + off, 1, 2 + c * 4, fe);
                        fe_pos += 2 + c * 4;
                        memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
                    }
                }
            }
            fclose(fe);
            std::cerr << "  " << fname << ": " << cm.size() << " corrections applied" << std::endl;
        };

        // Apply corrections for each geo entry type
        apply_corrections((uint32_t)PatchFileId::STREET_ENTRIES, "street_entries.bin",
                         geo.street_entries_data, geo.geo_cells_data, 20, 8);
        apply_corrections((uint32_t)PatchFileId::ADDR_ENTRIES, "addr_entries.bin",
                         geo.addr_entries_data, geo.geo_cells_data, 20, 12);
        apply_corrections((uint32_t)PatchFileId::INTERP_ENTRIES, "interp_entries.bin",
                         geo.interp_entries_data, geo.geo_cells_data, 20, 16);

        // Write the corrected geo_cells (offsets updated in-place by apply_corrections)
        // Rebuild offsets using flag corrections
        {
            size_t n = geo.geo_cells_data.size() / 20;
            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");
            // Scan corrected entry files to build offset arrays
            auto scan_offsets = [](const std::string& path) -> std::vector<uint32_t> {
                auto data = read_file(path);
                std::vector<uint32_t> offsets;
                uint32_t pos = 0;
                while (pos + 2 <= data.size()) {
                    offsets.push_back(pos);
                    uint16_t c; memcpy(&c, data.data() + pos, 2);
                    pos += 2 + c * 4;


I need to remove the old correction + rebuild code (lines 495-600+). Let me find the end:


   570→                }
   571→                return offsets;
   572→            };
   573→            auto s_offs = scan_offsets(out_dir + "/street_entries.bin");
   574→            auto a_offs = scan_offsets(out_dir + "/addr_entries.bin");
   575→            auto i_offs = scan_offsets(out_dir + "/interp_entries.bin");
   576→
   577→            uint32_t no_data_v = 0xFFFFFFFF;
   578→            size_t si = 0, ai = 0, ii = 0;
   579→            for (size_t c = 0; c < n; c++) {
   580→                uint64_t cid; memcpy(&cid, geo.geo_cells_data.data() + c*20, 8);
   581→                uint32_t old_s, old_a, old_i;
   582→                memcpy(&old_s, geo.geo_cells_data.data()+c*20+8, 4);
   583→                memcpy(&old_a, geo.geo_cells_data.data()+c*20+12, 4);
   584→                memcpy(&old_i, geo.geo_cells_data.data()+c*20+16, 4);
   585→                bool hs = (old_s != no_data_v), ha = (old_a != no_data_v), hi = (old_i != no_data_v);
   586→                auto fc = flag_corrections.find(cid);
   587→                if (fc != flag_corrections.end()) { hs = fc->second & 1; ha = fc->second & 2; hi = fc->second & 4; }
   588→                uint32_t ns = (hs && si < s_offs.size()) ? s_offs[si++] : no_data_v;
   589→                uint32_t na = (ha && ai < a_offs.size()) ? a_offs[ai++] : no_data_v;
   590→                uint32_t ni = (hi && ii < i_offs.size()) ? i_offs[ii++] : no_data_v;
   591→                fwrite(&cid, 8, 1, fg); fwrite(&ns, 4, 1, fg); fwrite(&na, 4, 1, fg); fwrite(&ni, 4, 1, fg);
   592→            }
   593→            fclose(fg);
   594→        }
   595→        geo = RebuiltGeo{}; // free
   596→        malloc_trim(0);
   597→        log_phase("  Geo entries + corrections", t_entry);
   598→
   599→        // Admin: small, use existing rebuild + corrections
   600→        {
   601→            uint32_t no_data = 0xFFFFFFFF;
   602→            std::unordered_map<uint32_t,uint32_t> ad_rm;
   603→            auto ait = id_remaps.find((uint32_t)PatchFileId::ADMIN_POLYGONS);
   604→            if (ait != id_remaps.end()) {
   605→                for (uint32_t i = 0; i < ait->second.size(); i++)
   606→                    if (ait->second[i] != 0xFFFFFFFF) ad_rm[i] = ait->second[i];
   607→            }
   608→            auto old_ac = read_file(cur_dir + "/admin_cells.bin");
   609→            auto old_adme = read_file(cur_dir + "/admin_entries.bin");

                }
                return offsets;
            };
            auto s_offs = scan_offsets(out_dir + "/street_entries.bin");
            auto a_offs = scan_offsets(out_dir + "/addr_entries.bin");
            auto i_offs = scan_offsets(out_dir + "/interp_entries.bin");

            uint32_t no_data_v = 0xFFFFFFFF;
            size_t si = 0, ai = 0, ii = 0;
            for (size_t c = 0; c < n; c++) {
                uint64_t cid; memcpy(&cid, geo.geo_cells_data.data() + c*20, 8);
                uint32_t old_s, old_a, old_i;
                memcpy(&old_s, geo.geo_cells_data.data()+c*20+8, 4);
                memcpy(&old_a, geo.geo_cells_data.data()+c*20+12, 4);
                memcpy(&old_i, geo.geo_cells_data.data()+c*20+16, 4);
                bool hs = (old_s != no_data_v), ha = (old_a != no_data_v), hi = (old_i != no_data_v);
                auto fc = flag_corrections.find(cid);
                if (fc != flag_corrections.end()) { hs = fc->second & 1; ha = fc->second & 2; hi = fc->second & 4; }
                uint32_t ns = (hs && si < s_offs.size()) ? s_offs[si++] : no_data_v;
                uint32_t na = (ha && ai < a_offs.size()) ? a_offs[ai++] : no_data_v;
                uint32_t ni = (hi && ii < i_offs.size()) ? i_offs[ii++] : no_data_v;
                fwrite(&cid, 8, 1, fg); fwrite(&ns, 4, 1, fg); fwrite(&na, 4, 1, fg); fwrite(&ni, 4, 1, fg);
            }
            fclose(fg);
        }
        geo = RebuiltGeo{}; // free
        malloc_trim(0);
        log_phase("  Geo entries + corrections", t_entry);

        // Admin: small, use existing rebuild + corrections
        {
            uint32_t no_data = 0xFFFFFFFF;
            std::unordered_map<uint32_t,uint32_t> ad_rm;
            auto ait = id_remaps.find((uint32_t)PatchFileId::ADMIN_POLYGONS);
            if (ait != id_remaps.end()) {
                for (uint32_t i = 0; i < ait->second.size(); i++)
                    if (ait->second[i] != 0xFFFFFFFF) ad_rm[i] = ait->second[i];
            }
            auto old_ac = read_file(cur_dir + "/admin_cells.bin");
            auto old_adme = read_file(cur_dir + "/admin_entries.bin");


I need to remove lines 495-597 (the old correction + rebuild code) and keep the admin section:


Edit

        // Apply corrections sequentially (one file at a time to limit memory)
        // Build correction maps
        auto apply_corrections = [&](uint32_t fid, const std::string& fname,
                                      std::vector<char>& derived_entries,
                                      const std::vector<char>& derived_cells, size_t cell_stride,
                                      size_t off_pos) {
            auto ecit = entry_corrections.find(fid);
            if (ecit == entry_corrections.end() || ecit->second.empty()) {
                write_file(out_dir + "/" + fname, derived_entries);
                return;
            }
            std::unordered_map<uint64_t, const std::vector<uint32_t>*> cm;
            for (auto& c : ecit->second) cm[c.cell_id] = &c.ids;

            // Walk cells, apply corrections, write output via FILE*
            size_t n = derived_cells.size() / cell_stride;
            FILE* fe = fopen((out_dir + "/" + fname).c_str(), "wb");
            FILE* fc_out = nullptr;
            std::vector<char> new_cells;
            uint32_t no_data_v = 0xFFFFFFFF;
            uint32_t fe_pos = 0;

            for (size_t i = 0; i < n; i++) {
                uint64_t cid; memcpy(&cid, derived_cells.data() + i * cell_stride, 8);
                uint32_t off; memcpy(&off, derived_cells.data() + i * cell_stride + off_pos, 4);

                auto cit = cm.find(cid);
                if (cit != cm.end()) {
                    // Use correction data
                    uint32_t new_off = cit->second->empty() ? no_data_v : fe_pos;
                    if (!cit->second->empty()) {
                        uint16_t c = cit->second->size();
                        fwrite(&c, 2, 1, fe);
                        fwrite(cit->second->data(), 4, c, fe);
                        fe_pos += 2 + c * 4;
                    }
                    // Update offset in cells data (in memory)
                    memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
                } else if (off != no_data_v) {
                    // Copy original entry data
                    if (off + 2 <= derived_entries.size()) {
                        uint16_t c; memcpy(&c, derived_entries.data() + off, 2);
                        uint32_t new_off = fe_pos;
                        fwrite(derived_entries.data() + off, 1, 2 + c * 4, fe);
                        fe_pos += 2 + c * 4;
                        memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
                    }
                }
            }
            fclose(fe);
            std::cerr << "  " << fname << ": " << cm.size() << " corrections applied" << std::endl;
        };

        // Apply corrections for each geo entry type
        apply_corrections((uint32_t)PatchFileId::STREET_ENTRIES, "street_entries.bin",
                         geo.street_entries_data, geo.geo_cells_data, 20, 8);
        apply_corrections((uint32_t)PatchFileId::ADDR_ENTRIES, "addr_entries.bin",
                         geo.addr_entries_data, geo.geo_cells_data, 20, 12);
        apply_corrections((uint32_t)PatchFileId::INTERP_ENTRIES, "interp_entries.bin",
                         geo.interp_entries_data, geo.geo_cells_data, 20, 16);

        // Write the corrected geo_cells (offsets updated in-place by apply_corrections)
        // Rebuild offsets using flag corrections
        {
            size_t n = geo.geo_cells_data.size() / 20;
            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");
            // Scan corrected entry files to build offset arrays
            auto scan_offsets = [](const std::string& path) -> std::vector<uint32_t> {
                auto data = read_file(path);
                std::vector<uint32_t> offsets;
                uint32_t pos = 0;
                while (pos + 2 <= data.size()) {
                    offsets.push_back(pos);
                    uint16_t c; memcpy(&c, data.data() + pos, 2);
                    pos += 2 + c * 4;
                }
                return offsets;
            };
            auto s_offs = scan_offsets(out_dir + "/street_entries.bin");
            auto a_offs = scan_offsets(out_dir + "/addr_entries.bin");
            auto i_offs = scan_offsets(out_dir + "/interp_entries.bin");

            uint32_t no_data_v = 0xFFFFFFFF;
            size_t si = 0, ai = 0, ii = 0;
            for (size_t c = 0; c < n; c++) {
                uint64_t cid; memcpy(&cid, geo.geo_cells_data.data() + c*20, 8);
                uint32_t old_s, old_a, old_i;
                memcpy(&old_s, geo.geo_cells_data.data()+c*20+8, 4);
                memcpy(&old_a, geo.geo_cells_data.data()+c*20+12, 4);
                memcpy(&old_i, geo.geo_cells_data.data()+c*20+16, 4);
                bool hs = (old_s != no_data_v), ha = (old_a != no_data_v), hi = (old_i != no_data_v);
                auto fc = flag_corrections.find(cid);
                if (fc != flag_corrections.end()) { hs = fc->second & 1; ha = fc->second & 2; hi = fc->second & 4; }
                uint32_t ns = (hs && si < s_offs.size()) ? s_offs[si++] : no_data_v;
                uint32_t na = (ha && ai < a_offs.size()) ? a_offs[ai++] : no_data_v;
                uint32_t ni = (hi && ii < i_offs.size()) ? i_offs[ii++] : no_data_v;
                fwrite(&cid, 8, 1, fg); fwrite(&ns, 4, 1, fg); fwrite(&na, 4, 1, fg); fwrite(&ni, 4, 1, fg);
            }
            fclose(fg);
        }
        geo = RebuiltGeo{}; // free
        malloc_trim(0);
        log_phase("  Geo entries + corrections", t_entry);

        // Admin: small, use existing rebuild + corrections

        // Free old geo data and offset vectors
        { std::vector<char>().swap(old_geo_data); }
        { std::vector<uint32_t>().swap(se_offsets); std::vector<uint32_t>().swap(ae_offsets);
          std::vector<uint32_t>().swap(ie_offsets); }
        malloc_trim(0);
        log_phase("  Geo entries complete", t_entry);

        // Admin: small, use existing rebuild + corrections


        // Free old geo data and offset vectors
        { std::vector<char>().swap(old_geo_data); }
        { std::vector<uint32_t>().swap(se_offsets); std::vector<uint32_t>().swap(ae_offsets);
          std::vector<uint32_t>().swap(ie_offsets); }
        malloc_trim(0);
        log_phase("  Geo entries complete", t_entry);

        // Admin: small, use existing rebuild + corrections

// geocoder-patch v4: Low-memory streaming patch application.
//
// Key design: mmap old files (zero copy), stream output via fwrite (never accumulate),
// process entry pipeline cell-by-cell. Target: <1 GiB peak RSS for planet.
//
// Usage: geocoder-patch <current-dir> <patch-file> -o <output-dir>

#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fcntl.h>
#include <iomanip>
#include <iostream>
#include <malloc.h>
#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unordered_map>
#include <unordered_set>
#include <unistd.h>
#include <vector>

#include "patch_format.h"

enum MergeOp : uint8_t { OP_MATCH_RUN = 0, OP_INSERT_RUN = 1, OP_DELETE_RUN = 2 };

// --- Helpers ---
static double now_ms() {
    struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6;
}
static size_t get_rss_mb() {
    FILE* f = fopen("/proc/self/statm", "r");
    if (!f) return 0;
    size_t dummy, rss; fscanf(f, "%zu %zu", &dummy, &rss); fclose(f);
    return rss * 4096 / (1024*1024);
}
static void log_phase(const char* label, double start) {
    std::cerr << "  [" << std::fixed << std::setprecision(1) << (now_ms() - start) / 1000.0
              << "s, " << get_rss_mb() << " MiB] " << label << std::endl;
}

// mmap a file read-only. Returns {pointer, size}. Caller must munmap.
struct MappedFile { const char* data; size_t size; };
static MappedFile mmap_file(const std::string& path) {
    int fd = open(path.c_str(), O_RDONLY);
    if (fd < 0) return {nullptr, 0};
    struct stat st; fstat(fd, &st);
    size_t sz = st.st_size;
    if (sz == 0) { close(fd); return {nullptr, 0}; }
    void* p = mmap(nullptr, sz, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    if (p == MAP_FAILED) return {nullptr, 0};
    return {static_cast<const char*>(p), sz};
}
static void unmap_file(MappedFile& f) {
    if (f.data) { munmap(const_cast<char*>(f.data), f.size); f.data = nullptr; f.size = 0; }
}

// Detect stride from file size
static size_t detect_stride(const std::string& path, std::initializer_list<size_t> candidates) {
    struct stat st; if (stat(path.c_str(), &st) != 0) return *candidates.begin();
    for (size_t s : candidates) if (st.st_size % s == 0 && st.st_size > 0) return s;
    return *candidates.begin();
}

int main(int argc, char* argv[]) {
    if (argc < 5 || std::string(argv[3]) != "-o") {
        std::cerr << "Usage: geocoder-patch <current-dir> <patch-file> -o <output-dir>" << std::endl;
        return 1;
    }
    std::string cur_dir = argv[1], patch_path = argv[2], out_dir = argv[4];
    ensure_dir(out_dir);
    std::string tmpdir = "/tmp/geocoder-patch-" + std::to_string(getpid());
    ensure_dir(tmpdir);
    double t_start = now_ms();

    // --- Phase 1: Decompress + mmap patch ---
    std::string raw_path = tmpdir + "/patch.raw";
    {
        std::string cmd = "zstd -d '" + patch_path + "' -o '" + raw_path + "' -f --quiet 2>/dev/null";
        if (system(cmd.c_str()) != 0) { std::cerr << "Failed to decompress" << std::endl; return 1; }
    }
    MappedFile patch_map = mmap_file(raw_path);
    if (!patch_map.data) { std::cerr << "Failed to mmap patch" << std::endl; return 1; }
    remove(raw_path.c_str());
    const char* P = patch_map.data;
    size_t patch_size = patch_map.size;
    std::cerr << "Patch: " << patch_size << " bytes" << std::endl;
    log_phase("Decompress", t_start);

    size_t pos = 0;
    auto ru32 = [&]() -> uint32_t { uint32_t v; memcpy(&v, P+pos, 4); pos += 4; return v; };
    auto ru64 = [&]() -> uint64_t { uint64_t v; memcpy(&v, P+pos, 8); pos += 8; return v; };

    // Header
    if (memcmp(P, GCPATCH_MAGIC, 8) != 0) { std::cerr << "Bad magic" << std::endl; return 1; }
    pos = 8;
    uint32_t ver = ru32(); if (ver != 2) { std::cerr << "Bad version" << std::endl; return 1; }
    ru32(); // flags

    // --- Phase 2: String rebuild ---
    std::unordered_map<uint32_t, uint32_t> str_remap;
    {
        uint32_t marker = ru32();
        if (marker == 0xFFFFFFF7) {
            uint32_t n_added = ru32(), n_deleted = ru32();
            std::vector<std::string> added;
            for (uint32_t i = 0; i < n_added; i++) { const char* s = P+pos; added.push_back(s); pos += strlen(s)+1; }
            std::vector<uint32_t> del_idx(n_deleted);
            for (uint32_t i = 0; i < n_deleted; i++) del_idx[i] = ru32();
            std::unordered_set<uint32_t> del_set(del_idx.begin(), del_idx.end());

            MappedFile old_pool = mmap_file(cur_dir + "/strings.bin");
            std::vector<std::pair<uint32_t, std::string>> old_strs;
            size_t sp = 0;
            while (sp < old_pool.size) {
                old_strs.push_back({(uint32_t)sp, std::string(old_pool.data + sp)});
                sp += strlen(old_pool.data + sp) + 1;
            }
            std::vector<std::string> merged;
            for (size_t i = 0; i < old_strs.size(); i++)
                if (!del_set.count(i)) merged.push_back(old_strs[i].second);
            for (auto& s : added) merged.push_back(s);
            std::sort(merged.begin(), merged.end());

            // Write new pool and build remap
            FILE* fp = fopen((out_dir + "/strings.bin").c_str(), "wb");
            std::unordered_map<std::string, uint32_t> new_offs;
            uint32_t wpos = 0;
            for (auto& s : merged) {
                new_offs[s] = wpos;
                fwrite(s.c_str(), 1, s.size() + 1, fp);
                wpos += s.size() + 1;
            }
            fclose(fp);
            for (auto& [old_off, s] : old_strs) {
                auto it = new_offs.find(s);
                if (it != new_offs.end() && it->second != old_off) str_remap[old_off] = it->second;
            }
            unmap_file(old_pool);
            std::cerr << "  Strings: +" << n_added << " -" << n_deleted << " → " << merged.size()
                      << ", " << str_remap.size() << " remapped" << std::endl;
            marker = ru32();
        }
        if (marker == 0xFFFFFFFE) { uint32_t c = ru32(); for (uint32_t i = 0; i < c; i++) { uint32_t a = ru32(), b = ru32(); str_remap[a] = b; } }
        else pos -= 4;
    }
    log_phase("Strings", t_start);

    // Detect strides
    size_t way_stride = detect_stride(cur_dir + "/street_ways.bin", {12, 9});
    size_t interp_stride = detect_stride(cur_dir + "/interp_ways.bin", {24, 20, 18});
    size_t admin_stride = detect_stride(cur_dir + "/admin_polygons.bin", {24, 20, 19});

    // String remap applier (works on mutable copy)
    auto apply_str_remap = [&](std::vector<char>& data, uint32_t fid, size_t stride) {
        if (str_remap.empty()) return;
        std::vector<size_t> offs;
        if (fid == (uint32_t)PatchFileId::ADDR_POINTS) offs = {8, 12};
        else if (fid == (uint32_t)PatchFileId::STREET_WAYS) offs = {(stride == 12) ? 8ul : 5ul};
        else if (fid == (uint32_t)PatchFileId::INTERP_WAYS) offs = {(stride >= 20) ? 8ul : 5ul};
        else if (fid == (uint32_t)PatchFileId::ADMIN_POLYGONS) offs = {8};
        else return;
        for (size_t i = 0; i + stride <= data.size(); i += stride)
            for (size_t o : offs) { uint32_t v; memcpy(&v, data.data()+i+o, 4); auto it = str_remap.find(v); if (it != str_remap.end()) memcpy(data.data()+i+o, &it->second, 4); }
    };

    // --- Phase 3: Merge replays (streaming output) ---
    // ID remaps: file_id → vector<uint32_t> where remap[old_idx] = new_idx
    std::unordered_map<uint32_t, std::vector<uint32_t>> id_remaps;
    std::vector<uint64_t> geo_added, geo_removed, admin_added, admin_removed;
    std::unordered_map<uint64_t, uint8_t> flag_corrections;
    struct CellCorr { uint64_t cell_id; std::vector<uint32_t> ids; };
    std::unordered_map<uint32_t, std::vector<CellCorr>> entry_corrections;

    while (pos < patch_size) {
        uint32_t file_id = ru32();
        if (file_id == 0xFFFFFFFF) break;

        // --- Metadata sections ---
        if (file_id == 0xFFFFFFF7) {
            // String diff in main loop — process it (may be the only occurrence)
            uint32_t n_added = ru32(), n_deleted = ru32();
            std::vector<std::string> added;
            for (uint32_t i = 0; i < n_added; i++) { const char* s = P+pos; added.push_back(s); pos += strlen(s)+1; }
            std::vector<uint32_t> del_idx(n_deleted);
            for (uint32_t i = 0; i < n_deleted; i++) del_idx[i] = ru32();
            std::unordered_set<uint32_t> del_set(del_idx.begin(), del_idx.end());
            MappedFile old_pool = mmap_file(cur_dir + "/strings.bin");
            std::vector<std::pair<uint32_t, std::string>> old_strs;
            size_t sp = 0;
            while (sp < old_pool.size) { old_strs.push_back({(uint32_t)sp, std::string(old_pool.data+sp)}); sp += strlen(old_pool.data+sp)+1; }
            std::vector<std::string> merged;
            for (size_t i = 0; i < old_strs.size(); i++) if (!del_set.count(i)) merged.push_back(old_strs[i].second);
            for (auto& s : added) merged.push_back(s);
            std::sort(merged.begin(), merged.end());
            FILE* fp = fopen((out_dir + "/strings.bin").c_str(), "wb");
            std::unordered_map<std::string, uint32_t> new_offs;
            uint32_t wpos = 0;
            for (auto& s : merged) { new_offs[s] = wpos; fwrite(s.c_str(), 1, s.size()+1, fp); wpos += s.size()+1; }
            fclose(fp);
            for (auto& [old_off, s] : old_strs) { auto it = new_offs.find(s); if (it != new_offs.end() && it->second != old_off) str_remap[old_off] = it->second; }
            unmap_file(old_pool);
            std::cerr << "  Strings (loop): +" << n_added << " -" << n_deleted << " → " << merged.size() << std::endl;
            continue;
        }
        if (file_id == CELL_CHANGES_GEO_MARKER) {
            uint32_t na = ru32(), nr = ru32();
            geo_added.resize(na); geo_removed.resize(nr);
            for (uint32_t i = 0; i < na; i++) { memcpy(&geo_added[i], P+pos, 8); pos += 8; }
            for (uint32_t i = 0; i < nr; i++) { memcpy(&geo_removed[i], P+pos, 8); pos += 8; }
            std::cerr << "  Geo cells: +" << na << " -" << nr << std::endl;
            continue;
        }
        if (file_id == CELL_CHANGES_ADMIN_MARKER) {
            uint32_t na = ru32(), nr = ru32();
            admin_added.resize(na); admin_removed.resize(nr);
            for (uint32_t i = 0; i < na; i++) { memcpy(&admin_added[i], P+pos, 8); pos += 8; }
            for (uint32_t i = 0; i < nr; i++) { memcpy(&admin_removed[i], P+pos, 8); pos += 8; }
            std::cerr << "  Admin cells: +" << na << " -" << nr << std::endl;
            continue;
        }
        if (file_id == CELL_FLAGS_MARKER) {
            uint32_t c = ru32();
            for (uint32_t i = 0; i < c; i++) { uint64_t cid; memcpy(&cid, P+pos, 8); pos += 8; flag_corrections[cid] = P[pos++]; }
            std::cerr << "  Flag corrections: " << c << std::endl;
            continue;
        }
        if (file_id == SECONDARY_REMAP_MARKER) {
            uint32_t nf = ru32();
            for (uint32_t f = 0; f < nf; f++) {
                uint32_t fid = ru32(), np = ru32();
                auto& rm = id_remaps[fid];
                for (uint32_t i = 0; i < np; i++) { uint32_t o = ru32(), n = ru32(); if (o < rm.size()) rm[o] = n; }
                std::cerr << "  Secondary remap " << fid << ": " << np << " pairs" << std::endl;
            }
            continue;
        }
        if (file_id == ENTRY_CORRECTION_MARKER) {
            uint32_t fid = ru32(), c = ru32();
            auto& list = entry_corrections[fid];
            for (uint32_t i = 0; i < c; i++) {
                uint64_t cid; memcpy(&cid, P+pos, 8); pos += 8;
                uint16_t ec; memcpy(&ec, P+pos, 2); pos += 2;
                std::vector<uint32_t> ids(ec);
                if (ec > 0) { memcpy(ids.data(), P+pos, ec*4); pos += ec*4; }
                list.push_back({cid, std::move(ids)});
            }
            std::cerr << "  Entry corrections " << fid << ": " << c << std::endl;
            continue;
        }

        // --- Merge sequence replay (streaming) ---
        uint32_t stride = ru32();
        uint64_t old_size = ru64(), new_size = ru64();
        if (file_id >= (uint32_t)PatchFileId::COUNT) { std::cerr << "Unknown file " << file_id << std::endl; break; }
        const char* fname = patch_file_names[file_id];

        if (stride == 0) {
            // Full replacement — write directly from patch mmap
            uint32_t nf = ru32(); (void)nf; uint64_t ds = ru64();
            FILE* fp = fopen((out_dir + "/" + fname).c_str(), "wb");
            fwrite(P+pos, 1, ds, fp); fclose(fp);
            pos += ds;
            std::cerr << "  " << fname << ": full replace " << ds << " bytes" << std::endl;
            continue;
        }
        if (stride == 0xFE) { uint32_t nf = ru32(); (void)nf; uint64_t ds = ru64(); pos += ds; continue; }

        size_t actual_stride = stride;
        if (file_id == (uint32_t)PatchFileId::STREET_WAYS) actual_stride = way_stride;
        else if (file_id == (uint32_t)PatchFileId::INTERP_WAYS) actual_stride = interp_stride;
        else if (file_id == (uint32_t)PatchFileId::ADMIN_POLYGONS) actual_stride = admin_stride;

        // Load old file into mutable buffer (need to apply remap + fixups)
        // For large files (nodes/vertices), this is the peak memory per file.
        // Files are processed sequentially, so only one is in memory at a time.
        auto old_data = read_file(cur_dir + "/" + std::string(fname));

        // Zero padding
        if (file_id == (uint32_t)PatchFileId::ADMIN_POLYGONS && actual_stride == 24)
            for (size_t i = 0; i + actual_stride <= old_data.size(); i += actual_stride) memset(old_data.data()+i+13, 0, 3);
        if (file_id == (uint32_t)PatchFileId::INTERP_WAYS && actual_stride == 24)
            for (size_t i = 0; i + actual_stride <= old_data.size(); i += actual_stride) { memset(old_data.data()+i+5, 0, 3); memset(old_data.data()+i+21, 0, 3); }

        apply_str_remap(old_data, file_id, actual_stride);

        // Apply fixups
        uint32_t n_fixups = ru32();
        if (n_fixups > 0) {
            uint32_t delta_size = ru32();
            uint32_t pi = 0, pv = 0; size_t fp = pos;
            for (uint32_t i = 0; i < n_fixups; i++) {
                uint32_t idx = pi + read_varint(P, fp);
                uint32_t val = pv + read_varint(P, fp);
                if ((size_t)idx * actual_stride + 4 <= old_data.size())
                    memcpy(old_data.data() + (size_t)idx * actual_stride, &val, 4);
                pi = idx; pv = val;
            }
            pos = fp;
        }

        // Replay merge sequence — stream output directly to file
        uint64_t seq_size = ru64();
        size_t seq_end = pos + seq_size;
        FILE* outf = fopen((out_dir + "/" + fname).c_str(), "wb");

        // Track ID remap (only for data files that need entry reconstruction)
        bool track = (file_id == (uint32_t)PatchFileId::ADDR_POINTS ||
                      file_id == (uint32_t)PatchFileId::STREET_WAYS ||
                      file_id == (uint32_t)PatchFileId::INTERP_WAYS ||
                      file_id == (uint32_t)PatchFileId::ADMIN_POLYGONS);
        std::vector<uint32_t> id_map;
        if (track) id_map.assign(old_data.size() / actual_stride, 0xFFFFFFFF);

        size_t old_rec = 0, new_rec = 0, old_bytes = 0, written = 0;
        while (pos < seq_end) {
            uint8_t op = P[pos++];
            uint32_t count; memcpy(&count, P+pos, 4); pos += 4;
            if (op == OP_MATCH_RUN) {
                size_t bytes = count * actual_stride;
                fwrite(old_data.data() + old_bytes, 1, bytes, outf);
                written += bytes;
                if (track) for (uint32_t k = 0; k < count; k++) if (old_rec+k < id_map.size()) id_map[old_rec+k] = new_rec+k;
                old_rec += count; new_rec += count; old_bytes += bytes;
            } else if (op == OP_INSERT_RUN) {
                size_t bytes = count * actual_stride;
                fwrite(P+pos, 1, bytes, outf);
                written += bytes; pos += bytes; new_rec += count;
            } else if (op == OP_DELETE_RUN) {
                old_rec += count; old_bytes += count * actual_stride;
            }
        }
        fclose(outf);

        if (track) {
            id_remaps[file_id] = std::move(id_map);
            size_t matched = 0;
            for (auto v : id_remaps[file_id]) if (v != 0xFFFFFFFF) matched++;
            std::cerr << "  " << fname << ": " << written << " bytes (mapped " << matched << "/" << id_remaps[file_id].size() << ")" << std::endl;
        } else {
            std::cerr << "  " << fname << ": " << written << " bytes" << std::endl;
        }

        // Free old data immediately — don't let glibc hold the pages
        { std::vector<char>().swap(old_data); }
        malloc_trim(0);
    }
    log_phase("Merge replays", t_start);

    // Free string remap (no longer needed)
    { std::unordered_map<uint32_t,uint32_t>().swap(str_remap); }
    malloc_trim(0);

    // --- Phase 4: Streaming entry pipeline ---
    // Walk old geo_cells, remap IDs, apply corrections, write output directly to files.
    {
        double t_entry = now_ms();
        std::cerr << "Entry pipeline (streaming, one type at a time)..." << std::endl;

        auto get_rm = [&](PatchFileId fid) -> const std::vector<uint32_t>& {
            static std::vector<uint32_t> empty;
            auto it = id_remaps.find((uint32_t)fid); return it != id_remaps.end() ? it->second : empty;
        };

        // Load old geo_cells
        auto old_geo_data = read_file(cur_dir + "/geo_cells.bin");
        size_t n_old_cells = old_geo_data.size() / 20;

        // Build derived cell list (old - removed + added, sorted)
        std::unordered_set<uint64_t> rm_set(geo_removed.begin(), geo_removed.end());
        struct CellRef { uint64_t cid; int32_t old_idx; };
        std::vector<CellRef> cells;
        cells.reserve(n_old_cells + geo_added.size());
        for (size_t i = 0; i < n_old_cells; i++) {
            uint64_t cid; memcpy(&cid, old_geo_data.data() + i * 20, 8);
            if (!rm_set.count(cid)) cells.push_back({cid, (int32_t)i});
        }
        { std::unordered_set<uint64_t>().swap(rm_set); } // free
        for (uint64_t cid : geo_added) cells.push_back({cid, -1});
        std::sort(cells.begin(), cells.end(), [](const CellRef& a, const CellRef& b) { return a.cid < b.cid; });
        log_phase("  Cell list built", t_entry);

        // Process each entry type SEPARATELY to keep memory low
        // For each type: load old entries → walk cells → remap + correct → write via fwrite
        // geo_cells offsets are recorded per-type and assembled at the end

        // Per-cell output offsets for geo_cells (0xFFFFFFFF = no data)
        std::vector<uint32_t> se_offsets(cells.size(), 0xFFFFFFFF);
        std::vector<uint32_t> ae_offsets(cells.size(), 0xFFFFFFFF);
        std::vector<uint32_t> ie_offsets(cells.size(), 0xFFFFFFFF);

        auto process_entry_type = [&](PatchFileId data_fid, PatchFileId entry_fid,
                                       const std::string& entry_fname, size_t geo_off_pos,
                                       std::vector<uint32_t>& out_offsets) {
            auto& rm = get_rm(data_fid);
            auto old_entries = read_file(cur_dir + "/" + entry_fname);

            // Build correction map for this type
            std::unordered_map<uint64_t, const std::vector<uint32_t>*> corr_map;
            auto ecit = entry_corrections.find((uint32_t)entry_fid);
            if (ecit != entry_corrections.end())
                for (auto& c : ecit->second) corr_map[c.cell_id] = &c.ids;

            FILE* outf = fopen((out_dir + "/" + entry_fname).c_str(), "wb");
            std::vector<uint32_t> ids; // reusable per-cell buffer
            ids.reserve(256);

            for (size_t ci = 0; ci < cells.size(); ci++) {
                uint64_t cid = cells[ci].cid;
                int32_t oi = cells[ci].old_idx;

                // Determine if this cell has entries for this type
                bool has_entry = false;
                if (oi >= 0) {
                    uint32_t off; memcpy(&off, old_geo_data.data() + oi * 20 + geo_off_pos, 4);
                    has_entry = (off != 0xFFFFFFFF);
                }
                // Flag corrections can change has_entry
                auto fc = flag_corrections.find(cid);
                if (fc != flag_corrections.end()) {
                    uint8_t bit = (geo_off_pos == 8) ? 1 : (geo_off_pos == 12) ? 2 : 4;
                    has_entry = (fc->second & bit) != 0;
                }
                // Corrections can introduce entries
                if (corr_map.count(cid)) has_entry = true;

                if (!has_entry) continue;

                // Check for correction
                auto cit = corr_map.find(cid);
                if (cit != corr_map.end()) {
                    // Write correction data directly
                    if (!cit->second->empty()) {
                        out_offsets[ci] = (uint32_t)ftell(outf);
                        uint16_t count = cit->second->size();
                        fwrite(&count, 2, 1, outf);
                        fwrite(cit->second->data(), 4, count, outf);
                    }
                    continue;
                }

                // No correction: read old entries, remap, write
                if (oi < 0) continue; // added cell, no old data
                uint32_t off; memcpy(&off, old_geo_data.data() + oi * 20 + geo_off_pos, 4);
                if (off == 0xFFFFFFFF || off + 2 > old_entries.size()) continue;
                uint16_t count; memcpy(&count, old_entries.data() + off, 2);
                if (count == 0 || off + 2 + count * 4 > old_entries.size()) continue;

                ids.resize(count);
                memcpy(ids.data(), old_entries.data() + off + 2, count * 4);
                // Remap
                for (auto& id : ids)
                    if (id < rm.size() && rm[id] != 0xFFFFFFFF) id = rm[id];
                std::sort(ids.begin(), ids.end());

                out_offsets[ci] = (uint32_t)ftell(outf);
                fwrite(&count, 2, 1, outf);
                fwrite(ids.data(), 4, count, outf);
            }
            fclose(outf);
        };

        process_entry_type(PatchFileId::STREET_WAYS, PatchFileId::STREET_ENTRIES,
                          "street_entries.bin", 8, se_offsets);
        log_phase("  street_entries", t_entry);
        malloc_trim(0);

        process_entry_type(PatchFileId::ADDR_POINTS, PatchFileId::ADDR_ENTRIES,
                          "addr_entries.bin", 12, ae_offsets);
        log_phase("  addr_entries", t_entry);
        malloc_trim(0);

        process_entry_type(PatchFileId::INTERP_WAYS, PatchFileId::INTERP_ENTRIES,
                          "interp_entries.bin", 16, ie_offsets);
        log_phase("  interp_entries", t_entry);

        // Write geo_cells using the recorded offsets
        {
            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");
            for (size_t ci = 0; ci < cells.size(); ci++) {
                fwrite(&cells[ci].cid, 8, 1, fg);
                fwrite(&se_offsets[ci], 4, 1, fg);
                fwrite(&ae_offsets[ci], 4, 1, fg);
                fwrite(&ie_offsets[ci], 4, 1, fg);
            }
            fclose(fg);
        }
        log_phase("  geo_cells written", t_entry);

        // Apply corrections sequentially (one file at a time to limit memory)
        // Build correction maps
        auto apply_corrections = [&](uint32_t fid, const std::string& fname,
                                      std::vector<char>& derived_entries,
                                      const std::vector<char>& derived_cells, size_t cell_stride,
                                      size_t off_pos) {
            auto ecit = entry_corrections.find(fid);
            if (ecit == entry_corrections.end() || ecit->second.empty()) {
                write_file(out_dir + "/" + fname, derived_entries);
                return;
            }
            std::unordered_map<uint64_t, const std::vector<uint32_t>*> cm;
            for (auto& c : ecit->second) cm[c.cell_id] = &c.ids;

            // Walk cells, apply corrections, write output via FILE*
            size_t n = derived_cells.size() / cell_stride;
            FILE* fe = fopen((out_dir + "/" + fname).c_str(), "wb");
            FILE* fc_out = nullptr;
            std::vector<char> new_cells;
            uint32_t no_data_v = 0xFFFFFFFF;
            uint32_t fe_pos = 0;

            for (size_t i = 0; i < n; i++) {
                uint64_t cid; memcpy(&cid, derived_cells.data() + i * cell_stride, 8);
                uint32_t off; memcpy(&off, derived_cells.data() + i * cell_stride + off_pos, 4);

                auto cit = cm.find(cid);
                if (cit != cm.end()) {
                    // Use correction data
                    uint32_t new_off = cit->second->empty() ? no_data_v : fe_pos;
                    if (!cit->second->empty()) {
                        uint16_t c = cit->second->size();
                        fwrite(&c, 2, 1, fe);
                        fwrite(cit->second->data(), 4, c, fe);
                        fe_pos += 2 + c * 4;
                    }
                    // Update offset in cells data (in memory)
                    memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
                } else if (off != no_data_v) {
                    // Copy original entry data
                    if (off + 2 <= derived_entries.size()) {
                        uint16_t c; memcpy(&c, derived_entries.data() + off, 2);
                        uint32_t new_off = fe_pos;
                        fwrite(derived_entries.data() + off, 1, 2 + c * 4, fe);
                        fe_pos += 2 + c * 4;
                        memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);
                    }
                }
            }
            fclose(fe);
            std::cerr << "  " << fname << ": " << cm.size() << " corrections applied" << std::endl;
        };

        // Apply corrections for each geo entry type
        apply_corrections((uint32_t)PatchFileId::STREET_ENTRIES, "street_entries.bin",
                         geo.street_entries_data, geo.geo_cells_data, 20, 8);
        apply_corrections((uint32_t)PatchFileId::ADDR_ENTRIES, "addr_entries.bin",
                         geo.addr_entries_data, geo.geo_cells_data, 20, 12);
        apply_corrections((uint32_t)PatchFileId::INTERP_ENTRIES, "interp_entries.bin",
                         geo.interp_entries_data, geo.geo_cells_data, 20, 16);

        // Write the corrected geo_cells (offsets updated in-place by apply_corrections)
        // Rebuild offsets using flag corrections
        {
            size_t n = geo.geo_cells_data.size() / 20;
            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");
            // Scan corrected entry files to build offset arrays
            auto scan_offsets = [](const std::string& path) -> std::vector<uint32_t> {
                auto data = read_file(path);
                std::vector<uint32_t> offsets;
                uint32_t pos = 0;
                while (pos + 2 <= data.size()) {
                    offsets.push_back(pos);
                    uint16_t c; memcpy(&c, data.data() + pos, 2);
                    pos += 2 + c * 4;
                }
                return offsets;
            };
            auto s_offs = scan_offsets(out_dir + "/street_entries.bin");
            auto a_offs = scan_offsets(out_dir + "/addr_entries.bin");
            auto i_offs = scan_offsets(out_dir + "/interp_entries.bin");

            uint32_t no_data_v = 0xFFFFFFFF;
            size_t si = 0, ai = 0, ii = 0;
            for (size_t c = 0; c < n; c++) {
                uint64_t cid; memcpy(&cid, geo.geo_cells_data.data() + c*20, 8);
                uint32_t old_s, old_a, old_i;
                memcpy(&old_s, geo.geo_cells_data.data()+c*20+8, 4);
                memcpy(&old_a, geo.geo_cells_data.data()+c*20+12, 4);
                memcpy(&old_i, geo.geo_cells_data.data()+c*20+16, 4);
                bool hs = (old_s != no_data_v), ha = (old_a != no_data_v), hi = (old_i != no_data_v);
                auto fc = flag_corrections.find(cid);
                if (fc != flag_corrections.end()) { hs = fc->second & 1; ha = fc->second & 2; hi = fc->second & 4; }
                uint32_t ns = (hs && si < s_offs.size()) ? s_offs[si++] : no_data_v;
                uint32_t na = (ha && ai < a_offs.size()) ? a_offs[ai++] : no_data_v;
                uint32_t ni = (hi && ii < i_offs.size()) ? i_offs[ii++] : no_data_v;
                fwrite(&cid, 8, 1, fg); fwrite(&ns, 4, 1, fg); fwrite(&na, 4, 1, fg); fwrite(&ni, 4, 1, fg);
            }
            fclose(fg);
        }
        geo = RebuiltGeo{}; // free
        malloc_trim(0);
        log_phase("  Geo entries + corrections", t_entry);

        // Admin: small, use existing rebuild + corrections
        {
            uint32_t no_data = 0xFFFFFFFF;
            std::unordered_map<uint32_t,uint32_t> ad_rm;
            auto ait = id_remaps.find((uint32_t)PatchFileId::ADMIN_POLYGONS);
            if (ait != id_remaps.end()) {
                for (uint32_t i = 0; i < ait->second.size(); i++)
                    if (ait->second[i] != 0xFFFFFFFF) ad_rm[i] = ait->second[i];
            }
            auto old_ac = read_file(cur_dir + "/admin_cells.bin");
            auto old_adme = read_file(cur_dir + "/admin_entries.bin");
            auto admin = rebuild_admin_from_remap(old_ac, old_adme, ad_rm, admin_added, admin_removed);

            auto ecit = entry_corrections.find((uint32_t)PatchFileId::ADMIN_ENTRIES);
            if (ecit != entry_corrections.end()) {
                std::unordered_map<uint64_t, const std::vector<uint32_t>*> ac_corr;
                for (auto& c : ecit->second) ac_corr[c.cell_id] = &c.ids;
                size_t n = admin.admin_cells_data.size() / 12;
                FILE* fac = fopen((out_dir + "/admin_cells.bin").c_str(), "wb");
                FILE* fae = fopen((out_dir + "/admin_entries.bin").c_str(), "wb");
                uint32_t ae_wpos = 0;
                for (size_t i = 0; i < n; i++) {
                    uint64_t cid; memcpy(&cid, admin.admin_cells_data.data()+i*12, 8);
                    fwrite(&cid, 8, 1, fac);
                    auto cit = ac_corr.find(cid);
                    if (cit != ac_corr.end()) {
                        uint32_t off = cit->second->empty() ? no_data : ae_wpos;
                        fwrite(&off, 4, 1, fac);
                        if (!cit->second->empty()) {
                            uint16_t c = cit->second->size();
                            fwrite(&c, 2, 1, fae); fwrite(cit->second->data(), 4, c, fae);
                            ae_wpos += 2 + c*4;
                        }
                    } else {
                        uint32_t old_off; memcpy(&old_off, admin.admin_cells_data.data()+i*12+8, 4);
                        if (old_off != no_data && old_off+2 <= admin.admin_entries_data.size()) {
                            uint32_t new_off = ae_wpos; fwrite(&new_off, 4, 1, fac);
                            uint16_t c; memcpy(&c, admin.admin_entries_data.data()+old_off, 2);
                            fwrite(admin.admin_entries_data.data()+old_off, 1, 2+c*4, fae);
                            ae_wpos += 2+c*4;
                        } else {
                            fwrite(&no_data, 4, 1, fac);
                        }
                    }
                }
                fclose(fac); fclose(fae);
                std::cerr << "  Admin: " << n << " cells, " << ac_corr.size() << " corrections" << std::endl;
            } else {
                write_file(out_dir + "/admin_cells.bin", admin.admin_cells_data);
                write_file(out_dir + "/admin_entries.bin", admin.admin_entries_data);
            }
            log_phase("  Admin", t_entry);
        }
    }

    // Cleanup
    unmap_file(patch_map);
    { std::string cmd = "rm -rf '" + tmpdir + "'"; system(cmd.c_str()); }
    log_phase("Total", t_start);
    std::cerr << "Patch applied. Output in " << out_dir << std::endl;
    return 0;
}

-        // Apply corrections sequentially (one file at a time to limit memory)

-        auto apply_corrections = [&](uint32_t fid, const std::string& fname,

-                                      std::vector<char>& derived_entries,

-                                      const std::vector<char>& derived_cells, size_t cell_stride,

-                                      size_t off_pos) {

-            auto ecit = entry_corrections.find(fid);

-            if (ecit == entry_corrections.end() || ecit->second.empty()) {

-                write_file(out_dir + "/" + fname, derived_entries);

-            std::unordered_map<uint64_t, const std::vector<uint32_t>*> cm;

-            for (auto& c : ecit->second) cm[c.cell_id] = &c.ids;

-            // Walk cells, apply corrections, write output via FILE*

-            size_t n = derived_cells.size() / cell_stride;

-            FILE* fe = fopen((out_dir + "/" + fname).c_str(), "wb");

-            std::vector<char> new_cells;

-            uint32_t no_data_v = 0xFFFFFFFF;

-            for (size_t i = 0; i < n; i++) {

-                uint64_t cid; memcpy(&cid, derived_cells.data() + i * cell_stride, 8);

-                uint32_t off; memcpy(&off, derived_cells.data() + i * cell_stride + off_pos, 4);

-                auto cit = cm.find(cid);

-                    // Use correction data

-                    uint32_t new_off = cit->second->empty() ? no_data_v : fe_pos;

-                    if (!cit->second->empty()) {

-                        uint16_t c = cit->second->size();

-                        fwrite(cit->second->data(), 4, c, fe);

-                    // Update offset in cells data (in memory)

-                    memcpy(const_cast<char*>(derived_cells.data()) + i * cell_stride + off_pos, &new_off, 4);

-                } else if (off != no_data_v) {

-                    // Copy original entry data

-                    if (off + 2 <= derived_entries.size()) {

-                        uint16_t c; memcpy(&c, derived_entries.data() + off, 2);

-                        uint32_t new_off = fe_pos;

-                        fwrite(derived_entries.data() + off, 1, 2 + c * 4, fe);

-            std::cerr << "  " << fname << ": " << cm.size() << " corrections applied" << std::endl;

-        // Apply corrections for each geo entry type

-        apply_corrections((uint32_t)PatchFileId::STREET_ENTRIES, "street_entries.bin",

-                         geo.street_entries_data, geo.geo_cells_data, 20, 8);

-        apply_corrections((uint32_t)PatchFileId::ADDR_ENTRIES, "addr_entries.bin",

-                         geo.addr_entries_data, geo.geo_cells_data, 20, 12);

-        apply_corrections((uint32_t)PatchFileId::INTERP_ENTRIES, "interp_entries.bin",

-                         geo.interp_entries_data, geo.geo_cells_data, 20, 16);

-        // Write the corrected geo_cells (offsets updated in-place by apply_corrections)

-        // Rebuild offsets using flag corrections

-            size_t n = geo.geo_cells_data.size() / 20;

-            FILE* fg = fopen((out_dir + "/geo_cells.bin").c_str(), "wb");

-            // Scan corrected entry files to build offset arrays

-            auto scan_offsets = [](const std::string& path) -> std::vector<uint32_t> {

-                auto data = read_file(path);

-                std::vector<uint32_t> offsets;

-                while (pos + 2 <= data.size()) {

-                    offsets.push_back(pos);

-                    uint16_t c; memcpy(&c, data.data() + pos, 2);

-            auto s_offs = scan_offsets(out_dir + "/street_entries.bin");

-            auto a_offs = scan_offsets(out_dir + "/addr_entries.bin");

-            auto i_offs = scan_offsets(out_dir + "/interp_entries.bin");

-            uint32_t no_data_v = 0xFFFFFFFF;

-            size_t si = 0, ai = 0, ii = 0;

-            for (size_t c = 0; c < n; c++) {

-                uint64_t cid; memcpy(&cid, geo.geo_cells_data.data() + c*20, 8);

-                uint32_t old_s, old_a, old_i;

-                memcpy(&old_s, geo.geo_cells_data.data()+c*20+8, 4);

-                memcpy(&old_a, geo.geo_cells_data.data()+c*20+12, 4);

-                memcpy(&old_i, geo.geo_cells_data.data()+c*20+16, 4);

-                bool hs = (old_s != no_data_v), ha = (old_a != no_data_v), hi = (old_i != no_data_v);

-                auto fc = flag_corrections.find(cid);

-                if (fc != flag_corrections.end()) { hs = fc->second & 1; ha = fc->second & 2; hi = fc->second & 4; }

-                uint32_t ns = (hs && si < s_offs.size()) ? s_offs[si++] : no_data_v;

-                uint32_t na = (ha && ai < a_offs.size()) ? a_offs[ai++] : no_data_v;

-                uint32_t ni = (hi && ii < i_offs.size()) ? i_offs[ii++] : no_data_v;

-                fwrite(&cid, 8, 1, fg); fwrite(&ns, 4, 1, fg); fwrite(&na, 4, 1, fg); fwrite(&ni, 4, 1, fg);

+        // Free old geo data and offset vectors

+        { std::vector<char>().swap(old_geo_data); }

+        { std::vector<uint32_t>().swap(se_offsets); std::vector<uint32_t>().swap(ae_offsets);

+          std::vector<uint32_t>().swap(ie_offsets); }

-        log_phase("  Geo entries + corrections", t_entry);

+        log_phase("  Geo entries complete", t_entry);


Bash

make -j$(nproc) geocoder-patch 2>&1 | tail -5

Build streaming v2


make: *** No rule to make target 'geocoder-patch'.  Stop.


cd /root/traccar-geocoder/builder/build && make -j$(nproc) geocoder-patch 2>&1 | tail -5

Build from correct dir