<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Yuanhao Feng</title><description>Personal site of Yuanhao Feng. AI application development, currently a Master of IT (AI) student at the University of Melbourne.</description><link>https://me.yuanhaofeng.com</link><item><title>LLM 管意图，代码管几何</title><link>https://me.yuanhaofeng.com/blog/cubemuse-build-dsl</link><guid isPermaLink="true">https://me.yuanhaofeng.com/blog/cubemuse-build-dsl</guid><description>为什么不让模型直接输出坐标，而是设计一套 JSON 指令集，把&quot;建什么&quot;和&quot;怎么画&quot;拆给两个系统。</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;你想让 AI 造一座 Minecraft 城堡，最直接的想法是让大模型逐方块输出坐标和材质。&lt;/p&gt;
&lt;p&gt;一栋 30×30×30 的建筑约 27,000 个体素位置。即使只列非空方块，输出仍然是数千行坐标加材质，每行长这样:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[12, 5, 8, &quot;stone_bricks&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Token 成本是个问题，但只是表层。更深的问题是这堆坐标不可编辑、不可组合、无法增量修改、不可迁移。用户说&quot;把屋顶换成红色&quot;，坐标序列里没有&quot;屋顶&quot;这个东西，只有一片恰好构成屋顶的方块。&lt;/p&gt;
&lt;p&gt;CubeMuse 的做法是把&quot;建什么&quot;交给 LLM，&quot;怎么画&quot;交给确定性引擎。两个系统之间用一套 JSON 指令集通信。这篇文章讲这个分离是怎么做的，以及为什么这样分。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../assets/blog/cubemuse-build-dsl/fig1-global-architecture.svg&quot; alt=&quot;CubeMuse 全局架构: LLM 管意图，代码管几何&quot;&gt;&lt;/p&gt;
&lt;h2&gt;CubeMuse 是什么&lt;/h2&gt;
&lt;p&gt;CubeMuse 是一个浏览器端的 AI 建筑生成器，面向 Minecraft 玩家和创作者。用户用自然语言描述想要的建筑，AI 生成结构化建造方案，确定性引擎将方案渲染为体素建筑，最后导出为 Java 版(.litematic)或基岩版(.mcstructure)在游戏中使用。&lt;/p&gt;
&lt;p&gt;核心矛盾: LLM 擅长理解自然语言意图，不擅长精确空间计算。如何让两者各干自己最擅长的事，是整个系统设计的出发点。&lt;/p&gt;
&lt;p&gt;后文会频繁使用以下术语:&lt;/p&gt;
&lt;p&gt;| 术语 | 定义 |
|------|------|
| Grid | 体素网格，存储每个位置的方块，是渲染和导出的唯一真相源 |
| Palette | 调色板，将 16 位索引映射到具体方块类型和状态 |
| Chunk | 16×16×16 的存储分块，Grid 只分配非空 chunk |
| Op | DSL 中的一条建造指令，如 wall、roof、door |
| Op-log | 一份完整的 DSL 程序(JSON)，是产出 Grid 的&quot;配方&quot; |
| Blueprint | 一份建造方案 = Grid(结果) + Op-log(配方) |&lt;/p&gt;
&lt;h2&gt;为什么不让模型直出坐标&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../../../assets/blog/cubemuse-build-dsl/fig2-token-cost-comparison.svg&quot; alt=&quot;同一栋建筑: 坐标方案 vs 结构化指令方案的 token 对比&quot;&gt;&lt;/p&gt;
&lt;p&gt;用一个具体例子说明。一栋 8×5×6 的小屋，坐标方案的输出长这样:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[0,0,0,&quot;stone_bricks&quot;], [1,0,0,&quot;stone_bricks&quot;], [2,0,0,&quot;stone_bricks&quot;], ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;约 300 个非空方块，按主流 BPE 分词器每行约 15 token，总计约 4,500 输出 token。&lt;/p&gt;
&lt;p&gt;同一栋建筑用结构化指令:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{ &quot;op&quot;: &quot;room&quot;, &quot;id&quot;: &quot;main&quot;, &quot;box&quot;: { &quot;from&quot;: [0,0,0], &quot;to&quot;: [8,4,6] }, &quot;wall_material&quot;: &quot;wall&quot; }
{ &quot;op&quot;: &quot;roof&quot;, &quot;anchor&quot;: { &quot;ref&quot;: &quot;main.top_face&quot; }, &quot;size&quot;: [8,4,6], &quot;shape&quot;: &quot;gable&quot;, &quot;material&quot;: &quot;roof&quot; }
{ &quot;op&quot;: &quot;door&quot;, &quot;anchor&quot;: { &quot;ref&quot;: &quot;main.south_face&quot;, &quot;align&quot;: &quot;center&quot; }, &quot;size&quot;: [1,2,1], &quot;facing&quot;: &quot;south&quot;, &quot;material&quot;: &quot;door&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3 条 op，token 量比坐标方案低一个数量级以上。&lt;/p&gt;
&lt;p&gt;但 token 差距不是最重要的。四个系统性缺陷比成本更关键:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不可编辑。&lt;/strong&gt; 想把屋顶从深色改成红色? 坐标方案需要找出所有属于屋顶的方块逐一替换。结构化方案改 palette 一行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可组合。&lt;/strong&gt; 想复用一排窗户? 坐标方案只能复制粘贴坐标。结构化方案用 &lt;code&gt;group&lt;/code&gt; + &lt;code&gt;repeat&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无法增量修改。&lt;/strong&gt; 用户说&quot;把屋顶改成 hip 式&quot;。坐标方案里没有&quot;屋顶&quot;这个概念，只有散落的方块。结构化方案替换 roof op 的 &lt;code&gt;shape&lt;/code&gt; 参数，其余 op 不受影响。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可迁移。&lt;/strong&gt; 导出 &lt;code&gt;.litematic&lt;/code&gt; 或 &lt;code&gt;.mcstructure&lt;/code&gt; 时，坐标方案丢失所有语义。结构化方案的 palette 和命名实体能映射到任意格式。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些不只是理论推演。项目早期实测证实裸体素方案 token 成本高两个数量级且空间一致性无保证。LLM 一次调用直出完整结构的天花板同样有限(&lt;a href=&quot;https://arxiv.org/abs/2406.08751&quot;&gt;已有研究&lt;/a&gt;中 GPT-4 完整率仅 38%)。另一个曾考虑的方案是 &lt;a href=&quot;https://arxiv.org/abs/2305.16291&quot;&gt;Voyager&lt;/a&gt; 式自由代码生成。视觉天花板可能更高，但产品丢失了命名编辑手柄、调色板换肤、锚点对齐、确定性测试和 agent 的定向修订能力。CubeMuse 需要的是可编辑、可分享、可导出的蓝图，不是一次性渲染。&lt;/p&gt;
&lt;h2&gt;一套 JSON 指令集&lt;/h2&gt;
&lt;h3&gt;为什么是 JSON&lt;/h3&gt;
&lt;p&gt;三个原因。LLM 天然能生成结构合规的 JSON，不需要维护自定义文本格式的解析器。JSON Schema 提供结构校验(字段类型、枚举值、必填项、&lt;code&gt;additionalProperties: false&lt;/code&gt; 拒绝未知字段)，在此之上还有语义校验层: 检查锚点 ref 是否指向已存在的命名实体、材质角色是否已在 palette 中声明、&lt;code&gt;box.from ≤ box.to&lt;/code&gt; 等约束。两层校验叠加，引擎可以在执行前拦截非法 op。工具调用(tool_calls)直接返回 JSON 对象，省去了从自由文本中提取结构的步骤，也减少了格式出错的概率。&lt;/p&gt;
&lt;h3&gt;抽象阶梯: 三层表达力&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;../../../assets/blog/cubemuse-build-dsl/fig3-dsl-op-layers.svg&quot; alt=&quot;DSL op 词汇分层: 从结构意图到自由体素&quot;&gt;&lt;/p&gt;
&lt;p&gt;18 个 op 按抽象程度排成三层阶梯(这是本文的分法；代码库内部 ADR-0013 按语法复杂度另有一套&quot;词汇三级&quot;——shape / path / blocks，两者视角不同)。抽象递减，表达力递增。LLM 应优先使用最高层，只在必要时降级。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一层: 结构 op。&lt;/strong&gt; 说意图，引擎算几何。&lt;/p&gt;
&lt;p&gt;包含 wall、room、roof、staircase，以及 door、window、beam 等构件。不是独立 op 的概念通过参数实现: 地板和天花板是 &lt;code&gt;room&lt;/code&gt; 的 &lt;code&gt;floor_material&lt;/code&gt; / &lt;code&gt;ceiling_material&lt;/code&gt; 参数，立柱是 &lt;code&gt;beam&lt;/code&gt; 的 &lt;code&gt;axis&lt;/code&gt; 取 y。这层 op 描述建筑意图。模型说&quot;这里放一面山墙屋顶&quot;，引擎计算每个方块的朝向(东/西/南/北)、半高(上/下)和形状状态。&lt;/p&gt;
&lt;p&gt;以 roof 为例:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{ &quot;op&quot;: &quot;roof&quot;, &quot;anchor&quot;: { &quot;ref&quot;: &quot;main.top_face&quot;, &quot;offset&quot;: [0,1,0] }, &quot;size&quot;: [10,5,8], &quot;shape&quot;: &quot;gable&quot;, &quot;material&quot;: &quot;roof&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模型只写了一行。引擎做的事: 确定屋脊线位置，为两道坡面分别计算朝向，填充山墙三角形(&lt;code&gt;gable_fill&lt;/code&gt;)。出檐不是一个独立参数，而是通过把屋顶脚印开大来实现: 锚点向上偏移一格，&lt;code&gt;size&lt;/code&gt; 每个水平轴各加 2。gable 屋顶真正在变的只有 &lt;code&gt;facing&lt;/code&gt;(两个取值，各管一道坡面)，其余状态整片统一。&lt;/p&gt;
&lt;p&gt;hip 屋顶最能体现结构 op 的价值。它的四个角各需要不同的转角形状(&lt;code&gt;outer_left&lt;/code&gt;、&lt;code&gt;outer_right&lt;/code&gt;)，整个屋面的朝向、形状逐块不同。如果让模型直接输出每块楼梯的 block state，错误率极高，因为这是在把最容易出错的部分交给概率模型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二层: 参数几何 op。&lt;/strong&gt; 说参数，引擎算体素。&lt;/p&gt;
&lt;p&gt;包含 shape(sphere、dome、cylinder、cone、ellipsoid、arch、pyramid 共 7 种)、path(Bresenham 折线体素化，最多 64 路点)和 fill(实心填充)。这层不再绑定建筑语义，而是通用几何原语。shape 支持 hollow 壳体，一条 op 就能造一个空心穹顶。path 用 Bresenham 算法做 3D 折线体素化，用于桅杆、树枝、缆线等线状结构。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三层: 自由体素 op。&lt;/strong&gt; 逃生舱。&lt;/p&gt;
&lt;p&gt;只有一个 op: &lt;code&gt;blocks&lt;/code&gt;，每次限 512 个方块，超出直接拒绝。直接指定每个方块的位置和材质。抽象最低、表达力最强。用于任何上两层无法表达的细节: 雕塑的五官、线条装饰、手动编辑的记录。&lt;/p&gt;
&lt;p&gt;&quot;只在必要时降级&quot;不是靠自觉。prompt 将 &lt;code&gt;blocks&lt;/code&gt; 钉死为逃生舱(&quot;escape hatch only——always prefer structural ops&quot;)，schema 拒绝超过 512 格的调用，eval 持续监控 &lt;code&gt;blocks&lt;/code&gt; 在输出中的占比。三条一起才能保证阶梯不塌。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后处理 op。&lt;/strong&gt; 不产出新几何，而是在已有表面上工作。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;detail&lt;/code&gt; 扫描当前作用域已累积的网格，自动为建筑补上勒脚、窗台、檐口等装饰细节。引擎在每份 AI 产出的程序尾部自动追加一条 &lt;code&gt;detail&lt;/code&gt; op，prompt 据此禁止模型手动摆放这类装饰。&lt;code&gt;scatter&lt;/code&gt; 在既有表面上随机撒落植被或碎石，内建支撑检查(只落在实体表面上，绝不覆盖、绝不悬空)。&lt;code&gt;place_component&lt;/code&gt; 将预制组件放置到指定位置，相当于一次库调用。这三个和前面的关系是&quot;后处理 vs 产出几何&quot;，不是抽象高低。&lt;/p&gt;
&lt;p&gt;设计哲学: 缺的从来不是自由度，而是阶梯的低层台阶。&lt;/p&gt;
&lt;h3&gt;力量倍增器: 组合 op&lt;/h3&gt;
&lt;p&gt;group、repeat、mirror、rotate 四个 op 不自己产出几何，而是复用和变换其他 op 的输出。它们跨层工作:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;group&lt;/strong&gt; 将一组 op 封装为命名组件。子 op 在独立子网格渲染，再 stamp 到主网格。相当于函数定义。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;repeat&lt;/strong&gt; 沿向量平移复制一组 op。一面墙上的三扇窗户不需要写三遍。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mirror&lt;/strong&gt; 沿 x/y/z 平面镜像。翻转时状态感知: 自动调整方块的 facing、half、hinge 和形状手性。楼梯镜像后朝向正确，门的铰链换边。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rotate&lt;/strong&gt; 绕 Y 轴 90°/180°/270° 旋转，同样状态感知。旋转还会交换 &lt;code&gt;axis&lt;/code&gt; 的 x/z 取值，这是 mirror 不做的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对称建筑不需要分别描述两半。一半 + mirror = 完整建筑，还保证严格对称。&lt;/p&gt;
&lt;h3&gt;材质角色解耦&lt;/h3&gt;
&lt;p&gt;Palette 将角色名映射到具体方块:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;palette&quot;: {
    &quot;primary&quot;: { &quot;id&quot;: &quot;minecraft:oak_planks&quot; },
    &quot;wall&quot;: { &quot;id&quot;: &quot;minecraft:stone_bricks&quot; },
    &quot;roof&quot;: { &quot;id&quot;: &quot;minecraft:dark_oak_stairs&quot; },
    &quot;accent&quot;: { &quot;id&quot;: &quot;minecraft:stripped_oak_log&quot; },
    &quot;glass&quot;: { &quot;id&quot;: &quot;minecraft:glass_pane&quot; }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Op 引用角色名(&lt;code&gt;&quot;material&quot;: &quot;wall&quot;&lt;/code&gt;)，不引用具体方块 ID。换皮只需改 palette 一处，整栋建筑的材质联动更新。&lt;/p&gt;
&lt;p&gt;Palette 解耦的是 &lt;strong&gt;op 与方块 ID&lt;/strong&gt;，而不是模型与方块 ID。模型仍然需要认识方块: prompt 给模型列了一份方块词汇表(木料家族、石材、楼梯台阶、玻璃、16 色块、铜及其氧化变体等)，模型从中选 ID 填进 palette。Palette 省掉的是&quot;每条 op 里重复写 ID&quot;和&quot;换皮要改 N 处&quot;。prompt 要求每份方案使用 3-5 种材质角色并保持对比度。&lt;/p&gt;
&lt;h2&gt;锚点定位: 让门不再悬空&lt;/h2&gt;
&lt;p&gt;Op 设计解决了&quot;表达什么&quot;的问题。还有一个问题: op 怎么知道放在哪?&lt;/p&gt;
&lt;p&gt;CubeMuse 提供三种定位模式:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;绝对坐标&lt;/strong&gt; &lt;code&gt;box&lt;/code&gt;(&lt;code&gt;from&lt;/code&gt;/&lt;code&gt;to&lt;/code&gt;): 闭区间，适合第一个落地的 op。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;锚点&lt;/strong&gt; &lt;code&gt;anchor&lt;/code&gt;(&lt;code&gt;ref&lt;/code&gt; + &lt;code&gt;align&lt;/code&gt; + &lt;code&gt;offset&lt;/code&gt; + &lt;code&gt;inset&lt;/code&gt;)(AI 首选): 引用前一个命名 op 的面、中心或边。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Along 边数组&lt;/strong&gt;: 沿一条边以指定间距排列放置，用于窗带、柱列等重复结构。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用对比说明锚点的作用。&lt;/p&gt;
&lt;p&gt;没有锚点时，模型需要自己算门的坐标。墙从 &lt;code&gt;[0,0,0]&lt;/code&gt; 到 &lt;code&gt;[8,4,6]&lt;/code&gt;，门应该在 &lt;code&gt;[4,0,3]&lt;/code&gt;。改了墙的宽度后，门还在 &lt;code&gt;[4,0,3]&lt;/code&gt;，已经不在中心了。多轮修改后坐标漂移，门悬空，窗嵌墙里。&lt;/p&gt;
&lt;p&gt;有锚点时:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{ &quot;op&quot;: &quot;door&quot;, &quot;anchor&quot;: { &quot;ref&quot;: &quot;main.south_face&quot;, &quot;align&quot;: &quot;center&quot; }, &quot;size&quot;: [1,2,1], &quot;facing&quot;: &quot;south&quot;, &quot;material&quot;: &quot;door&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;门自动贴在 &lt;code&gt;main&lt;/code&gt; 南面中心。墙移了，门跟着移。墙加宽了，门仍在中心。&lt;/p&gt;
&lt;p&gt;锚点是 DSL 的基本保证: 门不悬空、窗户条间距均匀、屋顶紧贴墙顶。&lt;/p&gt;
&lt;p&gt;在底层实现上，命名实体(带 &lt;code&gt;id&lt;/code&gt; 的 op)执行后注册自己的边界框，后续 op 通过 &lt;code&gt;anchor.ref: &quot;&amp;#x3C;name&gt;.&amp;#x3C;part&gt;&quot;&lt;/code&gt; 引用。&lt;code&gt;&amp;#x3C;part&gt;&lt;/code&gt; 可以是面(&lt;code&gt;south_face&lt;/code&gt;、&lt;code&gt;top_face&lt;/code&gt;)、角(&lt;code&gt;min&lt;/code&gt;/&lt;code&gt;max&lt;/code&gt;)、中心(&lt;code&gt;center&lt;/code&gt;、&lt;code&gt;center_bottom&lt;/code&gt;)、楼层(&lt;code&gt;floor:N&lt;/code&gt;)或边(&lt;code&gt;edge:&amp;#x3C;dir&gt;&lt;/code&gt;)。这些引用关系构成一个依赖图，让整个建筑在多轮修改中保持内部一致性。锚点的合法性不是 JSON Schema 能管的(Schema 只知道 &lt;code&gt;ref&lt;/code&gt; 是个字符串)，而是由语义校验层检查: ref 必须指向前序已执行 op 的 id，part 必须是合法取值。&lt;/p&gt;
&lt;h2&gt;Grid 是真相，Op-log 是配方&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../../../assets/blog/cubemuse-build-dsl/fig4-data-model.svg&quot; alt=&quot;Blueprint 数据模型: Grid(真相源) + Op-log(配方)&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Grid: 唯一真相源&lt;/h3&gt;
&lt;p&gt;渲染读它，导出读它，统计读它。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;16×16×16 chunk 稀疏存储，只分配非空 chunk&lt;/li&gt;
&lt;li&gt;每个格位存 16 位 palette 索引，最多 65,535 种不同方块&lt;/li&gt;
&lt;li&gt;索引 0 = air&lt;/li&gt;
&lt;li&gt;存储结构同为 palette + 索引数组，与 &lt;code&gt;.litematic&lt;/code&gt; / &lt;code&gt;.schem&lt;/code&gt; / &lt;code&gt;.mcstructure&lt;/code&gt; 三种格式同构；导入导出时适配器按各格式要求做索引重排和字节序转换&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Op-log: 配方，不是结果&lt;/h3&gt;
&lt;p&gt;Op-log 是一份 DSL 程序的 JSON 文本。从起点状态执行一遍，确定性地产出完整 Grid。起点通常是空网格，但导入的现有建筑会编码为起点快照(边界框 + palette + RLE 压缩体素)存入程序的 &lt;code&gt;base&lt;/code&gt; 字段，后续 op 在此基础上追加。导入件与生成件在编辑、撤销、AI 修订、导出上走同一条路径。&lt;/p&gt;
&lt;p&gt;程序自带 &lt;code&gt;dsl_version&lt;/code&gt; 字段。引擎版本演进时(比如 &lt;code&gt;roof&lt;/code&gt; 的 &lt;code&gt;gable_fill&lt;/code&gt; 缺省值从 false 改为 true)，迁移层给存量程序显式补上旧缺省值，确保老图纸逐格不变、新程序享受新缺省。&quot;配方&quot;能跨版本重放，是 Op-log 作为持久表示的前提。&lt;/p&gt;
&lt;h3&gt;为什么两个都需要&lt;/h3&gt;
&lt;p&gt;只有 Op-log 没有 Grid: 每次渲染和导出都要从头回放全部 op。&lt;/p&gt;
&lt;p&gt;只有 Grid 没有 Op-log: AI 修改时只能喂回原始体素，token 直接爆炸。版本历史和建造过程全部丢失。也无法做 &lt;code&gt;replace_group&lt;/code&gt; 这类语义级的定向修改。&lt;/p&gt;
&lt;p&gt;所以 Blueprint = Grid + Op-log。两者各管一面:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Blueprint
├── Grid           ← 渲染 / 导出读这里
│   ├── Palette    ← 索引 → 方块类型+状态
│   └── Chunks[]   ← 16³ 稀疏分块
└── Op-log         ← AI 理解 / 版本对比读这里
    └── DslProgram ← JSON, 确定性回放 → Grid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grid 不存数据库。数据库只存 Op-log(程序 JSON，含起点快照)。打开蓝图时从 Op-log 回放出 Grid。这保证了两者永远一致。确定性保证: 相同的 op 序列产出相同的 Grid。这是黄金测试(golden test)的基础。&lt;/p&gt;
&lt;p&gt;有一个细节: 喂给 AI 的既不是 Grid 也不是完整 Op-log，而是 Structure Summary。这是一段 3-4 行的文本摘要，包含边界、材质分布 top-8、命名实体列表。原始体素数据从不直接进入 LLM 上下文。&lt;/p&gt;
&lt;h2&gt;双端导出: 版本中立方块模型&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../../../assets/blog/cubemuse-build-dsl/fig5-dual-edition-export.svg&quot; alt=&quot;版本中立方块到 Java 和基岩双端的导出路径&quot;&gt;&lt;/p&gt;
&lt;p&gt;内部统一用 Java 风格命名加方块状态(block states)作为中立表示，称为 NeutralBlock。这不是自创的命名空间，而是复用 Java 版的体系，因为 Java 版的方块名覆盖最全、社区工具链最成熟。&lt;/p&gt;
&lt;p&gt;在导入/导出边界，适配器做格式转换:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Java 端: &lt;code&gt;.litematic&lt;/code&gt;(Litematica)导入导出 / &lt;code&gt;.schem&lt;/code&gt;(Sponge v1-v3)仅导入&lt;/li&gt;
&lt;li&gt;基岩端: &lt;code&gt;.mcstructure&lt;/code&gt;(Structure Block)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;跨版本差异在导出时检测，生成替换报告。例如 Java 的 &lt;code&gt;quartz_pillar&lt;/code&gt; 在基岩端对应 &lt;code&gt;quartz_block[chisel_type=lines]&lt;/code&gt;，标为 &lt;code&gt;renamed&lt;/code&gt;；楼梯的 Java &lt;code&gt;shape&lt;/code&gt; 状态(转角形态)在基岩端被丢弃(基岩自动计算拐角)；词汇表外的方块原样保留名称并标为 &lt;code&gt;unsupported&lt;/code&gt;。映射表只覆盖 DSL 词汇表实际产出的方块，不嵌入完整的万行跨版本数据集。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;回到标题。这套系统拆出了四条清晰的职责线:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DSL 让模型用意图说话，引擎把意图翻译成几何。模型不碰 block state。&lt;/li&gt;
&lt;li&gt;锚点让模型引用命名实体，而不是计算坐标。结构内部一致性由依赖图保证。&lt;/li&gt;
&lt;li&gt;Palette 让模型选角色，而不是在每条 op 里重复方块 ID。换皮是一行的事。&lt;/li&gt;
&lt;li&gt;双表示让系统同时拥有语义和像素: Op-log 给 AI 和人读，Grid 给渲染器和导出器读。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套管线是 CubeMuse 所有后续设计的基底。有了它，下一步的问题是: 谁来决定调用哪些 op、以什么顺序调用、调用多少次?&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2406.08751&quot;&gt;3D Building Generation in Minecraft via Large Language Models&lt;/a&gt;, Hu et al., 2024&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2305.16291&quot;&gt;Voyager: An Open-Ended Embodied Agent with Large Language Models&lt;/a&gt;, Wang et al., 2023&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mc-bench/orchestrator&quot;&gt;mc-bench/orchestrator&lt;/a&gt;, GitHub&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>