Skip to content

Terrain Record 地形记录 ​

状态:Authoritative Reference v2 / Layered Semantics / Tail Partial

1. 先说结论:BTL 地形不是一层 ​

Terrain 的正确阅读方式不是“一个地形 ID 对应一种地形”,而是至少分成六层:

text
地图来源层
  BTL embedded Terrain / external world.bin
        |
        v
磁盘记录层
  16-byte Terrain record
  [主地形 4B][LowerDoodad 4B][UpperDoodad 4B][Raw tail 4B]
        |
        v
MapTerrain 定义层
  raw id -> def_mapterrain row -> type / variant image / layer metadata
        |
        v
TerrainType 规则层
  def_mapterrain.type -> def_terraintype
  -> movementcost / penalty_infantry / penalty_armor / penalty_artillery
        |
        v
Runtime Tile 层
  current raw record -> Tile state / derived movement-validity cost
        |
        v
表现层
  ordinary land/sea/coast + Terrain/Lower/Upper overlays
  或 external chunk base + Tile-owned overlays

这些层的 identity 不能混用:

  • raw terrain id 不是 atlas frame index;
  • def_mapterrain.type 不是 texture number;
  • def_terraintype.movementcost 是规则数据,不是渲染字段;
  • Runtime Tile layout 也不是 BTL 磁盘 layout。

2. 地图来源层:embedded 与 external ​

2.1 MapSource == 0 ​

BTL 在 Country section 后直接内嵌:

text
MapWidth * MapHeight

条 Terrain record,每条固定:

text
0x10 = 16 bytes

Android 1.30.0 当前 corpus:

text
MapSource 0 = 1073 files
embedded Terrain records = 692699

2.2 MapSource == 1 ​

BTL 不携带 Terrain section。逻辑地形来自外部地图资源。

原版 world.bin 是:

text
YSAE v4 header
-> Width * Height * 0x10 Terrain records
-> Width * Height * 0x02 Province Anchor slots

原版加载 world.bin Terrain 时仍调用与 embedded BTL 相同的 FUN_004746A4(Tile, terrainRecord) 绑定函数。因此:

embedded BTL 与 world.bin 的 Terrain 来源不同,但进入 Runtime Tile 的 16-byte raw record 合同相同。

当前 Android 1.30.0 corpus:

text
MapSource 1 = 310 files

注意:external map 的基础视觉地图可以来自预生成 chunk 图像;这不表示 Tile raw Terrain 消失。原版仍会基于 materialized Tile 提交部分 Terrain/Doodad/Area overlay。

3. 16-byte 记录:当前已闭合到 byte 级的部分 ​

早期 clean-room API 为了不抢跑语义,只公开:

text
+0x00 uint32 Raw00
+0x04 uint32 Raw04
+0x08 uint32 Raw08
+0x0C uint32 Raw0C

后续 native rendering consumer 已把前 12 字节进一步闭合为三个 4-byte slice:

OffsetSize当前名称Byte layout状态
+0x004Primary Terrain sliceid:u8, variant:u8, offsetX:i8, offsetY:i8Confirmed
+0x044LowerDoodad visual sliceid:u8, variant:u8, offsetX:i8, offsetY:i8Confirmed(visual consumer)
+0x084UpperDoodad visual sliceid:u8, variant:u8, offsetX:i8, offsetY:i8Confirmed(visual consumer)
+0x0C4Raw tail未命名Unknown

展开后:

text
+0x00 uint8 PrimaryTerrainId
+0x01 uint8 PrimaryVariant
+0x02 int8  PrimaryOffsetX
+0x03 int8  PrimaryOffsetY

+0x04 uint8 LowerDoodadId
+0x05 uint8 LowerDoodadVariant
+0x06 int8  LowerDoodadOffsetX
+0x07 int8  LowerDoodadOffsetY

+0x08 uint8 UpperDoodadId
+0x09 uint8 UpperDoodadVariant
+0x0A int8  UpperDoodadOffsetX
+0x0B int8  UpperDoodadOffsetY

