如果只是生成一张“看起来像地图”的图片,今天已经不算困难。

真正困难的是生成一个能够继续推演的世界:山脉影响河流,河流影响聚落,道路连接城市,国家拥有省份,文化、宗教、经济、外交和军事彼此关联;当用户修改其中一个对象时,系统还要知道哪些内容应该跟着变化,哪些内容必须受到保护。

这就是我正在开发的项目——WebGL 幻想地图生成器

在线体验 · 项目仓库 · 项目 README

WebGL 幻想地图生成器的国家视角

它借鉴了 Fantasy Map Generator 的生成思路和视觉语言,但不是对原项目进行修改,也不是简单替换它的渲染层。原项目源码在仓库里只作为行为参考和性能基线;正式应用的生成器、WebGL 渲染器、界面、编辑系统、存档格式和自动化接口都是从头独立实现的。

还有一点比较特别:目前应用代码和项目文档全部由 Codex 与 GPT 完成。我的角色主要是提出需求、决定产品方向、审查结果,以及在真实浏览器里判断它究竟是不是一个好用的地图工具。

这款生成器现在能做什么

项目最初只是一个验证 WebGL 可行性的实验,但现在已经发展成了一套较完整的幻想世界生成和编辑工具。

生成一张新地图时,程序会先构建网格、海陆轮廓、高度与气候,再继续生成河流、生物群系和自然资源。自然环境形成后,系统会进一步安排文化、宗教、国家、省份、城市、道路、经济、贸易、外交、军事和事件地区。

生成完成并不意味着地图就此定稿。用户可以继续编辑高度、河流、湖泊、国家、省份、城市、路线、标签、地区和许多其它对象。常规操作支持撤销与重做;影响范围较大的修改会先进行预检,列出可能受影响的内容,并保护已经锁定的对象。

地图可以保存为项目自己的压缩存档,也可以导出 PNG、JPEG、SVG、GeoJSON、高度灰度图等结果。旧版存档则通过版本化迁移和字段回填继续兼容。

项目还提供了一套面向脚本和 AI 的受控 API。目前公开能力已经覆盖 17 个命名空间、316 个方法,其中包含对象查询、区域分析、地图控制和受约束编辑。AI 可以先读取地图、判断操作是否允许,再凭带有地图版本信息的预检凭证执行修改,而不是靠模拟鼠标点击,或者直接改写内部对象。

一切从性能基线开始

项目开始于 2026 年 5 月。

最初的问题很直接:Fantasy Map Generator 的 SVG 地图在复杂场景下会产生大量 DOM 节点,视图切换和完整重绘容易出现明显卡顿。我的第一反应也是“把 SVG 换成 WebGL”,但真正开始工作前,我决定先测量,而不是立即重写。

因此,第一个里程碑不是新应用,而是一套包裹在原项目外部的性能分析工具。它会用浏览器生成 10k、50k 和 100k 三档地图,记录生成耗时、完整绘制耗时、各图层节点数量以及单个绘制函数的成本。

在当时的 100k 样本里,原 SVG 地图包含大约 7.8 万个节点。人口、降水、标记、纹章、图标和标签都是主要节点来源。这说明问题不只是国家色块或者海岸线:即使把几个大面积图层迁移到 GPU,只要大量文字和标记仍然留在 DOM 中,整体性能问题就不会自动消失。

随后建立的 WebGL2 原型把近十万个基础单元转换为 GPU 三角形,并加入边界、路线、河流、点图层和拾取索引。原型阶段的单次 WebGL 绘制约为 0.3 毫秒,证明了方向可行。

但这个数字不能直接宣传成“比原版快多少倍”。原型没有覆盖完整文字、纹章、复杂河道和所有交互,两边使用的随机地图也不完全相同。它只能证明一件事:把几何缓存进 GPU 后,相机变化不需要重新构造整棵 SVG DOM。

这种对结论边界的克制,后来成为整个项目的开发原则之一。

从实验原型到独立应用

2026 年 6 月,项目进入第二阶段:不再继续改造参考项目,而是建立独立的 WebGL 地图生成器。

这一步最难的并不是把多边形画出来,而是重新理解原项目的世界模型。

地图内部同时存在两套重要空间结构:均匀的基础 Grid,以及经过筛选和压缩、承载更多业务语义的 Pack。早期原型曾错误地把 Pack cell 当成基础网格使用,结果在水域和边界附近出现了横跨地图的巨大三角形。修正后,基础地表改为使用 Grid 几何,国家、河流、边界和对象查询才继续使用 Pack 语义。

这是一个很典型的教训:两个数据结构都叫 cell,并不意味着它们能互换。重写一个成熟项目时,最危险的往往不是看不懂代码,而是以为自己已经看懂了。

