summaryrefslogtreecommitdiff
path: root/docs/api-reference/map-and-mapgen.md
blob: 59439673342678a33be05f4334904ecad18efed4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 地图与生成(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=<dir> dotnet src/LivingVillage.Desktop/bin/Release/net8.0/LivingVillage.Desktop.dll`,
  再 `python3 scripts/analyze-p68.py <dir> docs/evidence/p68-analysis.txt`。