我用 AI 从零开发了一款 WebGL 幻想地图生成器
如果只是生成一张“看起来像地图”的图片,今天已经不算困难。
真正困难的是生成一个能够继续推演的世界:山脉影响河流,河流影响聚落,道路连接城市,国家拥有省份,文化、宗教、经济、外交和军事彼此关联;当用户修改其中一个对象时,系统还要知道哪些内容应该跟着变化,哪些内容必须受到保护。
这就是我正在开发的项目——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 里重做,工程成本会远大于收益。

我是怎样管理 AI 开发的
让 AI 写代码并不难。困难的是让一个持续数月、拥有大量状态和兼容要求的项目不失控。
这个项目后来形成了几条很严格的工作方法。
只有一份当前任务清单
项目维护一份唯一的“权威任务清单”。需求只有正式进入清单后才能实施,每个任务都要写清范围、排除项和最小验收条件。
一项任务达到验收条件后必须立即停止,不能因为顺手发现了其它问题就扩大范围。新发现可以记录,但不能偷偷变成当前任务。
这条规则看起来有些机械,却有效防止了 AI 最常见的问题:不断补充“顺便可以优化”的内容,最后做出一个庞大但无法验收的改动。
到目前为止,项目已经走过两百多个编号任务。编号本身并不重要,重要的是每一项变化都能找到提出原因、范围边界、实现记录和验证证据。
调查、实现、审查和真实验收分开
复杂任务通常会经历几个阶段:
- 阅读当前代码和历史决策,冻结真实问题。
- 实现封闭范围内的改动。
- 独立检查代码、兼容性和潜在副作用。
- 在真实 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 执行;最后一定要在真实世界里验证。
地图生成如此,软件开发也是如此。


Mosu is located on the shore of Mosu Lake, facing the vast Chu Sea, backed by the Yihan Mountains. Thousands of miles of Mosu Desert can not erode the Mosu Valley. Thus the Mosu Empire was established.