独立应用随后逐渐补齐高度、气候、河流、文化、宗教、国家、省份、聚落、路线、经济、外交、军事和地区等生成链。界面则采用 WebGL2 画布配合 HTML/SVG overlay:大规模几何交给 GPU,文字、编辑手柄和复杂交互保留在浏览器布局系统中。

这不是最“纯粹”的 GPU 架构,却是当时最符合项目实际情况的方案。文字碰撞、输入框、焦点管理和无障碍支持如果全部自己在 WebGL 里重做,工程成本会远大于收益。

WebGL 幻想地图生成器的自然地貌视角

我是怎样管理 AI 开发的

让 AI 写代码并不难。困难的是让一个持续数月、拥有大量状态和兼容要求的项目不失控。

这个项目后来形成了几条很严格的工作方法。

只有一份当前任务清单

项目维护一份唯一的“权威任务清单”。需求只有正式进入清单后才能实施,每个任务都要写清范围、排除项和最小验收条件。

一项任务达到验收条件后必须立即停止,不能因为顺手发现了其它问题就扩大范围。新发现可以记录,但不能偷偷变成当前任务。

这条规则看起来有些机械,却有效防止了 AI 最常见的问题:不断补充“顺便可以优化”的内容,最后做出一个庞大但无法验收的改动。

到目前为止,项目已经走过两百多个编号任务。编号本身并不重要,重要的是每一项变化都能找到提出原因、范围边界、实现记录和验证证据。

调查、实现、审查和真实验收分开

复杂任务通常会经历几个阶段:

  1. 阅读当前代码和历史决策,冻结真实问题。
  2. 实现封闭范围内的改动。
  3. 独立检查代码、兼容性和潜在副作用。
  4. 在真实 Chrome 中验收最终行为。

测试脚本通过不等于功能完成。涉及界面和地图表现的改动,必须真正打开浏览器观察。

例如,一个图标按钮的容器可能在数学上完全居中,但里面的 SVG 图形仍然会因为自身观察盒留白而向上偏移。最终必须测量 SVG 本身的边界,而不是只看按钮的 CSS。

同样,地图标签是否遮挡、窄屏是否溢出、加载动画是否完整播放、导出的 PNG 是否真的包含正确图层,都不能只靠静态代码判断。

把文档当成项目记忆

项目文档不是开发结束后的附属品,而是开发流程的一部分。

架构约束、当前计划、开发日志、性能报告、审计矩阵、专题设计和浏览器验收结果都有明确位置。新会话接手时,不需要把整个仓库一次性塞进上下文,而是沿着固定入口逐步读取:先看项目规则,再看当前任务,最后搜索与当前问题直接相关的实现和记录。

这套方式既节省上下文,也降低了 AI 被过时计划误导的概率。

最棘手的几个问题

海岸线不是一条可以随便平滑的线

让海岸线看起来更自然很容易,让它在平滑后仍然保持正确拓扑却很难。

海岸和湖岸不仅决定视觉轮廓,还与城市、道路、河口、行政边界和单元归属相连。一次看似无害的曲线平滑,可能产生针状尖刺、缝隙、错侧填充,甚至让河口和海岸分离。

为此,项目专门建立了一个边界拓扑实验室,用固定夹具复现各种失败形态。后来的正式方案会复用共享边界、检查线面同侧关系,并在局部不安全时回退。海岸允许产生一定视觉形变,但沿岸城市、道路、河流和受保护对象不能被悄悄移动。

这类问题很难只写一个“看起来差不多”的截图测试。它需要几何约束、像素检查、对象不变量和人工视觉共同参与。

WebGL 很快,但整个页面不一定快

2026 年 8 月进行的一轮正式性能调查发现,连续缩放和平移的主要瓶颈已经不是 WebGL draw,而是 DOM/SVG overlay,尤其是标签。

在 100k 地图中,WebGL 的 CPU 提交仍然很轻,但标签布局、样式计算、绘制准备和合成会占据大量主线程时间。每一个滚轮事件都重新同步一遍覆盖层,最终把 GPU 带来的收益抵消了。

后来的优化把连续交互拆成两个阶段:交互期间只更新相机和根变换,浏览器空闲后再进行一次完整提交。一次缩放中的 18 次输入和一次拖动中的 47 次移动,不再分别触发 18 次、47 次完整 overlay 重排。

同时,加载阶段增加了几何缓存与颜色复用。100k 样本中,loadMap 中位耗时下降约 28.4%,点击生成到地图出现下降约 27.5%。

不过平移的最终帧耗时并没有同比例下降,因为完整路线、河流和覆盖层刷新只是被集中到了交互结束时。这再次说明,性能优化不能只挑一个好看的平均数;必须区分交互预览、空闲提交、CPU 提交、浏览器布局和 GPU 执行。