+0x0C..+0x0F raw / semantic Unknown

这里的名称有意带上 visual 边界。尤其 +0x04 / +0x08:

  • 已确认它们分别驱动 Lower/Upper Doodad 静态图层;
  • 不能因此推导“这四个 byte 在所有 gameplay consumer 中只具有装饰意义”;
  • 其他 Runtime consumer 对额外 raw layer 的读取仍应按各自证据描述。

4. 三个可视 slice 的 sentinel 与对象创建 ​

原版静态 overlay 对三个 slice 的创建条件不同。

Primary Terrain ​

text
id >= 2
and variant != 255

才创建对应 Terrain image object。

因此:

  • raw id 0 / 1 仍是有效主地形 identity;
  • 但它们通常不依赖 Tile+0x88 的独立 terrain sprite;
  • 普通地图中的基础陆地/海面有独立 base-map path。

Lower / Upper Doodad ​

text
id != 0
and id != 63
and variant != 255

才创建 image object。

不满足时原版对象就是 null;Reference 状态为:

text
AbsentByRawSentinel

不能自行生成 generic hex 或 fallback texture。

5. def_mapterrain:raw id 到定义 row ​

主地形的 gameplay 绑定链从:

text
Terrain +0x00 PrimaryTerrainId

开始。

视觉 lookup 对三个可视 slice 使用同一类 exact row / variant 解析:

text
raw id
-> exact def_mapterrain row
-> raw variant selects exact <tile> child
-> child.image exact name
-> shared Resource atlas lookup
-> image object or null

Android 1.29.0 的 def_mapterrain.xml 有 24 个 sparse terrain identity:

text
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15,
16, 18, 20, 21, 22, 26, 30, 31

当前结构:

text
24 terrain rows
193 tile children
190 children with image

terrain row 的重要属性包括:

  • terrain:逻辑 MapTerrain identity;
  • type:TerrainType category;
  • layerterrain;
  • layerlowerdoodad;
  • layerupperdoodad;
  • child tile.idx / tile.image。

因此下面两种解释都已经被否定:

text
raw terrain id == atlas frame index
def_mapterrain.type == texture number

6. def_terraintype:玩法类别层 ​

主链:

text
PrimaryTerrainId
-> def_mapterrain[terrain]
-> def_mapterrain.type
-> def_terraintype[type]

def_terraintype.xml 当前有 type 0..10 共 11 个定义。其 loader 已确认读取:

text
type
movementcost
penalty_infantry
penalty_armor
penalty_artillery

当前基础定义值:

TypeReference 中文名definition movementcost运行时边界
0平原3普通基础地形
1海洋3普通 coast path 将 PrimaryTerrainId=1 识别为水类
2沙漠4raw id 2 的 Terrain sprite 走 early pass
3丘陵4基础成本 4
4山地5基础成本 5
5高山5某些战斗/模式下 Runtime 会改成 -1
6树林4基础成本 4
7森林5基础成本 5
8农田3基础成本 3
9河流6基础成本 6
10装饰0不能把 0 直接解释成“所有普通单位免费通过”;普通 movement query 当前按不可通行处理

这里必须区分:

XML definition value 与最终 Runtime movement policy 不是同一层。

例如高山的 XML movementcost=5,但原版绑定时存在条件化 -1;装饰的 definition 值为 0,而普通移动模型仍有额外合法性门。

penalty_* 也是 TerrainType 定义字段,不位于 BTL Terrain record 内。字段存在已经确认,但完整的所有模式/技能叠加规则不由本页扩张。

7. Runtime Tile 绑定层 ​

Android 1.28.0 ARM64 原版 FUN_004746A4 的核心链已经闭合:

text
16-byte terrain record
-> copy into current Tile raw terrain region

Tile current raw byte 0
-> def_mapterrain lookup
-> terrain type
-> def_terraintype.movementcost
-> Tile+0xA0 terrain movement/validity cost

