Oh My World — 公式编写说明

目录
  一、格式
  二、变量
  三、运算符
  四、内置函数
  五、表达式示例
  六、性能与规则
  七、块
  八、组合示例
  九、注意事项
  十、专用服务器

一、格式

每层规则用分号 ";" 分隔。层规则支持以下写法：

【普通格式】
  y=<起始>..<结束>: <表达式>
  y=<层>: <表达式>               ← 单层可省略范围
  y=<起始>..: <表达式>           ← 开区间：到该维度顶部
  y=..<结束>: <表达式>           ← 开区间：从该维度底部起
  y=..: <表达式>                 ← 整个维度（等同覆盖全部高度）

  开区间的 ly 基准：y=<起始>..: 从 <起始> 起算；y=..<结束> / y=..: 从该维度
  最低 y 起算（与 biome 行的简写一致）。

  重叠与覆盖：同一 y 上后写的层覆盖先写的层（开区间同样如此）。例如
  {y=-64..0: A; y=..: B} 的结果是 B 覆盖整维（A 完全被盖住）；
  想要"基座 + 局部覆盖"，把基座写在前面：{y=..: B; y=-64..0: A}。

【let 代码块格式】
  y=<起始>..<结束>: {
    let 变量 = <表达式>;
    let 变量 = <表达式>;
    ...
    <最终表达式>
  }

  在 { } 内声明的局部变量可在后续 let 和最终表达式中使用。
  变量覆盖当前块的 x / z / ly 同名变量，退出块后恢复。
  也支持元组绑定 let (a, b) = 多返回函数(...);（见第四节「元组 let」）。

【共享 let（可选）】
  在公式内容里直接写（不放进层里）：

    let 变量 = <表达式>;

  该变量可被本维度的全部层与 biome 行复用；按声明顺序求值（后面的可以引用
  前面的），层内 let 可以覆盖同名共享变量。例：

    {overworld=
      let sea = 62;
      let hills = seedhash(floordiv(x, 40), floordiv(z, 40), 7);
      y=0..sea: hills < 0.4 ? minecraft:sandstone : minecraft:stone;
      biome: hills < 0.5 ? minecraft:desert : minecraft:plains
    }

  还可以定义"参数化 let"（宏）——调用处会在编译期直接展开，没有运行时开销，
  而且能用不同坐标重复求值（做坡度、邻域判断）：

    {overworld=
      let h(px, pz) = 64 + noise2(px, pz, 900, 1) * 30;
      let rise(px, pz) = h(px + 1, pz) - h(px, pz);
      y=-64..319: y <= h(x, z) ? (rise(x, z) > 6 ? minecraft:stone : minecraft:grass_block)
                             : minecraft:air
    }

  宏可以再调用宏（自引用会被展开深度上限拦下）；块内同名 let 会遮蔽宏参数；
  宏名不能与内置函数重名（如 slope/step 等；普通 let 不受此限制）；
  声明顺序不限（都是编译期展开）。

【注释】
  支持 // 行内注释（该行 // 之后的内容被忽略，直到行尾）：
    let base = 64 + fbm2(x, z, 600, 4, 1) * 20; // 缓起伏陆地
  注释在解析前剥离，可以放心写在任何一行末尾；// 之后的本行内容都会被忽略。

