日期:2026-08-02 | 项目:M-378 / P577 | 在线试:https://metazero-terminal.pages.dev/maptext/
没有一种格式能同时伺候 8×8 和 8000×8000。 这不是技术不够,是需求本身矛盾:
| 8×8 | 8000×8000 | |
|---|---|---|
| 你想要什么 | 看得见、随手改 | 一句话生成整个世界 |
| 该写什么 | 每一格都写出来(数据) | 一条格子都别写(规则) |
| 文本长度 | 64 个字符 | 6400 万格 → 不可能 |
所以正确答案不是"选一个格式",而是:
> 让"画出来的数据"和"写出来的规则"变成两层,能叠加。
> 小图全画、大图全写规则、中图混着来 —— 画了的地方以画的为准。
外加第二条,它决定了这门语言能不能规模化:
> "是什么"和"长什么样"必须分开。 格子里只写字符 A;A 是 150 米高的青色退台塔,由图例说了算。
> 于是换一行图例 = 整座城换风格,格子一个字都不用动。
我按这两条做了 MAPTEXT v0.1,代码和可试的网页都在下面第 4 节。
这是设计任何地图格式之前必须先回答的问题。我拆成 6 层,层与层之间只有"挂靠"关系,没有互相纠缠 —— 这样每一层都能独立换掉:
`
L5 氛围 天空色 · 调色板 · 雾 · 昼夜 ← 改一行,整张图换季
L4 活物 车 · 行人 · 无人机 ← 只给"在哪种地块上撒多少个",不给坐标
L3 地面附属 树 · 路灯 · 长椅 · 报刊亭 · 广告牌 ← 挂在【地块】上,不挂在楼上
L2 楼身装饰 霓虹带 · 天线 · 塔顶灯 · 阳台 · 招牌 ← 挂在【楼】上
L1 楼体 占地 · 高度 · 边数 · 体块形态 · 屋顶 ← "楼"这个大元素本身
L0 地块 马路 · 人行道 · 广场 · 公园 · 水面 · 空地
`
为什么 L3 和 L2 一定要分开? 因为树不是楼的一部分 —— 拆掉楼树还在。把它们混在一起,以后想"只换建筑风格不动绿化"就做不到了。GTA 那种复杂度的城市,本质就是这 6 层各自往里堆更多种类,不是多出第 7 层。
每层的具体词表(这就是 v0.1 的全部元素):
| 层 | 属性 / 词 |
|---|---|
| L0 地块 | road walk plaza park water empty build |
| L1 楼体 | h 高度区间 | w 占格比例 | sides 边数 | form 体块(方塔/锥塔/退台/双子/圆柱/斜切/桶楼/裙楼塔)| roof 屋顶(平/尖/冠/穹/天线/天台)| color | win 窗户密度 | rot 朝向 |
| L2 楼饰 | neon antenna beacon sign balcony crown pipe holo |
| L3 地饰 | tree lamp bench kiosk billboard barrier hydrant rock grass |
| L4 活物 | car bot(on= 哪种地块,speed= 区间) |
| L5 氛围 | sky palette seed |
`
@legend
A tower h=70..150 form=taper roof=spire color=cyan deco=neon,antenna
b low h=10..22 form=box roof=flat color=amber
. road , walk T park deco=tree*5
@grid
..........
.,bbb,.,,.
.,bAb,.TT.
..........
`
| 好 | 坏 |
|---|---|
| 文本本身就是地图缩略图,一眼看懂 | 一个字符一格 → 上限约 200×200,再大人就读不动了 |
| 改一个字符 = 改一格,git diff 一目了然 | 字符集有限(约 60 种可打印字符 = 最多 60 种地块) |
| AI 生成得极好(它本来就擅长画字符画) | 8000×8000 = 6400 万字符,纯粹不可能 |
适用: 8×8 到 200×200 的手工关卡、剧情场景、迷宫。
`
@grid_rle
10.
1. 1, 3b 1, 1. 1, 1, 1.
1. 1, 1b 1A 1b 1, 1. 2T 1.
`
| 好 | 坏 |
|---|---|
| 大片相同区域(海、荒地、郊区)压缩率极高 | 人读不出来了 —— 方案 A 最大的优点没了 |
| 仍然是精确数据,不引入随机 | 最坏情况(棋盘格)比 A 还长 |
| 好写好解析 | 仍然是 O(面积),8000×8000 还是撑不住 |
适用: 500×500 到 2000×2000、且有大片同质区域的地图。建议只当导出格式,不当手写格式。
`
@meta size=8000x8000 seed=42 road_every=5
@zones
core = circle(4000,4000,r=900) h*=1.7 density=.95
mid = ring(4000,4000,900,2400) h*=1.3 density=.86
edge = ring(4000,4000,2400,6000) h*=0.6 density=.62
@rules
near(park,2) => deco+=tree*2
rand<.03 => h*=1.8, roof=antenna, deco+=beacon
`
| 好 | 坏 |
|---|---|
| 文本长度和地图大小完全无关 —— 6400 万格也就 30 行 | 你写完看不见结果,必须渲染出来才知道长啥样 |
| 天然支持无限生成(换 seed 就是新城) | 想精确控制某一格做不到 |
| 逻辑是自洽的(市中心高、郊区矮是规则推出来的,不是硬摆的) | 规则多了会互相打架,需要明确的优先级 |
适用: 1000×1000 以上、开放世界、无限地图。
`
@roads
main = line(0,0)->(8000,0) w=48
ring = circle(4000,4000,r=1800) w=36
grid = lattice(spacing=220, jitter=.18, curve=.05)
@parcels
subdivide(min=40, max=120, setback=6)
`
先生成道路网(可以是放射状、环状、有机弯曲的),道路围出来的多边形再切成地块,地块上再盖楼。CityEngine / Cities:Skylines 的城市生成走的就是这条路。
| 好 | 坏 |
|---|---|
| 街道形态最真实 —— 能做环路、放射路、沿河弯道,不是横平竖直的棋盘 | 实现最重(要做多边形切分、道路相交、地块朝向) |
| 地块自然大小不一,天生不呆板 | AI 手写这种文本比较吃力(几何参数不直观) |
| 是"从 8×8 玩具走向 GTA"的必经之路 | 不适合手改单个地块 |
适用: 想要真实城市感的正式项目。建议作为 v0.2 的升级方向,不是 MVP。
`
@layers
terrain = noise(scale=.002) -> water<.30, land
height = gradient(center) * noise(scale=.01)
roads = grid(80) + radial(center, 8)
style = voronoi(12) -> palette[i]
props = scatter(density=.4, on=land, avoid=roads, r=6)
`
每一层都是一个"函数",最后合成。
| 好 | 坏 |
|---|---|
| 最紧凑 —— 5 行就是一个世界,且天然无限 | 最抽象,写的人要懂噪声/Voronoi 这些概念 |
| 换生物群系只是换一层,城市/荒野/雪原一套语法通吃 | 单点可控性最差 |
| 和地形(山、河、海岸)结合最自然 | 需要一个函数库,实现量比 C 大 |
适用: 荒野 / 地形为主的世界;和 C 组合最强(E 管地形,C 管城市)。
| A ASCII | B RLE | C 规则 | D 道路图 | E 图层 | |
|---|---|---|---|---|---|
| 人能直接看懂 | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
| AI 好不好写 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
| 单格可控 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐ | ⭐ |
| 能到 8000×8000 | ❌ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 无限随机但有逻辑 | ❌ | ❌ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 实现难度(越少越好) | 1 | 2 | 3 | 8 | 6 |
推荐:A + C 混合作为 v0.1(已实现),D 或 E 作为 v0.2 升级方向。
`
@meta size=512x512 cell=34 seed=2026 road_every=5 view=62 sky=#060a14
@legend A tower h=70..150 form=setback roof=deck color=cyan deco=neon,balcony weight=1
. road , walk deco=lamp?.3,tree?.45 T park deco=tree*6,bench*2
@grid (可选)直接画的 ASCII 地图
@zones core = circle(256,256,r=52) h*=1.7 density=.95
@rules near(park,2) => deco+=tree*2
@spawn car 90 on=road speed=12..34
`
三个语法细节,都是踩过坑才定的:
1. deco=tree*4,lamp?.5 —— *n 是"放 n 个",?p 是"以 p 的概率放"。既能确定摆放,也能随机点缀。
2. h=70..150 区间随机、h*=1.7 区域倍数、h+=20 规则加成 —— 三种改高度的方式互不冲突,按 图例 → 分区 → 规则 的顺序叠加。
3. 所有随机都是 hash(seed, x, z, 用途) —— 不是 Math.random()。所以同一份文本永远长出同一座城;只重算一个区域也不会影响别处。
优先级(冲突时谁说了算): @grid 画的 > @rules > @zones > @legend 默认值。
在线试:https://metazero-terminal.pages.dev/maptext/
左边写文本,右边直接长出 3D 城市。带 4 个模板 + 一个「🎲 随机生成」按钮(每点一次吐一份新的合法 MAPTEXT,直接就是 AI 可以模仿的样例)。
| 文件 | 是什么 |
|---|---|
web/terminal/maptext/maptext.js | 纯逻辑:解析器 + 求值器 + 生成器 + 模板。没有任何渲染依赖,可以直接搬到别的引擎(Unity/Godot/Three.js 都行) |
web/terminal/maptext/index.html | 编辑器 + three.js 渲染器 |
核心 API(三个函数):
`js
import { parse, build, generate } from './maptext.js';
const model = parse(文本); // 文本 → 结构(带逐行报错)
const world = build(model, {view: 96}); // 结构 → 一格一格的具体结果(含随机求值)
const 新文本 = generate({size:[128,128]}); // 随机吐一份新的 MAPTEXT
`
build() 返回的 world.cells 就是渲染端要的一切:每格的类型、高度、颜色、形态、屋顶、装饰清单、朝向。换渲染引擎只要重写这一步。
| 检查项 | 结果 |
|---|---|
| 8×8 手画 → 3D | 10×8 格 → 17 栋楼 · 120 件装饰 · 18 个活物 · 0 报错 |
| 改 ASCII 一个字符 | 把 2 个 T(公园) 改成 A(塔) → 楼数正好 17 → 19 |
| 同种子可复现 | 连跑两遍,前 400 格的类型+高度完全一致 |
| 换种子 | seed 2026 → 2027,同样规则长出不同的城 |
| 512×512 一格不画 | 全图 262,144 格,只靠 4 条分区 + 5 条规则 长出城 |
| 分区效果对不对得上 | 实测楼高比 core:mid = 1.30(文本写 1.31)、mid:edge = 1.98(文本写 2.17)→ 误差 <12% |
| 规则真的加东西 | 「公园旁多种两棵树」开:3195 棵 → 关:893 棵 |
| 8000×8000 | 6400 万格,文本 28 行 / 1090 字;解析+求值 23ms,连建几何一共 108ms(只渲染镜头附近的窗口) |
| 换图例 = 换世界 | 荒野模板语法一字未改,只换 @legend(松/橡/岩/湖)→ 长出 5,029 个自然物 |
| 随机生成器 | 连生成 12 份,全部 0 报错、都长出了城(每份约 27 行) |
| 写错了 | 故意写 4 处错 → 逐行报出第几行错在哪,页面不崩 |
18 / 18 通过。
最强反对:「分区+规则」生成的城市,一定是"统计上合理、局部上无意义"的。
规则能保证"市中心高、郊区矮",但保证不了"这条街有故事"。GTA 之所以是 GTA,恰恰是那些手工摆的地标。
回应(也是设计里已经处理的): 这正是为什么 @grid 和 @zones 必须能叠加而不是二选一 —— 主城手画、郊区交给规则。v0.1 已经支持:画了的格子以画的为准,没画的按规则长。
可证伪锚: 如果做到 v0.2 时发现"手画区和规则区的接缝处明显违和、必须额外写一堆过渡规则",那说明这个叠加模型不够,需要升级到方案 D(道路图先行,地块再分配给手画/规则)。这一条到时能被现实检验。
已知的缺口(诚实列出,没有掩盖):
1. 先用起来:让 AI 按第 3 节的语法生成 10 张不同风格的城,粘进网页看看哪种风格对味 —— 这一步能最快暴露语法缺什么。
2. 加地形层(方案 E 的一小块):@terrain height=noise(...),让地面有起伏。
3. 升级道路(方案 D):把 road_every=5 的棋盘换成真正的道路图,这是最像 GTA 的一步。
4. 把它接回知识城市:现在的知识城市是硬编码生成的,改成读 MAPTEXT,就能用一段文本换掉整座城。
*支撑文件:web/terminal/maptext/maptext.js(纯逻辑)· web/terminal/maptext/index.html(编辑器+渲染器)*