所以 Tile+0xA0 不是城市 ID、战略分或 texture id,而是:

text
terrain movement cost / terrain validity cost

高山特殊分支 ​

原版至少存在以下条件化行为:

  • primary terrain type 为 5 时,特定 Battle 状态下 Tile+0xA0 = -1;
  • BattleMode == 6 的路径还会检查额外 raw terrain layer,高山可使 Tile 被强制判为无效。

因此不能只看 XML 的 movementcost=5 判断高山最终可通行性。

Building / 主对象条件 ​

原版移动成本链存在在目标 Tile 有 Building / 主对象等条件下使用平原成本的分支。最终路径成本不是“永远等于 XML movementcost”。

AI consumer ​

默认 AI 的部分目标收集 mode 检查:

text
Tile+0xA0 >= 0

当前证据将其解释为“地形对该候选路径有效”,不是给 Tile 增加战略分。

8. 当前 Terrain raw 是运行时状态,不只是开局常量 ​

Terrain raw 记录在 Runtime 中并非只读开局输入。

已确认生命周期:

text
constructor default
-> full 16-byte record copy
-> derived cost recompute
-> current record copy-out
-> battle save serialization
-> battle load / bulk restore

Primary raw byte 0 还存在跨 ARM64/x86 对齐的独立 setter:

text
if current raw0 != newValue:
    raw0 = newValue
    related16 = 0

但当前静态审计没有找到该 setter 的 executable caller / runtime function pointer producer;已知 xref 只有 unwind metadata。

因此当前门禁是:

text
current raw0 read/capture = Confirmed
save/restore current record = Confirmed
setter body semantics = Confirmed
original runtime mutation producer = MissingAuthority
production terrain mutation implementation = NoGo

这也是为什么不能因为“知道 PrimaryTerrainId”就直接宣布动态地形编辑已经安全。

9. 普通地图表现层 ​

普通 embedded 地图的基础地形不是把每个 def_mapterrain.type 直接贴成一张纹理。

原版存在独立 base path:

text
map_land
map_sea
coast / coastmask

以及 Tile-owned overlay path:

text
LowerDoodad
Terrain sprite
UpperDoodad
Area / SelectArea
Province boundary

水与岸线 ​

ordinary coast path 已确认:

text
PrimaryTerrainId == 1
-> water-class tile for coastline logic

岸线由水格提交,根据六邻格生成 6-bit coast mask。

Terrain +0x01 PrimaryVariant 还会进入 coast b variant cache key。也就是说:

同一个 byte 在 Terrain overlay 中是 variant index,在普通海岸线 consumer 中也参与岸线变体选择。

这再次说明 BTL Terrain 是多 consumer、多层语义,而不是单字段单用途。

静态 overlay 提交顺序 ​

原版两个 pass 当前闭合为:

text
early:
  LowerDoodad
  Terrain only when raw primary id == 2

late:
  if Building exists: return
  Terrain when raw primary id != 2
  UpperDoodad

因此 Building 会 suppress late Terrain / UpperDoodad,但不会 suppress early LowerDoodad,也不会 suppress raw id 2 的 Terrain sprite。

10. external map 表现层 ​

external map 的 base geography 与 ordinary map 不同:

text
map%d.bin / map%d_hd.bin
+ pre-generated map chunk images / alpha

原版不会为 external world 的全部基础地形逐格重画 ordinary land/sea/coast。

但是:

text
external base chunk
-> shared Tile overlay barrier
-> Tile-owned terrain/doodad/area layers

仍然存在。

因此以下两句话都不准确:

  • “external map 完全由 world.bin 每格直接贴图”;
  • “external map 使用 chunk 后就完全不消费 Tile Terrain”。

正确模型是基础 geography 与动态/静态 Tile overlay 分层。

11. 版本证据必须分开 ​