【循环格式】
  y=<起始>..<结束>: <厚度>*[<表达式>],<厚度>*[<表达式>],...

  在指定 y 范围内，按逗号分隔的组循环铺设：每组的厚度层使用对应表达式，
  组完一轮后从第一组重新开始，直到填满整个 y 范围。

  例：
  y=-64..64: 3*[minecraft:bedrock],2*[minecraft:dirt],1*[(x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete]

  效果：周期 6 层（3+2+1），从底到顶重复：3×基岩, 2×泥土, 1×棋盘格, 3×基岩... 直到 y=64。

默认预设：

  y=-64: minecraft:bedrock;y=-63..64: (x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete

【多维度（可选）】

  默认公式只作用于主世界。若要用公式改造下界 / 末地，用分节语法把各维度的
  规则分别包在 { } 里：

    {overworld=
      y=-64: minecraft:bedrock;
      y=-63..64: (x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete
    }
    {the_nether=
      y=0..5: minecraft:netherrack;
      y=6..120: seedhash(x, z, 1) < 0.5 ? minecraft:basalt : minecraft:blackstone
    }

  - 维度名：overworld（主世界）、the_nether（下界）、the_end（末地）。
    可写简写 nether / end，也可加 minecraft: 前缀。
  - 没写到的维度 = 按原版生成：下界/末地的生物群系、特征与结构照常生成。
  - 别名：{the_end=overworld} 表示末地复用主世界的公式；别名目标必须在本公式
    里显式定义（链式允许，如 {the_end=nether}{nether=...} 需 nether 节存在）。
  - 节与节之间允许空白或分号；节内仍用分号分隔各层。
  - 各维度用自己的高度：层范围超出该维度高度时自动截断（例如 y=-64..319 在
    下界只有 0..255 生效）；ly 始终是"该层声明的起点"算起的层内坐标。
  - 不写 { } 的公式只作用于主世界。

  【主世界按原版生成】新建世界时，如果公式里没有 overworld 节（例如只写了下界
  公式），创建出的世界主世界将使用原版地形生成；下界/末地按各自公式。已有世界
  不受影响（生成器在创建时固化）。想显式要（空/平）的主世界，就写一个 overworld 节。

  【须知】公式接管的是地形 / 表面 / 洞穴：生物群系、特征与结构（堡垒、末地城、
  矿物、植被等）仍按原版生成，会落进你的公式地形里。两点建议：
  - 末地建议自行绘制主岛区域——末影龙战、黑曜石柱等原版机制依赖主岛；
  - 不画基岩顶就没有顶（下界高度上限仍为 256）。

【结构与群系指令（可选）】

  在每个维度的公式内容开头，可以写方括号指令（各出现一次、顺序任意；
  例外：叠加模式的 [terrain:vanilla] 必须写在最前）：

    {the_nether=
      [structure:none]
      y=0..5: minecraft:netherrack;
      y=6..120: seedhash(x, z, 1) < 0.5 ? minecraft:basalt : minecraft:blackstone
    }
    {overworld=
      [structure:only=villages,strongholds] [biome:vanilla]
      y=-64: minecraft:bedrock;
      y=-63..64: (x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete
    }

  - [structure:...] 控制该维度的自然结构生成（默认 all = 全开）：
      [structure:all]              全开（默认，省略不写也是全开）
      [structure:none]             全关
      [structure:only=a,b,c]       白名单：只生成列出的结构
      [structure:except=a,b,c]     黑名单：除列出的外都生成
    名单里可写"结构组名"（villages、strongholds、mineshafts、nether_complexes、
    end_cities…）或"具体结构名"（village_plains、fortress、bastion_remnant…），
    两种写法可以混用；自定义/模组结构写 命名空间:名称（如 mymod:tower）。

  - 结构表（原版）：
    【主世界】villages 村庄、strongholds 要塞、ruined_portals 废弃传送门、
      mineshafts 废弃矿井、desert_pyramids 沙漠神殿、jungle_temples 丛林神庙、
      igloos 冰屋、swamp_huts 沼泽小屋、shipwrecks 沉船、buried_treasures 埋藏的宝藏、
      ocean_ruins 海底废墟、ocean_monuments 海底神殿、pillager_outposts 掠夺者前哨站、
      woodland_mansions 林地府邸、ancient_cities 远古城市、trail_ruins 古迹废墟、
      trial_chambers 试炼密室、abandoned_camp 废弃营地
    【下界】nether_complexes 下界要塞+堡垒遗迹、nether_fossils 下界化石、
      ruined_portals 废弃传送门（下界变体）
    【末地】end_cities 末地城

  - 结构位置按"公式地形"计算（叠加模式按叠加后的地形）。地表结构（村庄/神庙/
    冰屋/前哨站等）如果起点落在水面上，该结构不会生成——避免公式海洋里冒出
    陆地结构；海底结构（沉船/海底废墟/海底神殿）与地下结构（矿井/要塞/远古
    城市/试炼密室等）不受影响。埋藏结构（如埋藏的宝藏）仍按原版逻辑跟随最终
    地形与土质（软土层越厚埋得越深）。

  - [biome:...] 控制该维度的生物群系（默认保持现状：主世界平原、下界/末地原版）：
      [biome:vanilla]          该维度使用原版群系分布（按坐标生成，与公式地形无关）
      [biome:minecraft:desert] 整个维度固定为指定群系
    使用 [biome:...] 后：群系专属结构（沙漠神殿、丛林神庙、冰屋…）会按群系自然生成，
    刷怪、天气、水色随群系变化；装饰特性（树/花草/矿物等）也跟随新群系照常生成
    （超平坦基座换群系后同样会生长原版植被）。只影响新生成的区块（已生成区块不变）。

  - biome 行——按高度精细控制群系（3D：同一 xz 不同 y 可以是不同群系）：

      {overworld=
        [biome-fallback:3d]
        biome y=-64..30: minecraft:deep_dark;
        biome y=31..60: minecraft:lush_caves;
        biome: minecraft:desert
      }

      biome: <表达式>                    覆盖整个维度（最简写法）
      biome y=<起始>..<结束>: <表达式>   只覆盖这段高度（y=<层> 单层亦可）

    表达式返回群系 id（写 命名空间:名称，如 minecraft:desert；支持 let / 三元 /
    全部函数，共享 let 通用）；同一高度上后写的行覆盖先写的行。群系是 4×4×4
    一格（以格子起点坐标判定）。

  - biome 行还能直接读原版群系分布（不读公式地形、不会成环）：
      vanilla                     该格的原版群系（与 [biome:vanilla] 逐格一致）
      biome_at(t, h, c, e, d, w)  查原版群系参数表：六参为浮点气候值
                                  （temperature/humidity/continentalness/
                                  erosion/depth/weirdness，内部按原版 ×10000 量化）
    例（温度高处回落原版，否则沙漠）：
      biome: climate(temperature, x, z) > 0.3 ? vanilla : minecraft:desert
      biome: biome_at(0.3, 0.5, 0.2, 0.0, 0.1, 0.4)
    末地没有参数表（biome_at 视为未命中走回退；超平坦主世界可用——参数表由
    原版主世界预设构造，与生成器类型无关）；
    方块层里的 vanilla 仅在叠加模式下合法（见下文）——非叠加模式下写方块层会报错。

  【叠加模式（terrain:vanilla）】
  在原版噪声地形上做公式后处理：在维度内容开头写 [terrain:vanilla]
  （必须是第一条指令），其余照常写层：
      {overworld=[terrain:vanilla] [surface:vanilla+patch]
        y=surf(-30..30): …}

  语义：
    - 未被任何层覆盖的坐标保持原版（等价于隐含首层 y=..: vanilla）。
    - vanilla = 该坐标的原版方块（层顺序无关的快照）；keep = 之前各层的结果。
      结果与当前值相同则不写；同一高度后写的层覆盖先写的。
    - 默认雕刻器为 vanilla（原版含水层）；[carvers:...] 仍可显式覆盖。
    - 只适用于带原版噪声地形的维度。新建「公式化生成」世界时，主世界节写了
      [terrain:vanilla] 会自动换成原版噪声主世界；已有的超平坦世界（包括
      server_mode 的超平坦世界）仍会停用该维度公式，改用普通世界预设即可。
      叠加模式的 biome 行不能读公式地形（terrain/surfis/blockis 报错）。
    - 结构起点高度按叠加后的地形计算：被叠加压低或抬高的地形上，村庄/遗迹
      等会跟着新的地表落位。

  可用的量：
    vanilla                     原版方块值（方块层）：保持原样
    vis(方块)                   当前坐标的原版方块是否是指定方块
    vsolid / vfluid / vair      原版方块是否固体 / 含流体 / 空气
    sy / sw                     该列原版含水面高度 / 液体面高度（无水时与 sy 相同；
                                sy = 最高非空气方块，原版 WORLD_SURFACE 语义；
                                sw = 最高流体方块，无水时回落到 sy）
    curis(方块)                 surface 行：当前（补铺进行中）方块是否是指定方块
    sdist(x, z)                 到最近结构保护盒的水平距离（域内 0；无结构时很大）

  其他说明：
    - sd/sdb/wd/slope 在叠加模式的 surface 行同样可用，基于当前活方块
      （叠加层写入后的地形 + 本条与先前 surface 行的补铺结果）；curis 只在叠加
      模式的 surface 行合法（非叠加模式会报错）。
    - 层范围可写 y=surf(a..b): <表达式>——以该列 sy 为基准（a、b 可为负；
      仅叠加模式；循环层不支持）。
    - 表面档位（写在维度内容开头）：
      [surface:vanilla]           默认：只跑原版材料/表面通道
      [surface:vanilla+patch]     原版之后再用 surface 行补铺（curis 找暴露面）
      [surface:none]              不跑原版材料/表面，只由 surface 行铺
    - 结构保护：[overlay:protect-structures] 默认开。只保护"会做地形适配"的结构
      （村庄/前哨站/远古城市/要塞/试炼密室等，原版地形适配会改动地形）；海底遗迹、
      沉船、海底神殿、埋藏的宝藏等无地形适配的结构不参与保护——它们的部件在叠加
      之后才放置，高度按叠加后地形计算。保护范围内也只保留原版"实心地面"：目标为
      空气/水的写入照常执行，避免把原版水体/陆地冻结成浮空水或突出水面的地形柱。
      [overlay:protect-structures=off] 关闭后可用 sdist 自行淡出。
    - 编辑器预览是近似（没有原版地形：vanilla 按空气、sy/sw 按 0）。

  - biome 行可以读"公式地形"（只按公式层计算、不读已生成的方块，所以群系填充
    时就能用；循环层同样支持）：
      terrain(x, z)              该列表面方块的 y（含水面；全空列 = 世界最低 y - 1）
      surfis(x, z, <方块>)       表面方块是否指定方块 → 真 / 假
      blockis(x, z, y, <方块>)   指定高度处是否指定方块 → 真 / 假
    例（按地表材质分群系）：
      biome: surfis(x, z, minecraft:sand) ? minecraft:desert : minecraft:plains
    末位方块参数按方块校验（可写三元等方块表达式）；这三个函数只能出现在
    biome 行里，写进方块层会报错。

  - 反过来，方块层也可以读群系：
      biomeis(x, z, y, <群系>)   指定位置的群系是否指定群系 → 真 / 假
    例（按群系铺地表材质）：
      y=-63..64: biomeis(x, z, 64, minecraft:desert) ? minecraft:sandstone : minecraft:stone
    末位也可以是群系表达式（三元等），例如按高度切换判定目标：
      y=-63..64: biomeis(x, z, 64, y > 0 ? minecraft:desert : minecraft:ocean) ? ...
    [biome:vanilla]、[biome:<单一群系>] 与 biome 行产生的群系都能读（读的是
    该位置实际生效的群系）。只允许写在方块层里；不能与"读地形的 biome 行"
    同时出现（会成环），也不能与 [biome-fallback:2d] 组合（参考高度需要尚未
    填充的群系）。群系表达式里不能再用地形查询（terrain/surfis/blockis）。
    编辑器预览无法求值 biomeis（按假分支显示）。

  - [biome-fallback:...] 决定 biome 行没覆盖到的高度怎么办（默认 none）：
      [biome-fallback:none] 不接管：biome 行必须覆盖整个维度，否则解析报错
      [biome-fallback:3d]   按实际高度交还原版（保留原版纵向分布：地表群系带、
                            深处洞穴群系）
      [biome-fallback:2d]   取该列公式地形的表面高度，采样一次原版后覆盖整列
                            （适合"表面按原版、深层自定"的分层玩法）

    注意：biome 行与 [biome:...] 不能同时写（互斥）；带命名空间的自定义群系在
    运行时向注册表解析，不存在时该维度的群系不接管（日志会提示）。只影响新生成
    的区块。

  - [features:...] 控制该维度的群系装饰特性（树/花草/矿物/荧石/玄武岩斑块与石柱…；
    开或关都只留公式地形与结构，不细分具体装饰物）：
      [features:all]   装饰照常（默认，省略不写也一样）：写了 [biome:...] 时按新群系
                       生成原版装饰；没有群系规则时保持各维度原本行为（下界/末地照常，
                       超平坦基座本身就没有装饰）
      [features:none]  关闭全部群系装饰特性

  - [carvers:...] 公式接管地形时是否放行原版雕刻器（峡谷/经典洞穴）：
      [carvers:none]     默认：取消雕刻器，公式完全接管地形
      [carvers:vanilla]  放行：原版雕刻器会在公式地形上打出峡谷/洞穴
      [carvers:vanilla-ew] 放行，但跳过流体方块本身（水与岩浆）——水中/岩浆池中
                        不会被掏出洞穴/峡谷（流体保持完整）；流体下方的固体
                        （海床、下界岩层及其下方）照常雕刻。下界的岩浆海/岩浆池
                        不会被原版雕刻器掏空（ew = except water/lava）。
                        叠加模式下，只有公式实际改写过的区块才关闭原版含水层
                        （洞穴只留空气）；接缝外未改写的原版区块仍走原版 carver/
                        含水层。装饰阶段后清除公式水体之外的 plain water（spring、
                        lush 池等）
    所有维度都支持：
    - 噪声维度（普通世界 / 超平坦的下界·末地节）按该维度原版设置雕刻；
    - 超平坦主世界也可——用"干燥代理"跑原版雕刻器：
      洞穴只留空气、不灌地下水，y=-54 以下仍是岩浆（与原版深部岩浆一致）。

  - 注意：
    - [structure:...] 只影响新生成的结构；/locate 与末影之眼仍按原版理论位置计算。
    - 主世界默认群系是平原，很多结构（如沙漠神殿）需要对应群系才会生成——
      想让它们都能出现，可以配合 [biome:vanilla]。
    - 新建的世界默认全部结构开启；已有世界的结构覆盖表在创建时固化，
      [structure:...] 只能减少、不能增加。
    - 省略指令即保持默认行为。

二、变量

  x    世界 X 坐标（整数，可正可负）
  z    世界 Z 坐标（整数，可正可负）
  ly   层内相对 y 坐标（从 0 开始，即 ly = 当前 y - 层起始 y）
  y    绝对 y 坐标（整数；= ly + 层起始 y）——地层/洞穴公式建议直接用 y
  seed 当前世界的种子（整数；同一世界内恒定，不同世界一般不同）

       用 seed 可以让同一公式在不同世界呈现不同结果。注意：种子按 64 位保存，
       公式按双精度小数运算（约 53 位有效），仅低位不同的种子可能得到相同值；
       需要完整精度时请改用 seedhash()。

  spawnx 出生点的 X 坐标（整数，取"出生区块中心"）
  spawnz 出生点的 Z 坐标（整数）

       用于把局部构造摆在出生点附近，例如以出生点为圆心、半径 20 的圆盘：
         (x - spawnx) * (x - spawnx) + (z - spawnz) * (z - spawnz) < 400
       取"出生区块中心"：世界创建时游戏先选定出生区块并写下中心，再扫描安全
       落点作为最终重生点——最早生成的出生点周边区块读到的就是中心值，之后
       所有区块也用它，因此不会出现"接缝"。它与最终重生点通常相差不超过一个
       区块。各维度同值（下界/末地的公式也指向同一个出生点）。

三、运算符

  算术：     +  -  *  /  %
  比较：     ==  !=  <  >  <=  >=
  逻辑：     &&  ||  !
  分组：     ( )

  三元条件： 条件 ? 值A : 值B

四、内置函数

  函数调用格式： 函数名(参数1, 参数2, ...)

  【整数运算】
  floordiv(a, b)   向负无穷取整除法，等价 Math.floorDiv(a, b)
  floormod(a, b)   向负无穷取模，结果始终 >= 0，等价 Math.floorMod(a, b)

  【通用数学】
  abs(x)           绝对值
  max(a, b)        取较大值
  min(a, b)        取较小值
  floor(x)         向下取整
  ceil(x)          向上取整
  round(x)         四舍五入
  sign(x)          符号函数（正数返回 1，负数返回 -1，0 返回 0）
  sqrt(x)          平方根
  pow(a, b)        幂运算 a^b
  exp(x)           e^x
  log(x)           自然对数 ln(x)
  log10(x)         以 10 为底对数

  【三角函数（参数 / 返回值均为弧度）】
  sin(x)           正弦
  cos(x)           余弦
  tan(x)           正切
  asin(x)          反正弦（返回值 -π/2 ~ π/2）
  acos(x)          反余弦（返回值 0 ~ π）
  atan(x)          反正切（返回值 -π/2 ~ π/2）
  todeg(rad)       弧度转角度
  torad(deg)       角度转弧度

  【随机】
  rand()                     从所有已注册方块（排除空气）中等概率随机选取一个
  rand(b1, b2, ...)          白名单模式——从指定的方块列表中随机选取
  randexcept(b1, b2, ...)    黑名单模式——从全部方块中排除指定的方块后随机选取

    - rand() 基于 (x, z, y) 坐标哈希生成"伪随机"值——同一坐标永远返回同一方块，
      不会闪烁变化。每个坐标的随机结果在创建时即确定且不会改变。
    - 注意：rand() 不使用世界种子——同一公式在任何世界的结果都一样。
      需要"随世界种子变化"的图案请用下面的 seedhash()。

  【种子散列】
  seedhash(a, b, ...)   把世界种子与全部参数（参数可为 0 个）做散列混合，
                         返回 [0,1) 的小数。同一世界、同一参数永远返回同一值；
                         不同世界一般不同——适合做"随世界种子变化"的噪声。

    - 例：seedhash(x, z, 0) < 0.5 ? minecraft:stone : minecraft:dirt
      （不同种子创建的世界图案不同；同一世界内完全确定、不会闪烁。）
    - 参数建议传整数格坐标（如 x、z 与通道号），便于构造格子类噪声：
      先 floordiv 到格子，再插值/多通道混合即可做光滑噪声。

  【平滑噪声（读取世界种子；连续、平滑，做地形/洞穴/岛屿）】
  noise2(x, z, 尺度, 盐)               2D 平滑噪声，[-1,1]
  noise3(x, y, z, 尺度, 盐)            3D 平滑噪声，[-1,1]
  fbm2(x, z, 尺度, 层数, 盐)           2D 多倍频叠加（层数 1..8），[-1,1]
  fbm3(x, y, z, 尺度, 层数, 盐)        3D 多倍频叠加，[-1,1]
  worley2(x, z, 尺度, 盐)              细胞噪声的最近特征点距离，[0,1]
  worley3(x, y, z, 尺度, 盐)           3D 细胞噪声，[0,1]
  worley2f2(x, z, 尺度, 盐)            第二近特征点距离 F2，[0,1]
  worley2edge(x, z, 尺度, 盐)          F2 - F1：细胞边缘线（≈0 处是边界），[0,1]

    - 尺度 = 特征尺度（单位：方块）：值越大、特征越大；换"盐"就换一套图案。
    - 与 seedhash 的分工：逐方块随机斑点/概率用 seedhash/rand；
       连续起伏的地形、洞穴、岛屿用 noise/fbm/worley。
    - worley2f2 / worley2edge 做"细胞边界/裂纹"：edge 接近 0 处连起来就是
       边界线（如 < 0.06 铺裂纹方块）；f2 是"双点距离"场。目前只有 2D 版。
    - 算法稳定：同一公式与种子始终生成相同的世界。

  【噪声变体（算法同样稳定）】
  fbm2(x, z, 尺度, 层数, 盐, 倍频, 增益)      2D 多倍频：倍频∈[1.01,4]、增益∈[0,1]；
                                             每层错开晶格（减少轴向网格感）
  fbma2(x, z, 尺度, 盐, 振幅0, 振幅1, ...)    自定义振幅列表（1..8 个）；按 |振幅| 之和
                                             归一，[-1,1]
  ridged2(x, z, 尺度, 层数, 盐, 锐度)         山脊多重分形（1-|噪声|），[0,1]
  billow2(x, z, 尺度, 层数, 盐, 锐度)         圆丘多重分形（|噪声|），[0,1]
  fbm2e(x, z, 尺度, 层数, 盐, 侵蚀)           梯度衰减 fbm：坡陡处细节被抑制（侵蚀感）；
                                             侵蚀=0 时与 fbm2 完全一致，[-1,1]

    - 锐度越大山脊/圆丘越尖（1..8）；倍频/增益用于调整层间距与强度
       （如 倍频=2.2、增益=0.55 让细节更细碎）。

  【样条映射（自定义曲线；点按位置升序，越界取端点值）】
  spline(v, p0, v0, p1, v1, ...)    分段线性：过所有点、直来直去
  cspline(v, p0, v0, p1, v1, ...)   Catmull-Rom：过所有点、曲线更顺滑（可轻微过冲）

    - 用途：把噪声（或任意数值）按自定义曲线映射成高度/厚度，比多层三元可读：
       let h = 64 + spline(noise2(x, z, 1200, 1), -1, -30, -0.2, -6, 0.2, 8, 1, 60);
    - 至少两组"位置 值"点对（参数个数为 ≥5 的奇数）；位置必须升序
       （位置全为数字字面量时会提前校验）。算法稳定。

  【元组 let（多返回函数：一次调用返回多个分量）】
  let (u, v) = warp2(x, z, 尺度, 幅度, 盐)        2D 域扭曲：u、v 是坐标偏移量
  let (u, v, w) = warp3(x, y, z, 尺度, 幅度, 盐)  3D 域扭曲偏移量
  let (值, gx, gz) = noise2g(x, z, 尺度, 盐)      平滑噪声值 + 解析梯度（每格变化率）
  let (f1, f2, 细胞, px, pz) = worley2c(x, z, 尺度, 盐)
                                                   最近/次近距离、细胞随机值 [0,1)、
                                                   最近特征点坐标（方块）
  let (gx, gz) = grad(表达式)                     任意表达式的中心差分梯度（步长 1 格）

    - 写法：let (名字, 名字, ...) = 多返回函数(...);  名字个数必须等于分量个数；
       多返回函数只能写在元组 let 的右边（写在别处会报错）。
    - 典型用法（把坐标"揉皱"后再采样噪声）：
         let (u, v) = warp2(x, z, 220, 40, 7);
         let base = fbm2(x + u, z + v, 900, 4, 1);
    - 分量与元组同享"与高度无关"的判定：不依赖 ly 时每列只算一次（同普通 let）。

  【常用助手】
  clamp(v, 低, 高)             夹取到 [低, 高]
  lerp(a, b, t)                线性插值 a + (b - a) * t
  smoothstep(t)                0..1 输入的三次平滑
  map(v, a, b, c, d)           把 [a,b] 线性映射到 [c,d]（原版 Mth.map）
  waterline(x, z, 水位)        该列的水位面高度：水位 ± 缓变噪声（特征约 512 格、
                               实际幅度约 ±3 格，随世界种子变化）。用来写
                               "低地淹水 / 洞穴蓄水"（与地形高度比较即可）。
  atan2(a, b)                  两参数反正切（Math.atan2(a, b)）
  fract(v)                     小数部分（负数也返回 [0,1)）
  step(边, v)                  v < 边 为 0，否则 1
  smootherstep(t)              0..1 输入的五次平滑（比 smoothstep 更缓）
  tanh(v)                      双曲正切
  hypot(a, b)                  √(a² + b²)
  bias(v, b) / gain(v, g)      Schlick 偏置 / 增益：塑形概率与曲线；b=g=0.5 时恒等
  saturate(v)                  夹取到 [0,1]
  select(条件, 真值, 假值)      与三元表达式等价
  terrace(v, 台阶数, 锐度)      阶地：把连续值变成阶梯（台阶高 1/台阶数，
                               锐度 1..256 控制台阶边缘陡峭度）
  min(a, b, c, ...)            最小/最大也支持多参数直接折叠

  【循环与空间助手】
  sum(k, a, b, 表达式)         循环求和：k 依次取 a..b（a、b 必须是整数字面量）。
                              如 sum(k, 0, 3, k * x) = 0*x + 1*x + 2*x + 3*x
   min/max(k, a, b, 表达式)     循环形式的最小/最大（4 参且首参是变量名时按循环处理）
  shift(表达式, dx, dz)        把表达式里出现的 x、z 换成 x+dx、z+dz
                              （dx、dz 里的 x、z 在原点求值）
  slope(表达式)                √(gx²+gz²)：中心差分坡度（步长 1 格）
  curv(表达式)                 拉普拉斯：二阶差分之和（山脊/沟谷处显著）
  isodist(表达式)              |值| / |梯度|：到零等值线的距离估计
                              （河流用法：isodist(噪声 - 阈值) < 河宽 → 河床）

    - 循环在编译期展开（单层 ≤64 次、嵌套总数 ≤64）；循环变量会遮蔽同名变量。
    - 空间助手用"步长 1 格"的中心差分；参数表达式会在偏移位置重新求值
       （外层的 let 绑定按中心列的值参与差分）。
    - min/max 的 4 参循环形式与多参数折叠：首参是变量名、a/b 为整数字面量、
       且表达式引用该变量时 → 循环；否则按 4 参折叠（例：min(h, 62, 70, y)）。

  【网格采样缓存与平滑（3D 密度地形省算力）】
  cache2d(表达式) / cache2d(表达式, 步长)          世界对齐网格的双线性插值
                                                   （步长 1..16，默认 4）
  cache3d(表达式) / cache3d(表达式, sx, sy, sz)    三维网格三线性插值
                                                   （默认 4, 8, 4；角点用绝对 y）
  blur2(表达式, r) / blur2(表达式, r, 步长)        网格点上的 (2r+1)² 盒卷积
                                                   （r 1..4；步长默认 4）

    - 值 = 网格角点上的表达式求值 + 插值；网格与世界坐标对齐、跨区块无缝，
       同一角点只算一次（各线程本地缓存）。
    - 表达式必须自包含：只能用内建量（x/y/z/ly/seed/spawnx/spawnz），
       不得引用 let 绑定，也不能用 terrain/surfis/blockis/biomeis/rand；
       cache2d 与 blur2 不得引用 ly 与 y，cache3d 不得引用 ly（可以用 y）。
    - blur2 的网格点值 = 以该点为中心、半径 r（间距 = 步长）的 (2r+1)² 个格点的
       表达式平均；查询点用相邻四点双线性插值——用来把 0/1 掩膜、河网距离等
       平滑成柔和过渡（例：blur2(riverMask, 3, 8) ≈ 24 格平滑带）。
    - 平滑尺度 = r × 步长 格：小于该尺度的细节被抹平；再套 0.4/0.6 之类阈值
       取色时，过渡带宽度与平滑尺度同量级（r=3、步长=8 → 过渡带约 15 格；
       r=4、步长=16 → 约 60 格）。默认 r=2、步长=4 只抹平 ~8 格内的细节。
    - 宏会在自包含检查之前展开：在 cache/blur2/rivernet 的表达式里可以调用
       宏（展开后仍须满足自包含）。
    - 适合"每格都算很贵的场"降采样（如 3D 密度、大尺度噪声）。

  【表面通道（surface 行：实心之后按深度/水/坡度铺面）】
   surface y=<a>..<b>: <表达式>       该高度范围内应用表面规则
   surface: <表达式>                 整维简写
   surface[maxdepth=N] y=..: ...     只对 sd <= N 的方块求值（1..64，默认 8）

    - 时机：公式地形铺完之后、写入区块之前（在雕刻器与特征之前，与原版一致）。
    - 表达式返回方块或 keep（保持当前方块）；同一 y 后写覆盖先写。
    - 变量（只在 surface 行合法）：
         sd     本方块上方连续实心数（顶面 = 0）
         sdb    本方块下方连续实心数（洞顶/洞底判断）
         wd     无水 = -1；否则上方连续流体块数（≥1）
         slope  本列顶面与四邻列顶面的最大高差（格距 1）
       （无括号的 slope 是这里的列量；slope(表达式) 是函数——按上下文区分）
    - 实心 = 非空气且非流体；邻列顶面取自表面修改前的地形
       （跨区块一致、与规则顺序无关）。
    - surface 行也可以读群系（biomeis(x, z, y, 群系)）——读的是当前已填充的群系。
    - 编辑器预览与结构落点按表面修改前的公式地形计算（surface 只在区块生成时生效）。
    - 例（水下铺沙 + 陡坡露石 + 其余保留）：
         surface wd >= 1 && sd == 0 ? minecraft:sand : keep;
         surface slope > 2 && sd <= 1 ? minecraft:stone : keep

  【河网（rivernet：非局部河流；配合 min 使用）】
  let (距离, 半宽, 水面, 流量) = rivernet(cs, 盐)       默认高度场
  let (…) = rivernet(coarse, cs, 盐)                   自定义高度场（列表达式）
   可在盐后追加可调参数：(…, w0, wk) 半宽基数/流量斜率；(…, w0, wk, warp) 再调蜿蜒幅度。
   全形式：rivernet(cs, 盐[, w0, wk[, warp]]) / rivernet(coarse, cs, 盐[, w0, wk[, warp]])。

    - 抖动网格节点 + 严格下降的下游 + 深度 3 的流量累积 →
       半宽 = w0 + wk·√流量（默认 w0=3、wk=1）。
    - 返回：距离=到最近河中线的距离；半宽=该处河道半宽；水面=水面高度
       （节点海拔沿线插值 − 3）；流量=上游累积量。
    - cs ∈ 64..512（整数字面量，节点间距：越大河网越稀、支流越少）；
       盐/w0/wk/warp 为数字字面量（w0/wk/warp ≥ 0）；
       海拔 ≤ 62 的节点是终点（汇入海）。省略 coarse 时用内部高度场
       （62 ± 28 的多倍频噪声）。warp = 蜿蜒幅度（格，默认 0.12×cs；0 = 直线折线）。
    - 外观：河道按"排水树"绘制——每个汇水盆地一棵（在汇流处相接），
       彼此之间隔着分水岭（没有河），这是设计形态而非缺陷；疏密 ≈ cs。
       想要更长、连成网络的河流，给 coarse 加大尺度坡度（例：径向坡
       76 - sqrt(x*x+z*z)*0.015），让水从高处一路流向海。
    - coarse 必须自包含（只能用内建量；不引用 ly/y；不含视图/随机函数）。
    - 附近没有河道时：距离=1e9、水面=1e9 —— 配合 min(base, …) 即"不接管"。
    - 例（河道 + 河床 + 水体；coarse 用与 base 同源的高度场，水面不超过地形太多）：
        let base = 60 + fbm2(x, z, 600, 4, 1) * 20;
        let (d, w, s, o) = rivernet(60 + fbm2(x, z, 600, 4, 1) * 20, 192, 7);
        let surf = min(s, base + 2);
        let bed = min(base, surf - 4 + max(d - w, 0) * 0.7);
        y=..: y <= bed ? (d < w && y > bed - 3 ? minecraft:sand : minecraft:stone)
             : (d < w && y <= surf ? minecraft:water : minecraft:air)
    - 调参：w0 控制最小河宽、wk 控制汇流后变宽的速度；warp 越大河道越蜿蜒。
       两处 fbm2 必须一致（同一高度场），否则水面与地形不同源会出现水柱。

  【共享气候（生成与编辑器预览均可用）】
  climate(字段, x, z)          原版气候值（约 [-1,1]；2D 形式在 y=63 采样）
  climate(字段, x, y, z)       任意高度（多支持 depth）
  peaks(w)                     原版峰谷折叠 -3·(|(|w| - 2/3)| - 1/3)

    - 字段：temperature / humidity / continentalness / erosion / weirdness / depth
       （字段名是关键字，不能写成变量；数值为原版量化值，步进 1e-4，
       按原版 4 格分辨率采样：同一 4×4×4 格内取值相同）。
    - 地形直接用会有 4 格台阶——建议包一层 cache2d 做双线性平滑，例如
       cache2d(climate(temperature, x, z), 4)。
    - 取值来自该维度的原版气候采样器（与 F3 的 NoiseRouter 行一致；
       下界/末地用各自维度设置）；同一套参数同时驱动地形、群系与河流。
    - 编辑器预览按创建界面的种子本地构建气候（与最终世界一致）；
       没有气候视图的环境返回 0。

  【原版密度函数 / 噪声 / 地表高度（生成与编辑器预览均可用）】
  df(注册名, x, y, z)          按注册名调用原版/数据包密度函数
                                （如 minecraft:overworld/ridges；单点求值）
  noise(注册名, x, y, z[, xzScale[, yScale]])
                                按注册名调用原版噪声（如 minecraft:temperature）
                                xzScale 作用于 x/z，yScale 只作用于 y（省略为 1）
  vheight(x, z)                原版地表高度估计（finalDensity 自顶向下扫描 + 二分）

    - 注册名要带命名空间（数据包里的条目也能用）；找不到的条目返回 0，
       每个缺失 id 只记一次日志。
    - df/noise 单点求值没有原版缓存——逐方块直接调用非常慢，务必用
       cache2d / cache3d 包一层，例如固定高度：cache2d(df(minecraft:overworld/ridges, x, 63, z), 4)
       （cache2d 不能引用变量 y；要按高度变化请用 cache3d）。
       vheight 自带列缓存（同一列只扫描一次），逐方块调用没有问题。
    - vheight 描述的是原版噪声地形（超平坦世界按该维度噪声设置估计），
       与公式层/叠加后的地形无关；整列无实心时返回维度最低 y。
    - vheight 每列要做几十次 finalDensity 采样（列缓存只保证同列一次）——
       逐列大量调用会明显拖慢生成；只在需要跨列决策处使用，
       或用格点判断（如 floormod(x, 8) == 0）缩小调用面。
    - 数值随该维度的噪声设置与种子而定（与 F3 对应条目一致）。

   - 三角函数的参数单位是弧度。如需使用角度，用 torad() 转换：
     例：sin(torad(30)) = 0.5

   - 涉及格子类图案（/ 和 % 运算）务必使用 floordiv 和 floormod 替代 / 和 %，
     否则负坐标区域会出现异常尺寸的格子。详见第五节示例。
五、表达式示例

  示例：均匀层（所有坐标返回同一个块）

    y=-64: minecraft:bedrock

  示例：1×1 棋盘格（相邻方块交替）

    y=-64..64: (x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete

  示例：3×3 格子（大格子交替，无中心交换）

    y=-64..64: (floordiv(x,3)+floordiv(z,3))%2==0 ? minecraft:white_concrete : minecraft:gray_concrete

    - 不能用 ((x/3)+(z/3))%2——负数除法向零截断，原点附近会形成 5×5 异常格子。

  示例：3×3 格子 + 中心交换（中心块取对面格子的颜色）

    y=-64..64: (floordiv(x,3)+floordiv(z,3))%2==0
      ? (floormod(x,3)==1 && floormod(z,3)==1 ? minecraft:gray_concrete : minecraft:white_concrete)
      : (floormod(x,3)==1 && floormod(z,3)==1 ? minecraft:white_concrete : minecraft:gray_concrete)

  示例：沿 X 方向每 4 格换色（条纹）

    y=-64..64: floordiv(x,4)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete

  示例：沿 Z 方向的单格条纹

    y=-64..64: (x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete

    注：1×1 棋盘格用 + 即可，不涉及 / 或 % 大数，无需 floordiv。

  示例：半径 20 格以内的圆形，圆形区域放白色，以外放灰色

    y=-64..64: (x*x+z*z)<=400 ? minecraft:white_concrete : minecraft:gray_concrete

  示例：多色棋盘格（4 种颜色，按 x,z 奇偶）

    y=-64..64: x%2==0 ? (z%2==0 ? minecraft:white_concrete : minecraft:red_concrete)
                     : (z%2==0 ? minecraft:blue_concrete  : minecraft:yellow_concrete)

  示例：正弦波条纹（灰度变化）

    y=-64..64: (x+z*3)%6<3 ? minecraft:white_concrete : minecraft:gray_concrete

  示例：绝对值的图案（菱形区域）

    y=-64..64: (abs(x)+abs(z))<=20 ? minecraft:white_concrete : minecraft:gray_concrete

  示例：正弦波条纹（连续灰度渐变，需搭配彩色块呈现）

    y=-64..64: sin(torad(x*10))>0 ? minecraft:white_concrete : minecraft:gray_concrete

    每个周期约 36 格（360° / 10° = 36）。

  示例：正弦波

    y=-61: abs(z - (sin(x / 5.0) * 5)) <= 1 ? minecraft:white_concrete : minecraft:light_gray_concrete

  示例：圆形波纹（同心圆）

    y=-64..64: sin(sqrt(x*x+z*z)*0.5)>0 ? minecraft:white_concrete : minecraft:gray_concrete

  示例：循环层：3 层基岩 + 2 层泥土 + 1 层棋盘格交替（周期 6 层）

    y=-64..64: 3*[minecraft:bedrock],2*[minecraft:dirt],1*[(x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete]

  示例：循环层：1 层白混凝土 + 1 层灰混凝土交替（每层都换色）

    y=-64..64: 1*[minecraft:white_concrete],1*[minecraft:gray_concrete]

    效果与 (x+z)%2 棋盘格不同——这里是水平方向统一颜色，逐层交替。
    棋盘格是同一层内方块交替。

  示例：循环层 + 普通层混用

    y=-64: minecraft:bedrock
    y=-63..64: 2*[minecraft:dirt],1*[(x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete]

    第 -64 层为基岩，第 -63~64 层按 2 泥土 + 1 棋盘格循环。

  示例：指定池随机：棋盘格白色部分随机替换为 3 种方块之一

    y=-64..64: (x+z)%2==0 ? rand(minecraft:white_concrete, minecraft:dirt, minecraft:bedrock) : minecraft:gray_concrete

  示例：全方块随机（整个层从全部注册方块中随机抽取）

    y=-64..64: rand()

  示例：条件随机 + 不同随机池

    y=-64..64: (floordiv(x,4)+floordiv(z,4))%2==0 ? rand(minecraft:stone, minecraft:cobblestone) : rand(minecraft:oak_planks, minecraft:glass)

  示例：黑名单随机：从全部方块中排除石头、泥土、草方块后随机

    y=-64..64: randexcept(minecraft:stone, minecraft:dirt, minecraft:grass_block)

  示例：立体模型（ly 用于 y 方向，配合 air 可实现镂空）

    y=-64..64: (x+z)%2==0 && ly<10 ? minecraft:white_concrete : minecraft:air

    只在棋盘格的白色位置生成 10 层高的立柱，其余为空气。

  示例：随世界种子变化的图案（同一公式，不同种子的世界各不相同）

    y=-64..64: seedhash(x, z, 0) < 0.5 ? minecraft:white_concrete : minecraft:gray_concrete

    做格子类噪声时先把坐标 floordiv 到格子，例如：
    y=-64..64: seedhash(floordiv(x,3), floordiv(z,3), 0) < 0.5 ? minecraft:white_concrete : minecraft:gray_concrete

  示例：多维度（主世界棋盘格 + 下界公式 + 末地复用主世界）

    {overworld=y=-64: minecraft:bedrock;y=-63..64: (x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete}
    {the_nether=y=0..40: seedhash(x, z, 7) < 0.5 ? minecraft:basalt : minecraft:blackstone}
    {the_end=overworld}

    末地没有写公式内容，而是用别名复用主世界的公式（各维度仍按自己的高度范围）。

  示例：let 代码块——避免重复计算，提升复杂公式可读性

    y=-64..319: {
      let cellX = floordiv(x, 192);
      let cellZ = floordiv(z, 192);
      let cellSeed = cellX*37.1 + cellZ*91.7;
      let offX = sin(cellSeed)*40;
      let offZ = cos(cellSeed*1.3)*40;
      let dx = x - (cellX*192 + 96 + offX);
      let dz = z - (cellZ*192 + 96 + offZ);
      dx*dx + dz*dz < 400 ? minecraft:white_concrete : minecraft:air
    }

    将长公式中的重复子表达式提取为变量，逻辑清晰易维护。

  示例：元组 let——一次拿到多个返回值（域扭曲）

    y=-64..319: {
      let (u, v) = warp2(x, z, 240, 45, 7);
      let h = 64 + fbm2(x + u, z + v, 900, 4, 1) * 26;
      y <= h ? minecraft:stone : minecraft:air
    }

    warp2 返回两个偏移量，把坐标"揉皱"后再采样噪声，地形边缘更自然；
    多返回函数只能这样接收：let (名字, 名字, ...) = 多返回函数(...)。

  示例：出生点附近的局部构造（spawnx / spawnz）

    y=-64..319: {
      let dx = x - spawnx;
      let dz = z - spawnz;
      dx*dx + dz*dz < 400 ? minecraft:white_concrete : minecraft:air
    }

    以世界出生点为圆心生成半径 20 的圆盘。想做"出生点纪念建筑 / 塔基"这类
    以出生点为中心的构造时，不必手抄坐标，直接用 spawnx / spawnz。

六、性能与规则

  - 噪声/细胞函数是稳定契约：同公式 + 同种子始终生成相同的世界；
  - 地层与大面积噪声尽量放进共享 let（与 ly 无关的部分自动按列缓存）；
  - 逐方块随机斑点继续用 seedhash/rand，连续起伏用 noise/fbm/worley；
  - 往地形里挖洞（写 air 的分支）时，记得在末尾保留"原方块"分支——
    整列写成 air 会把地形挖光；
  - 只作用于局部的重计算要放进三元 / && / || 的分支里（引擎是惰性、短路的），
    并用廉价的包围盒/距离判断挡在最前——不需要计算的位置就真的不会算；
  - 共享 let 是惰性的：没被用到的绑定不计算；只在某个分支里用到的绑定，
    只有走到该分支的列才会算。与 ly 无关的部分按列缓存；只经气候（4×4×4
    量化数据）依赖高度的部分按 4×4×4 格缓存；
  - 层顺序（后写覆盖先写）不影响计算量，层的 y 范围与分支结构才决定成本；
  - 编辑器预览按"当前种子"渲染：创建世界界面里打开编辑器时会取种子框里的
    种子（空种子 = 界面当前的随机种子，创建时用的就是它）；改过种子后重新
    打开编辑器即可同步。进入世界后预览与真实种子一致。rand() 不依赖种子。

七、块（Block）

块用标准 Minecraft 资源 ID 表示：

  minecraft:white_concrete
  minecraft:gray_concrete
  minecraft:bedrock
  minecraft:dirt
  minecraft:grass_block
  minecraft:stone
  minecraft:glass
  minecraft:cobblestone
  minecraft:oak_planks

支持的颜色混凝土（switch 常量，无需注册表）：
  white_concrete, gray_concrete, black_concrete, red_concrete,
  blue_concrete, yellow_concrete, green_concrete, orange_concrete,
  purple_concrete, light_gray_concrete, light_blue_concrete,
  magenta_concrete, lime_concrete, pink_concrete, cyan_concrete,
  brown_concrete

其他块通过 BuiltInRegistries.BLOCK 注册表查询。

八、组合示例

  示例：1 层基岩 + 10 层泥土 + 1 层草方块 + 117 层 3×3 棋盘格

    y=-64: minecraft:bedrock;y=-63..-54: minecraft:dirt;
    y=-53: minecraft:grass_block;
    y=-52..64: (floordiv(x,3)+floordiv(z,3))%2==0 ? minecraft:white_concrete : minecraft:gray_concrete

  示例：循环层：基岩底 + 泥棋盘循环铺满 -63~64（3 泥土,1 棋盘格...4 泥土,1 棋盘格）

    y=-64: minecraft:bedrock;y=-63..64: 3*[minecraft:dirt],1*[(x+z)%2==0 ? minecraft:white_concrete : minecraft:gray_concrete]

  示例：深板岩式地层过渡（哈希噪声 × 高度斜坡，复刻原版观感）

    y=-64..64: seedhash(x, y, z, 11) < clamp(map(y, 0, 8, 1, 0), 0, 1)
      ? minecraft:deepslate : minecraft:stone

    原理与原版深板岩一致：逐方块哈希噪声 < 概率（y≤0 恒为 1、y≥8 恒为 0，
    中间线性下降），所以过渡带里两种方块逐块交杂、不出现生硬的分界线。
    调参：map 里的 0 与 8 是过渡带的底与顶；哈希最后一个参数（11）换掉 =
    另一套随机图案；同一套路换方块名就能做各类"风化层 / 砂浆层"地层。

九、注意事项

  - 分号 ";" 与方块 ID 必须在英文输入法下输入；中文分号 "；" 不能识别。
  - 嵌套三元条件时用括号明确优先级；格子类图案（涉及 / 和 %）使用
    floordiv 和 floormod，避免负坐标出现异常。
  - ly 是层内相对坐标；绝对 y 用变量 y（= ly + 层起始 y）。地层/洞穴公式建议用 y。
  - 顶层不是平坦连续表面时，建议在创建世界时关闭“生成结构”。

十、专用服务器

专用服务器没有创建世界界面，无法通过编辑器写入公式。请在
config/ohmyworld.json 中设置 server_mode: true，公式将应用于服务器上
所有写了公式节的维度（超平坦与噪声生成器世界均可；[terrain:vanilla]
叠加模式需要噪声生成器）。server_mode 下修改公式约 5 秒自动生效（仅影响
之后新生成的区块），无需重启服务器。

  公式有两种写法（二选一）：
    "formula": "……"         直接内联。JSON 字符串里的换行必须写成 \n、
                             引号必须写成 \"，复杂公式直接粘贴很容易损坏配置。
    "formula_file": "路径"   推荐：从 txt 文件读取公式，原样支持换行/引号/
                             注释。绝对路径原样使用；相对路径先按服务器根目录
                             查找，再按 config/ 目录查找（例如
                             "config/formula.txt" 或 "formula.txt"）。
                             formula_file 非空时优先于 formula；文件被修改后
                             约 5 秒自动重载。
  配置或公式文件解析失败时，server_mode 不会用默认公式生成世界（保持原版），
  并在日志中打印具体路径/原因；修好后热加载会自动重试。

  server_mode 的公式同样支持分节语法：写了哪个维度，哪个维度被接管；
  没写的维度保持原版。服务器端主世界生成器由 level-type 决定：想要
  "主世界原版 + 公式化下界/末地"，把 level-type 设为 normal（默认）并只写
  the_nether / the_end 节即可。
  - 运行中新增/移除维度节：需要重启服务器（单人游戏退出重进）才会对已有
    生成器生效；只改各节内的公式内容则约 5 秒自动生效。
  - 复杂公式（尤其 [terrain:vanilla] 叠加 + 多条 surface 行）会让传送门落地、
    首次进入维度时变慢：服务端需要生成落点周围的区块，进入过的区域会复用
    区块并明显加快。可先用 /forceload 预生成常用落点、降低视距，并在公式里
    多用 cache2d / cache3d、减少逐方块重噪声与 surface 行。
  - 排查问题时，可以把 config/ohmyworld.json 的 "debug_logs" 设为 true：
    日志会输出每个维度的接管决定、公式的读写、主世界替换决定等
    诊断信息（默认 false，正常游玩无需开启）。
