# 地图与生成(MapGen / ProceduralMap / MapSnapshot) 源码:`src/LivingVillage.Desktop/MapGen.fs`、`src/LivingVillage.Desktop/ProceduralMap.fs`、 `src/LivingVillage.Desktop/MapSnapshot.fs`。 ## 职责 确定性生成可玩地图(河道 / 桥 / 石板路 / 民居 / 农田 / 主路),并把生成结果离线渲染为整图 PNG。 `MapGen` 是默认可玩世界(256×192)与大图(512×384)的生成器;`ProceduralMap` 是遗留 `LV_MAP_SCALE` 路径;`MapSnapshot` 只做纯 CPU 像素渲染,不参与游戏帧循环。 ## 对外 API 概览 - `MapGen.defaultParams seed`:默认 64×48、`RiverCount = 2`、`RiverWidth = 3`、`CoreSide = 14`、 `SpawnColumns = 5`、`SpawnRows = 6`。 - `MapGen.paramsForSize width height seed`:尺寸自适应;`large = width > 256 || height > 192`。 大图追加 `ExtraRiversideHouses = 30`、`RiverDecorations = true`、`ClusterSeed = seed ^^^ 0xC1A57E2UL`, 并关闭 `DecorativeStones`;非大图这些字段保持默认(不消费额外 RNG)。 - `MapGen.generate params` / `MapGen.generateWithSize width height seed` → `MapGen.Result`。 - `MapGen.serialize map`:确定性文本(同 seed 逐字节一致),用于回归/跨机核对。 - `MapGen.tileAt map x y`、`MapGen.isWalkable map x y`、`MapGen.floodFill map x y`、 `MapGen.visibleTileRange`、`MapGen.visibleTileCount`。 - `ProceduralMap.generate`:遗留大世界地形(512×384 路径)。 - `MapSnapshot.renderRgb map scale` / `MapSnapshot.renderPng map scale` / `MapSnapshot.save path map scale`; `defaultScale = 8`。 ## 单位与量纲 - 地图尺寸以**瓦片**计(`Result.Width` / `Result.Height`);内存瓦片码 `Tiles: int array`(行优先)。 - `GroundTile`:`Grass=0 / Water=1 / Stone=2 / Peat=3 / PaddyField=4 / VegetablePlot=5`。 - `River = { CenterY; Width }`(每列一条);`Building = { Left; Top; Width; Height; DoorX; DoorY }`(瓦片)。 - `MainRoadCenter: int array`(长度 = 宽,逐列主路中心 y);`Paths: (int*int) list`、`Bridges`。 - `MapSnapshot`:输出像素 = `Width*scale × Height*scale`;8-bit RGB PNG(无 alpha)。 ## 不变量 1. **种子确定性**:同 `(width, height, seed)` 两次 `generateWithSize` 的 `Tiles`、 `Rivers/Bridges/Paths/Buildings/Farmhouses/Groves/MainRoadCenter` 与 `serialize` 逐字节一致。 2. **既有尺寸逐字节不变**:64×48 / 256×192 不进入大图分支、不消费额外 RNG(默认 stones 开启、 decorations 关闭、ClusterSeed = 0)。 3. **河道连续**:每条河每列恰好一段,宽度 ≥ `RiverWidth`;桥面仍标 `Water` 但 `isWalkable` 为真。 4. **可达性**:`Result.ReachableTiles / ReachabilityOk / BridgeCrossingsOk` 由 `floodFill` 判定; 民居/农舍门贴可达地面。 5. **大图农田**:`PaddyField`/`VegetablePlot` 只在陆地上、避开路径与核心矩形。 6. **渲染纯函数**:`MapSnapshot` 不依赖图形设备与时钟;同输入 PNG 逐字节一致。 ## 失败分支 - `paramsForSize` 对任意正整数尺寸不抛异常(`large` 判定决定分支)。 - `MapSnapshot.renderRgb/renderPng` 对越界瓦片写入静默跳过(`fillTile` 有 `tx/ty` 范围检查); `scale` 经 `max 1` 保护;`save` 的 IO 异常由调用方处理。 - `MapGen.isWalkable` 对越界坐标返回 `false`。 ## 算法与性能取舍 - 生成原语:`splitmix64`(无外部依赖、无时钟)+ `valueNoise` 多八度 + 泊松布点; 大图再加聚落组团(`ClusterSeed`)与主路缓弯(相邻列步长 ≤1)。 - 渲染:`MapSnapshot` 用扁平色块 + 简化几何(屋顶矩形、门、主路/小径)而非纹理, 目的是**离线、可复跑、无 GPU 依赖**;PNG 用 BCL `ZLibStream` 压缩。 - 实测(`docs/evidence/p68-analysis.txt`,512×384 / seed=4242 / scale=8 → 4096×3072): 河 388,608px、水田 742,080px、菜畦 824,128px、主路 32,768px、小径 34,112px、屋顶 19,328px 均 > 0; 无 24×24 纯黑块 / 64×64 纯白块;`missing_glyph_slots = 0`;确定性 sha256 = `DD23CC2D…0FBF`。 - 复跑:`LV_P68_MAPSHOT=1 LV_RECORD_DIR= dotnet src/LivingVillage.Desktop/bin/Release/net8.0/LivingVillage.Desktop.dll`, 再 `python3 scripts/analyze-p68.py docs/evidence/p68-analysis.txt`。