Evidence版本当前用途
BTL physical corpusAndroid 1.30.01383/1383 精确解析;MapSource 1073/310
def_mapterrain.xmlAndroid 1.29.024 terrain rows;218 总 element;193 tile child
def_terraintype.xmlAndroid 1.29.011 type;1.28 -> 1.29 SemanticIdentical
Terrain raw visual consumer / static overlay corpusAndroid 1.28.0前 12 byte 三 slice、sprite pass、atlas lookup
native movement / AI terrain costAndroid 1.28.0primary id -> type -> movementcost -> Runtime cost
world.bin physical formatAndroid 1.28.0 + 1.25 + UWP 1.16.1三份样本 byte-identical

1.29 的 def_mapterrain 相比 1.28 有一项表现定义变化:雪地 row 的 tail tile mapping 被裁剪,但 name/terrain/type/layerterrain 身份未改变。不能把这项表现资源变化误解成 movement / terrain penalty 规则变化。

12. 当前闭合状态 ​

层状态可以安全声明什么
BTL Terrain section 定位、数量、strideConfirmedembedded 为 width*height*0x10
external world.bin Terrain stride / bindingConfirmed同一 16-byte binding contract
+0x00..+0x0B static visual slice layoutConfirmed三组 id/variant/i8 x/i8 y
+0x00 primary gameplay bindingConfirmedraw id -> MapTerrain -> TerrainType
def_mapterrain exact row / variant image lookupConfirmedsparse id,不是 texture index
def_terraintype movement / penalty definition loaderConfirmeddefinition 层字段已知
Runtime movement / AI terrain-validity 主链Confirmed/Partial主链闭合,模式/技能长尾仍需各自证据
ordinary static Terrain/coast overlayConfirmed(已审计子路径)base 与 overlay 分层
external base + Tile overlay 分层Confirmed(静态子路径)chunk 不等于 Tile overlay 消失
+0x0C..+0x0FUnknown必须 raw-preserve
原版动态 terrain mutation producerUnknown / MissingAuthority不允许据 setter body 猜 producer
arbitrary semantic terrain writerNoGo不能把读侧闭合自动升级成任意写入安全

13. 编辑器 / writer 边界 ​

当前可以确认:

  • 未修改 Terrain record 必须 byte-preserve;
  • +0x0C..+0x0F 必须原样保存;
  • PrimaryTerrainId 同时影响 definition、movement/validity 与表现;
  • variant / offset 会影响表现,PrimaryVariant 还有岸线 consumer;
  • lower/upper slice 的视觉语义已经确认,但不能外推为完整 gameplay 语义。

当前不能声称:

text
all 16 bytes gameplay semantics closed
arbitrary terrain id is safe to write
dynamic terrain mutation lifecycle closed
all invalid id / variant combinations are original-compatible
external chunk visuals automatically regenerate after terrain edits

所以本页是完整的当前证据分层解析,不是“把 Unknown 隐藏掉”的伪 100% 语义表。

14. 主要证据入口 ​

text
reconstruction/src/WC4.Runtime.BattleData/BtlMapRecords.cs
reconstruction/src/WC4.Runtime.MapPresentation/TerrainAreaOverlayCommands.cs
reconstruction/docs/route4_round7_world_bin_physical_format_report.md
reconstruction/docs/route5_round6_movement_query_report.md
reconstruction/docs/rendering/terrain_coastline_rendering_report.md
reconstruction/docs/rendering/route8_final_frame_round11_terrain_area_overlay_report.md
reconstruction/docs/data-reference/xml/def_mapterrain.md
reconstruction/docs/data-reference/xml/def_terraintype.md
reports/action_ai_tile_a0_terrain_cost.md
reports/route5_combat_dependency_recovery/terrain_raw0_setter_authority.json
reports/rendering/route8_final_frame/terrain_area_overlay_resource_matrix.csv
reports/btl_reference/remaining_fixed_records.json

除另有说明外,本站有权许可的原创文档采用 CC BY-SA 4.0;第三方内容除外。