旧存档比新功能更难处理

程序可以很容易地生成一张符合最新结构的新地图,但用户真正珍贵的是已经编辑很久的旧地图。

项目曾遇到过一个很有代表性的问题:某个运行时缓存原本是 JavaScript Map,经过旧版存档序列化后变成普通对象。恢复地图时,代码仍然调用 .get(),最终导致新增国家失败。旧图里还可能存在稀疏 ID、缺失字段、不同版本的政治镜像以及高度和地貌标记不一致等情况。

修复这种问题不能只补一个类型判断。兼容入口必须覆盖浏览器缓存、完整 JSON 导入、旧导出文件和云端存档;创建国家等复杂操作还要在任一阶段失败时完整恢复国家、省份、cell、城市和派生状态,不能留下半个对象。

因此,项目后来把“旧数据适配”变成默认要求:只要新增字段、修改结构或调整存档格式,就必须考虑迁移、回填和旧样本验证。

安全地让 AI 修改地图

只读分析相对简单,真正困难的是让 AI 修改一个存在复杂规则的世界。

“把这些格子交给另一个国家”看起来像修改一个数组,实际可能影响省份连续性、城市归属、首都、路线、人口统计、外交关系和后续重算。允许 AI 直接写内部 map,几乎一定会制造难以发现的不一致。

项目最终采用了分层模型:空间层只负责判断目标 cell;领域 inspector 判断业务规则;执行器创建可撤销事务;AI planner 只负责组合已经授权的规则动作。

每次高影响操作都可以先只读预检。预检结果绑定地图 identity、revision、动作和输入指纹;如果地图随后发生变化,旧凭证就会失效。执行中出现异常则完整回滚,而不是依赖“执行后再撤销”来判断操作是否安全。

这套架构做起来比开放一个万能修改接口慢得多,却让自动化能力真正具备了长期使用的可能。

云盘和桌面打包的边界

项目近期也尝试过 Dropbox、Google Drive 和桌面打包。

网页里的云盘授权本身就涉及 OAuth 回调、来源校验、PKCE、弹窗通信和短期令牌生命周期。为了避免扩大安全边界,项目只在当前标签页的 sessionStorage 保存短期令牌与配置指纹,不使用 refresh token,也不把授权状态长期写入 LocalStorage。

我后来又用 Pake 尝试生成 Windows MSI 和 EXE。核心地图能够启动,但在 WebView2 容器里,Dropbox 和 Google Drive 的授权都失败了。桌面壳改变了窗口、来源和浏览器会话环境,原本在 Chrome 中成立的授权流程不再天然成立。

最终我决定不把 Pake 作为正式桌面方案。对当前项目而言,Chrome 自带的“安装为应用”更实用:它保留浏览器环境和用户会话,又能获得接近独立窗口的体验。

这个尝试也提醒了我:能够生成一个安装包,不等于已经完成桌面产品化。

AI 写代码,人仍然决定什么是“对”

这个项目的代码与文档由 Codex 和 GPT 完成,但它并不是一句提示词自动生成的。

AI 很擅长快速调查代码、建立回归夹具、补齐重复接口、生成机器矩阵,以及在明确边界内持续施工。它也很容易过度实现、误读视觉反馈、把一次偶然通过当作稳定结论,或者根据旧文档继续执行已经失效的计划。

因此,人真正不可替代的部分是:

  • 决定项目为什么存在;
  • 判断视觉和交互是否符合预期;
  • 决定哪些兼容成本值得承担;
  • 在多个技术上可行的方案中做产品取舍;
  • 对 AI 给出的证据保持怀疑。

我并不需要亲手输入每一行代码,但必须知道什么结果才算正确。

现在,以及接下来

截至 2026 年 8 月,WebGL 幻想地图生成器已经从性能原型发展为可以生成、查看、编辑、保存、导出和自动化分析完整幻想世界的应用。

项目同时维护了 20 篇中文 Wiki 页面、可复现的功能截图、性能基线、浏览器回归以及覆盖主要地图能力的 API 和规则矩阵。它已经越过“能不能做出来”的阶段,开始面对真正产品化的问题:默认体验、视觉一致性、旧数据兼容、复杂操作安全和长期性能。

不过它仍然不是一个稳定发布版。生成算法、界面和存档约定仍可能继续调整。我更愿意让下一阶段来自真实使用中暴露的问题,而不是为了让任务列表显得热闹而继续堆功能。

如果要总结这段开发经历,我得到的最大体会不是“AI 写代码很快”,而是:

先测量,再重写;先冻结边界,再让 AI 执行;最后一定要在真实世界里验证。

地图生成如此,软件开发也是如此。

项目入口:在线体验 · GitHub 仓库 · 项目 README