If I absolutely had to do this, here’s basically how I’d approach things.
What I’d do is take the raw sample data and write it to a binary file. I wouldn’t worry about welding nearby vertices or smoothing collinear vertices or any of that other stuff you talked about, because with as fast as computers are today those optimizations simply do not matter. My file format would be a raw, complete sequence of floating point triples, optionally tagged with metadata (e.g. if it’s a resource).
A floating point triple is 12 bytes, so at a sample rate of 1 Hz it’ll take around a full day to generate 1 MB of data. This is an inconsequential amount of information. Your computer processes thousands of times more data per frame of a modern game. If you’re really not convinced, I would probably use some kind of differential coding to reduce the size of the data. Then I’d investigate reducing the floating point precision. I wouldn’t investigate smoothing or welding until after I’d already done those things, and again I’d only do them if I absolutely had to do them.
And again, this format would be binary. At these file sizes it really doesn’t matter, but this is my preference. Parsing text is one of the most expensive things you can do on a modern computer, and roughly ten thousand nines of the data your program processes will never be seen by a human, so I’d rather provide an external tool that can encode and decode this file for testing, rather than permanently cripple my throughput by forcing everything to go through text.
Then the next step is to read the file and build a polygonal chain in memory. It should easily fit, but there are ways around it if it doesn’t. What you’d do now is find pairs of line segments that intersect. (I assume such intersections can be determined easily - flatten it to 2D, maybe, then apply an O(nlogn) sweep line algorithm - you sort the endpoints along a certain axis, then perform a linear scan. It’s real fast.)
Then take the graph with closed loops, copy it*, and smooth the graph by deleting non-resource degree 2 vertices. Make sure to sum the edge weights (lengths) when you delete a vertex.
Now you have two things. A weighted but extremely simple graph connecting resource vertices and significant intersections, and a set of path fragments from the original data, which can be executed or reversed for motion planning between significant vertices but are not otherwise relevant for pathfinding.
For making this final data human readable, SQLite and a tool to decode binary blobs. Again, it’s for you. Nobody is ever going to edit this **** by hand.
The final problem is point location at run-time. If you can convince the user that they need to start the tool when they’re standing next to a POI, great. At arbitrary locations, you need a much more complete navmesh. *handwaves*
Or just extract the collision mesh from the game data, lol.