Generator Audit — mapv10 Visual Foundation (Wave 1 Agent C)
Scope: generator-side Rust only. Viewer not audited here.
config.rs (386 lines)
Public surface
pub struct GeneratorConfig— serializable world configpub struct RangeU32— min/max u32 pairpub struct RangeF64— min/max f64 pairpub struct ScalePresetSpec— static preset definition (not serialized)pub struct ScalePresetLodSpec— per-LOD spec entrypub fn tile_lod_band_rank(lod_band: &str) -> Option<u8>— numeric rank for sortpub fn label_zoom_band_rank(zoom_band: &str) -> Option<u8>— numeric rankpub fn label_zoom_band_for_lod(lod_band: &str) -> Option<&'static str>— mappingpub fn known_scale_presets() -> &'static [&'static str]pub fn scale_preset_spec(id: &str) -> Option<ScalePresetSpec>
Function inventory
config.rs:61tile_lod_band_rank— returns numeric 0–5 for continent…location-detailconfig.rs:73label_zoom_band_rank— returns numeric 0–3 for zoom stringsconfig.rs:83label_zoom_band_for_lod— maps lod_band → label zoom-band stringconfig.rs:199GeneratorConfig::from_preset— construct config from named presetconfig.rs:223GeneratorConfig::preset— look up ScalePresetSpec from stored idconfig.rs:227GeneratorConfig::cell_size_x_km— world width / raster_widthconfig.rs:231GeneratorConfig::cell_size_y_km— world height / raster_heightconfig.rs:236known_scale_presets— static list of 4 preset idsconfig.rs:245scale_preset_spec— returns static ScalePresetSpec for each of 4 presets
Visual-foundation findings
LOD ladder — continent preset (config.rs:147–190):
z=0 continent 1× 1 tiles sample_step=16
z=1 macro-region 4× 3 tiles sample_step=16 ← SAME step as z0
z=2 realm 8× 6 tiles sample_step=8
z=3 province-cluster 16×12 tiles sample_step=4
z=4 province 32×24 tiles sample_step=2
z=5 location-detail 64×48 tiles sample_step=1
Z5 (location-detail) has sample_step=1 and 64×48 tiles. With raster_width=2048, raster_height=1536, each z5 tile covers 2048/64=32 source columns and 1536/48=32 source rows. Cell size = 2400/2048 ≈ 1.172 km. Z5 tile spans 32 × 1.172 ≈ 37.5 km × 37.5 km. Pixels-per-km at z5: 1/1.172 ≈ 0.854 cells/km (i.e. 1 sample per ~1.17 km). This is strategic-map resolution, not fine terrain resolution.
Z1 "same step as Z0" anomaly (config.rs:155–163): z1 macro-region has sample_step=16, identical to z0 continent. The build_lod_pyramid code (tile_pyramid.rs:920–933) explicitly handles this as a "straight copy" case. This means the z1 raster is byte-identical to z0 — no additional detail is added. The z1 tiles are a re-cut of the same coarse raster into a 4×3 grid.
Rectangle slab risk: The continent preset's world bounds are hardcoded rectangles (config.rs:307–332). The sea region is a full-world rectangle with a continent polygon as a hole (continent.rs:83–101). If the viewer renders the sea mesh flat at elevation_km=0, the entire world bounding rectangle will render as a flat slab unless the continent polygon cutout is faithfully triangulated.
Tile sizing: no explicit pixel-dimension assertion per tile. nominal_tile_width_samples = source_width.div_ceil(sample_step) from tile_pyramid.rs:333–344. For z5 on continent: (2048/64).div_ceil(1) = 32 inner samples wide; padded to 32 + 2×1 = 34 by BORDER_CELLS=1.
continent.rs (101 lines)
Public surface
pub struct ContinentStageProductspub fn generate(config: &GeneratorConfig) -> ContinentStageProductspub fn contract() -> Value
Function inventory
continent.rs:16generate— top-level entry: callsgenerate_continent,generate_sea_regionscontinent.rs:43generate_continent— produces ellipse polygon with sinusoidal lobing viaconfig.seedcontinent.rs:83generate_sea_regions— produces outer-rectangle sea region with continent polygon as hole
Visual-foundation findings
Rectangle slab (continent.rs:83–101): The sea region outer ring is a full-world rectangle: (0,0) → (world_width,0) → (world_width,world_height) → (0,world_height) → (0,0). This is the outer boundary that the viewer renders as ocean. If the sea mesh is triangulated at flat elevation_km=0.0 (see meshes.rs:304) this will be a full-world rectangle behind the continent. That is architecturally correct with the continent polygon as a hole — but only if the hole is correctly triangulated. If triangulation fails or is skipped, the viewer renders a solid rectangular sea slab covering the entire world.
Continent polygon shape (continent.rs:43–81): The continent boundary uses sinusoidal lobe noise (amplitude up to 10%, 7%, 4% of the base ellipse). The clamp at world edges (continent.rs:58–59, config.world_width_km * 0.04 … 0.96) means continent boundary points can touch the 4% margin. The sea polygon hole is the continent points verbatim — no buffer. Any triangulation jitter at the margin will produce slivers.
Seed derivation (continent.rs:48): phase = (config.seed as f64).sin() — seed is cast to f64 first, then sin() applied. For seed=20260502, sin(20260502.0) is deterministic but has no guaranteed unique distribution for nearby seeds. This is a cosmetic concern, not a correctness problem.
heightfield/mod.rs (573 lines)
Public surface
pub mod erosion— submodulepub struct HeightfieldProductspub fn generate(config, continent, ridge_graph, basins) -> HeightfieldProductspub fn contract(config) -> Valuepub fn horn_slope_from_height(...)— Horn 1981 slope operatorpub fn centered_normals_from_height(...)— centred-difference normalspub fn encode_signed_normal(value: f32) -> i16
Function inventory
heightfield/mod.rs:105generate— drives noise→erosion→slope/normals for full rasterheightfield/mod.rs:173contract— JSON product contractheightfield/mod.rs:200ridge_influence— adds ridge spine elevation via Gaussian profileheightfield/mod.rs:250continental_macro_relief— continent-wide large-scale tilt/troughheightfield/mod.rs:280segment_field— signed distance from a point to a segmentheightfield/mod.rs:305connected_ridge_width— average edge width at a nodeheightfield/mod.rs:321gaussian— Gaussian decay kernelheightfield/mod.rs:326smoothstep_f64— canonical cubic smoothstepheightfield/mod.rs:334basin_influence— negative elevation depression for basinsheightfield/mod.rs:346centroid/basin_radius— polygon centroid/radiusheightfield/mod.rs:363deterministic_noise— thin wrapper onrotated_gradient_fbmheightfield/mod.rs:367compute_slope— callshorn_slope_from_heightat full resheightfield/mod.rs:397horn_slope_from_height— public Horn 1981 operatorheightfield/mod.rs:439centered_normals_from_height— public centred-difference normalsheightfield/mod.rs:484compute_normals— callscentered_normals_from_heightat full resheightfield/mod.rs:498encode_signed_normal— clamp+round to i16
Visual-foundation findings
Edge sharing between tiles (heightfield/mod.rs:388–392): The full-resolution raster uses clamp_to_edge (via .saturating_sub(1) and .min(height-1) stencil bounds in horn_slope_from_height and centered_normals_from_height). This means edge cells produce finite values but the raster does NOT share edge rows between tiles — each tile slices a window out of the same continent-wide LOD raster. The slice_lod_buffer function in tile_pyramid.rs adds BORDER_CELLS=1 padding by reading the LOD raster's neighbour cells. Adjacent tiles thus read overlapping 1-cell-wide strips from the same shared LOD raster, which guarantees continuity under bilinear sampling.
Slope operator mismatch at LODs: The compute_slope at z5 uses horn_slope_from_height with dx_m = cell_size_x_km * 1000. The lod_dx_dy_metres function in tile_pyramid.rs:793 correctly scales Δ by sample_step. The contract() comment (heightfield/mod.rs:185) confirms Horn is "harmonized across z5 and the LOD pyramid". No bug found here.
No shared-edge vertex duplication: the heightfield is a flat Vec<f32> indexed by row×width+col. The tile_pyramid slicer reads overlapping margins; there is no mesh-vertex deduplication concern at the raster level. At the mesh level, TerrainMeshBuilder uses a HashMap<TerrainVertexKey, u32> (meshes.rs:548) that deduplicates by (row, col) key — adjacent tiles do NOT share vertices; each tile's mesh is self-contained.
water.rs (376 lines)
Public surface
pub struct WaterProductspub fn generate(config, continent, ridge_graph, basins, heightfield) -> WaterProductspub fn contract(config) -> Value
Function inventory
water.rs:23generate— drives lake, river, raster generationwater.rs:56contract— JSON contractwater.rs:81generate_lakes— ellipse-shaped lake polygons derived from basin centroidswater.rs:131generate_rivers— hand-coded 7-node / 5-edge river graph with bent centerlineswater.rs:281generate_water_rasters— per-cell waterMask (0=dry,1=sea,2=lake,3=river) and riverWidthwater.rs:333river_node— builds RiverNode sampling height from heightfieldwater.rs:348bent_centerline— 9-point quadratic-like curve with sinusoidal bendwater.rs:368sample_height— nearest-cell height lookup (floor, no bilinear interpolation)
Visual-foundation findings
Lake shape is a hardcoded ellipse (water.rs:92–116): lakes are closed_ellipse(center, radius_x, radius_y, 28) — 28-point ellipse, not a polygon extracted from the heightfield. The basin depression (basin_influence in heightfield) is a Gaussian well, but the lake polygon does not follow the actual terrain contour. This is a visual-foundation concern: at detailed zoom levels, lake shores will not align with the heightfield mesh.
River graph is hardcoded (water.rs:131–278): nodes are at fixed world-fraction positions (config.world_width_km * 0.105, etc.). The ridge_graph.nodes[1] and ridge_graph.nodes[4] positions are used as sources — these are hard-indexed and will panic if the ridge graph has fewer than 5 nodes.
Water is polylines + raster, not a triangulated mesh at this stage. The water_mask raster is consumed by meshes.rs:write_water_meshes which then produces actual mesh geometry: lake surfaces via earcut triangulation, rivers via ribbon meshes. The polyline is an intermediate product.
sample_height uses floor (nearest), not bilinear (water.rs:368–376): river node elevations and lake surface elevations are nearest-cell sampled. This is adequate for the strategic map but produces stair-step artifacts for water mesh elevation along river banks.
No edge welding between water meshes and terrain: river ribbons (meshes.rs:243–266) are independent meshes floating 0.000_22 km above the heightfield. Terrain mesh vertices are keyed by (row, col) and not shared with river ribbons. There is no gap between them — but the ribbon may not perfectly conform to the terrain if sample_height_km picks a different nearest cell than the terrain mesh vertex position formula.
political.rs (1112 lines)
Public surface
pub struct PoliticalProductspub fn generate(config, continent) -> PoliticalProductspub fn contract(config) -> Valuepub(crate) fn splitmix64(mut value: u64) -> u64
Function inventory
political.rs:43generate— top-level: province Voronoi, location Voronoi, ID rasters, neighbor graph, color seedspolitical.rs:115build_province_color_seeds— low-discrepancy HSL palette per location, province-coherentpolitical.rs:157province_palette_hue— golden-ratio hue assignment per provincepolitical.rs:165hsl_to_rgb8/hue_to_rgb_channel/float_channel_to_u8— HSL→RGBpolitical.rs:206contract— JSON product contractpolitical.rs:242generate_provinces— Voronoi subdivision of continent polygonpolitical.rs:297generate_locations— per-province Voronoi subdivisionpolitical.rs:377generate_id_rasters— rasterize province and location polygon IDspolitical.rs:410rasterize_location_id/rasterize_polygon_id— bounding-box scan with PIP testpolitical.rs:472raster_col/raster_row— world-km → raster cellpolitical.rs:484outside_holes— true if point is outside all holespolitical.rs:488target_count— midpoint of a RangeU32political.rs:495allocate_counts_by_area— proportional allocation of locations to provincespolitical.rs:529relaxed_seed_points— Lloyd relaxation on Voronoi seedspolitical.rs:562blue_noise_seed_points— Halton+jitter blue noise seed placementpolitical.rs:620update_nearest_distances— distance-based blue noise updatepolitical.rs:635voronoi_cell_polygons— Sutherland-Hodgman bisector clipping Voronoipolitical.rs:662clip_polygon_to_nearer_seed— Sutherland-Hodgman half-plane clippolitical.rs:698strip_closure/close_ring/dedupe_open_ring/points_equal— ring utilitiespolitical.rs:754halton— quasi-random low-discrepancy Halton sequencepolitical.rs:765fract/unit_hash/splitmix64— hash utilitiespolitical.rs:781generate_neighbor_graph— realm→province→location containment + raster adjacencypolitical.rs:864add_raster_adjacency_edges/add_adjacency_edge— adjacency from raster scan
Visual-foundation findings
Names are intentionally empty at this stage (political.rs:37–42): a large block comment explicitly states "The name field on every emitted entity is intentionally the empty string here. Stage 6 (political_polygons) shapes the realm, provinces, and locations; the downstream political_naming stage fills in the procedurally-generated per-biome names." Old 120-name pool and "Province N" placeholder deleted.
No collision check in political.rs itself: the generate_provinces and generate_locations functions emit empty name: String::new(). Naming collisions are delegated entirely to political_naming.rs + naming.rs.
Unmatched land cells stay 0 (political.rs:381–408): rasterization only writes non-zero IDs where PIP test passes. A land cell not covered by any location polygon stays at locationId=0. The test unmatched_land_cell_without_location_polygon_owner_stays_zero validates this explicitly. This is the documented contract, not a bug, but viewers must handle locationId=0 as "no owner" rather than as an error.
political_naming.rs (402 lines)
Public surface
pub fn run(political: &mut PoliticalProducts, biomes: &BiomeMaterialProducts, config: &GeneratorConfig)
Function inventory
political_naming.rs:28run— drives RealmNamer, ProvinceNamer, LocationNamer over all political entities; panics on empty name outputpolitical_naming.rs:170sample_biome_at— floor-clamp nearest-cell lookup into biome raster; returns 0 for out-of-bounds anchorspolitical_naming.rs:196refresh_neighbor_graph_names— intentional no-op stub
Visual-foundation findings
Naming is scoped per-realm (political_naming.rs:121): location_namer = LocationNamer::new() is created once per run() call — it is realm-scoped. The emitted: BTreeSet<String> accumulates all previously assigned location names within the realm. Uniqueness is enforced at realm scope, not province scope.
"Sheafhaugh" duplication root-cause analysis: naming.rs:165–169 contains PLAIN_SUFFIXES which includes "haugh" at index 31. PLAIN_STEMS at naming.rs:155–162 includes "Sheaf" at index 46. compose_name at naming.rs:603–616 forms stem + suffix (or stem + connector + suffix). The string "Sheafhaugh" = stem "Sheaf" + suffix "haugh" is therefore a valid output from the plain biome table. Whether two entities receive this same name depends on whether the LocationNamer's emitted set successfully blocks the collision. The collision-prevention path is: LocationNamer::next_location_name → disambiguate → reroll_within_bank — which checks self.emitted.contains(&candidate) before returning. If "Sheafhaugh" appears twice, it means one of: (a) two separate LocationNamer instances were used (one per province, not one per realm); (b) the namer was not called through run() for all entities; or (c) the run was split across separate generation calls. Looking at political_naming.rs:121: let mut location_namer = LocationNamer::new() is created ONCE and used for ALL locations in the realm — this is correct realm-scoping. However, if any path outside political_naming::run calls generate then serializes without calling run, the empty-string names survive and two different serialization runs could emit the same names. No path in the audited code shows this happening, but the concern would be at the orchestration level in main.rs (not audited here).
refresh_neighbor_graph_names is a no-op (political_naming.rs:196–198): the neighbor graph nodes do not carry a name field in the current schema. This means debug overlays reading the neighbor graph will see absent names.
sample_biome_at silent fallback at boundary (political_naming.rs:183–186): if label_anchor lands outside the raster (col < 0, row < 0, or beyond dimensions), returns 0 (sea biome id). This routes through biome_tag_for_id(0) which returns None → DEFAULT_TABLE (plain stems/suffixes). This is a non-flagrant silent default but is documented as "only triggered by test fixtures".
routes.rs (1282 lines)
Public surface
pub struct RouteProductspub fn generate(config, locations, neighbor_graph, heightfield, water) -> RouteProductspub fn contract() -> Value
Function inventory
routes.rs:58generate— full route pipeline: nodes, candidates, edge selection, centerlines, crossingsroutes.rs:123contract— JSON contractroutes.rs:140crossing— construct a CrossingAnchorroutes.rs:159generate_crossings— route×river intersection searchroutes.rs:199build_route_nodes— one node per location, identify province hubsroutes.rs:214province_hub_location_ids— most-central location per provinceroutes.rs:237province_centrality_cost— sum of squared distances to siblingsroutes.rs:247build_route_candidates— neighbor-graph adjacency + spatial fallback pairsroutes.rs:298add_sparse_spatial_fallback_pairs— 2-nearest-neighbor fallback per noderoutes.rs:320insert_pair_source— deduplicated pair insertionroutes.rs:332build_route_candidate— terrain-aware quadratic centerline + costroutes.rs:379assign_location_connection_ids— sequential connection-id assignmentroutes.rs:385connection_kind— "river-crossing" | "land"routes.rs:393terrain_aware_centerline— 5-candidate midpoint selection minimizing costroutes.rs:451quadratic_points— 9-point quadratic Bézierroutes.rs:471average_route_penalty— per-segment slope/elevation/water penaltyroutes.rs:497terrain_penalty_at— slope + highland + lake penalty at a cellroutes.rs:524raster_index— bounds-checked cell lookuproutes.rs:539water_crossing_count— river intersection count per routeroutes.rs:548route_importance/route_width_km/route_kind— importance/width/kind from crossing flagsroutes.rs:586select_strategic_edges— Kruskal-MST + degree cap to ~1.3× node countroutes.rs:645materialize_route_products— route edges + centerlines from selected plansroutes.rs:678materialize_location_connection_graph— full candidate graph productroutes.rs:727nearest_location_id— min-distance to label_anchorroutes.rs:739squared_distance/ordered_pair— utilitiesroutes.rs:751route_river_crossing_point— segment intersection + proximity fallbackroutes.rs:780segment_intersection— line segment intersectionroutes.rs:797cross/ DisjointSet impl — utilities
Visual-foundation findings
No zoom-band field on route records themselves (routes.rs): RouteEdge carries route_kind, width_km, importance. The zoom-band visibility filtering is done in meshes.rs:route_detail_asset_visible_in_tile_lod and route_aggregate_min_importance. The route records do not carry explicit min_zoom_band / max_zoom_band fields — visibility is computed at mesh-generation time.
Aggregate meshes for z0–z3 only (meshes.rs:402): route aggregate meshes are written for z in 0..=3; at z>=4 (province and location-detail), only per-route individual meshes with route_detail_asset_visible_in_tile_lod filtering are written. Individual route meshes require lod_band to be "province" or "location-detail" to be included.
Route determinism: candidates are sorted (routes.rs:288–295) by cost then by location id strings before selection — fully deterministic given the same neighbor graph and seed. The neighbor graph itself is deterministic (raster adjacency scan in consistent order, BTreeSet for deduplication).
map_features.rs (321 lines)
Public surface
pub struct MapFeatureProductspub fn generate(config, ridge_graph, water, realm_polygons, province_polygons, locations, routes) -> MapFeatureProductspub fn contract() -> Value
Function inventory
map_features.rs:19generate— constructs 6 hardcoded feature anchors + 3 footprints, then callsgenerate_labelsmap_features.rs:141contract— JSON product contractmap_features.rs:153generate_labels— emits labels for realms, provinces, locations, lakes, crossings, map featuresmap_features.rs:247feature— construct MapFeatureAnchormap_features.rs:267footprint— construct MapFeatureFootprintmap_features.rs:281label— construct LabelAnchor with min/max zoom bands and categorymap_features.rs:306nearest_location_id— min-distance centroid fallback
Visual-foundation findings
All 6 feature anchors are hardcoded (map_features.rs:35–90): "Westwatch Harbor", "Crown Pass", "Crown Ford", "Mirrorfen", "Greenfold Woods", "Westmere Shore" are literal string names at fixed world-fraction positions. These are not generated from content — they will not vary with different seeds.
Label zoom bands are set (map_features.rs:163–244): every label record carries min_zoom_band, max_zoom_band, and category (see generate_labels). Realm labels: min="continent", max="realm". Province labels: min="realm", max="province". Location labels: min="province", max="location". Lake labels: min="realm", max="location". These answer Q4 affirmatively.
Feature name uniqueness: the 6 feature anchor names are hardcoded literals. There is no collision check. If the upstream wave runs multiple presets, the same 6 names appear regardless of seed. This is intentional for the current single-realm implementation.
Map-features label for "Crown Ford" indexes crossing_anchors.anchors[0] (map_features.rs:57): this will panic (index out of bounds) if generate_crossings in routes.rs produced zero crossings — possible if no route intersects any river. There is no guard.
tile_pyramid.rs (2774 lines)
Public surface
pub const BORDER_CELLS: usize = 1— bilinear seam paddingpub struct TilePyramidProductspub fn generate(run_dir, config, heightfield, water, biomes, political, borders_sdf, routes, features) -> Result<TilePyramidProducts>pub fn contract(config) -> Valuepub fn zoom_levels(config) -> Vec<TileZoomLevel>pub fn tile_coordinates(config) -> Vec<TileCoordinate>pub fn terrain_mesh_key(tile) -> Stringpub(crate) struct LodLevel/pub(crate) struct LodPyramid— internal pyramidpub(crate) struct BorderSdfLodLevel/pub(crate) struct BorderSdfLodPyramidpub(crate) fn build_lod_pyramid(...)/pub(crate) fn build_border_sdf_pyramid(...)
Function inventory (selected — full module has ~60 functions)
tile_pyramid.rs:60generate— top-level: builds LOD pyramid, then per-tile raster/vector/semantic/mesh fan-outtile_pyramid.rs:277contract— JSON contract with zoom-aware vector payload spectile_pyramid.rs:325zoom_levels— derives TileZoomLevel from preset specstile_pyramid.rs:348tile_coordinates— thin wrapper ontile_coordinates_with_lod_metadatatile_pyramid.rs:352tile_coordinates_with_lod_metadata— generates full tile list with bounds and source windowstile_pyramid.rs:428tile_id— "zZ.X.Y" stringtile_pyramid.rs:432parent_tile_id/child_tile_ids— tile hierarchy navigationtile_pyramid.rs:484tile_height_bounds_and_error— per-tile height range + geometric errortile_pyramid.rs:523fallback_geometric_error_meters— formula-based error estimatetile_pyramid.rs:541tile_cost_metadata— estimated decode ms / upload bytes / triangles / texture bytestile_pyramid.rs:557terrain_mesh_key— "terrain.zZ.X.Y"tile_pyramid.rs:582LodLevelstruct definition with all channelstile_pyramid.rs:672build_border_sdf_pyramid— iterative-2:1 cascade for SDF channelstile_pyramid.rs:793lod_dx_dy_metres— LOD-scaled cell size in metres for Horn/normalstile_pyramid.rs:863build_lod_pyramid— two-pass iterative-2:1 mip pyramid with Toksvig roughnesstile_pyramid.rs:1033sample_step_ratio— asserts power-of-2 ratios between LOD stepstile_pyramid.rs:1054cascade_box_mean_f32/cascade_box_mean_rgba8/cascade_mode_u8/cascade_border_sdf_2to1tile_pyramid.rs:1126box_mean_2to1_f32/box_mean_2to1_rgba8/mode_2to1_u8tile_pyramid.rs:1254toksvig_roughness_from_finer_lod— Toksvig normal-roughness prefiltertile_pyramid.rs:1360slice_lod_buffer<T>— padded per-tile slice with clamp-to-edgetile_pyramid.rs:1404write_lod_f32_tile_asset/write_lod_rg16_tile_asset/write_lod_u8_tile_asset/write_lod_rgba8_tile_assettile_pyramid.rs:1510write_raster_tile_assets— writes 9 raster channels per tiletile_pyramid.rs:~1700write_semantic_tile_assets— writes provinceId / locationId / waterMask u32/u8 rasterstile_pyramid.rs:~1900write_vector_tile— zoom-aware JSON vector tile with entity filteringtile_pyramid.rs:~2100write_tile_height_preview/write_tile_location_preview— PNG previews
Visual-foundation findings
Iterative-2:1 LOD pyramid is implemented (tile_pyramid.rs:863–1031): the earlier single-step "labyrinth bug" artifact has been addressed. The cascade uses cascade_box_mean_f32 / cascade_mode_u8 iteratively through power-of-2 ratios, not a single-step direct-from-source downsample. The Toksvig roughness sidecar is computed in a second pass.
Raster tiles are padded with BORDER_CELLS=1 (tile_pyramid.rs:47): each per-tile raster is (inner_W + 2) × (inner_H + 2). This is the canonical bilinear-seam fix. The slice_lod_buffer function reads the LOD raster's neighbour cells for the border ring — adjacent tiles share the same LOD raster source, guaranteeing matching values at seam texels.
Z1 straight-copy from Z0 (tile_pyramid.rs:920–933): when ratio == 1 (z0 and z1 both have sample_step=16 on continent), the height, water_mask, biome, and material channels are cloned byte-for-byte. The z1 tile rasters have identical content to z0 but are emitted as a separate 4×3 grid. The viewer must handle this without visible artifact.
No bbox-quad terrain mesh (meshes.rs:459–527): terrain_mesh_for_tile iterates over sampled grid cells and emits two triangles per cell via TerrainMeshBuilder. It is NOT a bounding-box quad — it is a full grid mesh conforming to the heightfield.
Sea surface mesh triangulates with earcut (meshes.rs:665–715): polygon_surface_mesh uses the earcutr crate with hole support. This is not a bbox quad; it correctly triangulates the sea outer boundary with the continent polygon as a hole.
Route aggregate mesh at z0–z3 only (meshes.rs:402): LODs z4 and z5 use per-route individual meshes with route_detail_asset_visible_in_tile_lod filter.
meshes.rs (1351 lines)
Public surface
pub struct MeshProductspub fn generate(run_dir, config, heightfield, water, continent, sea_regions, political, routes) -> Result<MeshProducts>pub fn contract() -> Valuepub struct MeshBuffer
Function inventory
meshes.rs:29generate— drives terrain/water/route mesh writingmeshes.rs:134contract— JSON contractmeshes.rs:162write_terrain_meshes— per-tile terrain grid meshesmeshes.rs:194write_water_meshes— sea surface + lake surfaces + river ribbonsmeshes.rs:270write_sea_surface_meshes— earcut triangulation of sea regionsmeshes.rs:324write_route_meshes— aggregate + per-route ribbons per zmeshes.rs:392write_route_aggregate_meshes— z0–z3 aggregate ribbonsmeshes.rs:459terrain_mesh_for_tile— grid mesh with all-sea fast-pathmeshes.rs:529TerrainMeshBuilder implementationmeshes.rs:656lake_surface_mesh— earcut with holesmeshes.rs:665polygon_surface_mesh— earcut with holesmeshes.rs:717append_ring_vertices— flatten ring into earcutr coordinate arraymeshes.rs:724ribbon_mesh/append_ribbon_mesh— per-segment quad ribbonmeshes.rs:790write_mesh_asset— write .mesh.bin + produce MeshAssetRefmeshes.rs:842tile_membership_refs— TileMembershipRef conversionmeshes.rs:854mesh_axis_indices— sampled axis index sequence with end guaranteemeshes.rs:869strip_closure/sample_height_km/polyline_originmeshes.rs:904points_intersect_bounds/route_detail_asset_visible_in_tile_lodmeshes.rs:918route_aggregate_min_importance— importance threshold per LOD bandmeshes.rs:926RouteMeshVisualBandstruct +route_mesh_visual_band+route_visual_width_kmmeshes.rs:999local_bounds/write_bytes— utilities
Visual-foundation findings
No bbox-quad emitted anywhere: confirmed — terrain uses TerrainMeshBuilder grid, water uses earcut, routes use ribbon (strip quad per segment). CLAUDE.md tenet "Never bbox-quad a complex shape" is respected.
River ribbon uses per-segment quads (meshes.rs:757–788): each segment of the centerline gets a quad (2 triangles), with width_km defining the half-width. Adjacent segments do NOT share vertices — each segment independently pushes 4 positions. At tight curves this produces visible triangle-fan gaps at segment junctions. No mitigation (e.g. miter joints) is present.
Sea surface at elevation_km=0.0 (meshes.rs:304): polygon_surface_mesh(&sea.outer, &holes, origin, 0.0) — sea surface elevation is hardcoded to 0.0 km. If the terrain has negative elevation (ocean shelf, elevation_range_meters.min = -1200), the sea surface sits at 0 while the terrain mesh below it can have vertices at negative elevation. This produces a "sea floor visible through ocean surface" risk unless the renderer handles depth correctly.
write_sea_surface_meshes assigns all tiles as membership (meshes.rs:285): let membership = tiles.to_vec() — the sea surface mesh is listed as belonging to ALL tiles in the pyramid. This is correct for a continent-wide sea polygon but means the viewer must handle a single mesh artifact linked to thousands of tile entries.
Route ribbon elevation_offset_km = 0.000_8 (meshes.rs:383): route ribbons float 0.8 m above the terrain. The exact height is sampled with sample_height_km (nearest-cell floor lookup). Small-cell artifacts possible where the terrain has rapid height change between adjacent cells.
Specific Question Answers
Q1: LOD ladder and z5 raster sample size
Defined at config.rs:147–190 in CONTINENT_LODS. Z5 has sample_step=1, tile_count_x=64, tile_count_y=48. With raster_width=2048, each z5 tile covers 2048/64=32 source columns. Cell size = 2400/2048 ≈ 1.172 km. Z5 pixel-per-km estimate: 1/1.172 ≈ 0.854 cells/km (approximately 1 sample per 1.17 km).
Q2: Could the generator produce a rectangular slab artifact?
Yes, by design for the sea surface. continent.rs:83–101 generates a full-world rectangle as the sea outer ring. meshes.rs:304 triangulates this at elevation_km=0.0. The continent polygon is passed as a hole. If earcut succeeds (and tests confirm it does), the result is NOT a rectangle slab — it is a correctly-holed mesh. However, if earcut were to fail or the hole polygon were corrupted, the fallback would be an empty mesh (bail! on triangulation failure — meshes.rs:694), not a slab fallback. So the slab risk is during earcut failure, not during normal operation.
For raster tiles: the pyramid emits tiles covering the full world grid, including sea-only tiles. All-sea tiles produce no terrain mesh geometry (meshes.rs:492–509 fast path). The sea surface mesh covers the full world minus the continent hole. This is correct architecture — no spurious raster-behind-vector slab.
Q3: "Sheafhaugh" collision root cause
"Sheafhaugh" comes from PLAIN_STEMS[46]="Sheaf" + PLAIN_SUFFIXES[31]="haugh" in naming.rs:150–170. This is a valid composition from compose_name at naming.rs:603–616. The collision prevention is LocationNamer.emitted: BTreeSet<String> in naming.rs:316–411. The reroll_within_bank function at naming.rs:383–393 checks self.emitted.contains(&candidate) before returning a name. If "Sheafhaugh" was assigned twice, the bug would be: two separate LocationNamer instances (not realm-scoped), OR the location that received the name the second time was processed by a different political_naming::run call (e.g. split across generation runs that each start a fresh namer). In the audited political_naming.rs:121, a single LocationNamer is created and shared across all locations in the realm within one run() call. No duplication path is visible in the generator source. The root cause is likely at the orchestration level (main.rs not audited) or in a scenario where the artifacts from two separate runs were merged in the viewer.
Q4: Label zoom-band tagging
Confirmed present. Every LabelAnchor carries min_zoom_band, max_zoom_band, and category fields. These are written by map_features.rs:281–303 (label constructor function) and generate_labels at map_features.rs:153–244. Realm labels: min="continent", max="realm". Province labels: min="realm", max="province". Location labels: min="province", max="location".
Q5: Water polygons serialized as
Both polylines and triangulated meshes. Outputs:
riverCenterlines.json— polylines (list ofPointKm), per river edgelakePolygons.json— closed polygon rings, per lakemeshes/water/lake-*.mesh.bin— earcut-triangulated surfaces (meshes.rs:656–715)meshes/water/river-*.mesh.bin— per-segment ribbon meshes (meshes.rs:724–788)meshes/water/sea-*.mesh.bin— earcut-triangulated sea surface with land hole
Q6: Determinism and seed plumbing
The generation is fully deterministic given config.seed. The seed flows as:
GeneratorConfig.seed(u64, default 20260502) is the rootcontinent.rs:48:phase = (config.seed as f64).sin()— continent shapeheightfield/mod.rs:126:config.seed ^ 0xa6d0...— detail noisepolitical.rs:262:relaxed_seed_points(config.seed, 0x7b58..., ...)— province seedspolitical.rs:334:config.seed ^ province_cell.polygon.numeric_id as u64— location seeds per provincenaming.rs:554–596:location_seed,province_seed,realm_seedmixconfig.seedwith numeric IDs- All random choices use
splitmix64with XOR mixing of the seed and entity ids
Charter / Governance Lines
CLAUDE.md:29— "Never bbox-quad a complex shape — if a feature has a non-rectangular outline (lakes, regions, areas), extract the actual polygon (marching squares) and triangulate (ear clipping or Delaunay)."CLAUDE.md:28— "Never use billboards or 2D primitives in a 3D scene where a real mesh is the correct primitive — this applies to: water (rivers, lakes, waterfalls)..."CLAUDE.md:30— "When you patch the same subsystem 3+ times, the original design is wrong — stop patching, redesign.".claude/rules/orchestrator-mode.md:53–55— "Not spawn further sub-agents. Forbidden tools:Agent,Task,spawn_task,ScheduleWakeup,CronCreate, and any other scheduling tool. Deferred work belongs in the sub-agent's response text.".claude/rules/mapv10.md:30— "Missing required generated artifacts must fail loudly.".claude/rules/mapv10.md:34— "The target is 3D terrain with real water/border/overlay geometry or stable generated data products, not a flat 2D political image."
Risks of Fabricated Source / Invented Syntax / Silent Fallback
map_features.rs:57panic on zero crossings:routes.crossing_anchors.anchors[0]is an unconditional index-0 access. If no route crosses any river, generation panics. No.secspressure, but a generator panic that could block test runs.water.rs:137–138ridge_graph.nodes[1]andridge_graph.nodes[4]are hard-indexed. If a future preset produces a ridge graph with fewer nodes, this panics silently in the middle of water generation.water.rs:104–108lake names are hardcoded ("Westmere","Mirrorfen Lake"). These are not procedurally generated and will not honor the no-fallback naming contract at continent scale where multiple lakes would be generated. Currently only 2 basins produce 2 lakes; if the basin count changes, index-based access still only names 2.political_naming.rs:183–186sample_biome_atreturns0(sea) for out-of-bounds anchors. This silently routes to the DEFAULT_TABLE (plain stems) without any logged warning. An anchor placed off-continent would produce a plain-biome name for what might be a sea or highland area — silent wrong-palette, not a panic.naming.rs:239–247DEFAULT_TABLE=PLAIN_TABLE. Used whenbiome_tagisNone. The comment says this "only catches the case where a synthetic NamingContext is built directly... without going through the political_naming stage's biome sampler." However, ifbiome_tag_for_idreturnsNone(e.g. for biome id=5 freshwater or id=0 sea), the DEFAULT_TABLE silently produces plain-themed names for those entities without panicking or logging.
Open Questions for the Implement Step
- Z1=Z0 byte-identity: is the viewer intended to display z1 as a distinct visual LOD from z0, or is this a known placeholder until the continent preset adds a proper z1 intermediate band? The
tile_pyramid.rs:920–933"straight copy" path suggests this is known behavior, but the visual consequence (no additional detail at z1 vs z0) should be verified. - Sea surface at elevation_km=0 vs terrain negative elevation: the ocean-shelf terrain runs as low as -1200 m. The sea surface mesh sits at 0. Is the viewer expected to render the terrain mesh for the seabed (which includes the coastal seabed via the "all_sea fast-path skip"), or rely on the sea-surface mesh at 0 to occlude the seabed? The current code emits a seabed terrain mesh for coastal (non-all-sea) tiles but not for fully-deep tiles.
- River ribbon miter joints:
append_ribbon_meshproduces per-segment quads without miter joints at bends. Is visible triangle-fan gapping at river bends acceptable for the current visual target? - "Sheafhaugh" duplication: if the duplication was seen in a specific viewer run, was it from two generation runs merged, or from a single run? The generator source shows no intra-run duplication path, but the orchestration (main.rs not audited) could split the naming stage.
map_features.rs:57hardcoded index-0 crossing access: should this be guarded with a conditional (no crossing → no Crown Ford feature), or is the crossing guaranteed by the hardcoded river/route layout?
Deferred Work
main.rsorchestration not audited — the seed plumbing, stage ordering, and potential multi-run artifact merging should be audited to confirm the Sheafhaugh duplication path.stages/biomes_materials.rsnot read — the biome classification logic (which biome IDs map to which biome tags) should be cross-checked againstnaming.rs:301–310biome_tag_for_idto confirm no biome class is silently dropped to DEFAULT_TABLE in production runs.stages/borders_sdf.rsnot read — the border SDF stage that feeds into the tile pyramid'sborderSdfchannel is outside this audit's primary scope but was referenced.stages/erosion.rsnot read — the Mei 2007 pipe model + Štava 2008 sediment transport implementation details may have independent findings relevant to heightfield visual quality.