msw-behaviourtree

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MSW BehaviourTree

MSW 行为树

End-to-end authoring skill for MSW
.behaviourtree
files. Owns both the project-specific authoring spec (
<ProjectRoot>/.behaviourDocs/bt-spec.md
) and the tree generation itself. Fixed graph rules and skeletons live in this skill's
references/
; the per-project spec is (re)built by this skill's local
scripts/build-spec.cjs
.

这是用于MSW
.behaviourtree
文件的端到端编写技能。同时负责项目专属的编写规范
<ProjectRoot>/.behaviourDocs/bt-spec.md
)和行为树生成工作。固定的图规则和骨架存储在该技能的
references/
目录中;每个项目的规范由该技能本地的
scripts/build-spec.cjs
脚本(重新)构建。

🚦 Execution order (follow this sequence)

🚦 执行顺序(遵循此流程)

0. Build / refresh the project spec (
bt-spec.md
)

0. 构建/刷新项目规范(
bt-spec.md

The spec is the source of truth for every project-specific data point: each custom action/decorator node's
definitionId
,
btNodeType
, visible
propertyKey
names, and the serialized
Type.type
strings stamped to this project's
CoreVersion
.
When to (re)build:
  • First time working on BT in a project (no
    .behaviourDocs/bt-spec.md
    yet).
  • After any change that affects BT node surface area:
    • new / renamed / removed
      .codeblock
      whose paired
      .mlua
      extends
      ActionNode
      /
      DecoratorNode
    • added / removed / renamed
      property
      lines in such a
      .mlua
    • Environment/config
      CoreVersion
      bumped (the serialized type strings are version-tagged).
  • The user says they recently added/changed a BT codeblock or a
    .mlua
    property — stale UUIDs / missing properties silently produce broken trees.
  • The downstream validation (Step 7) flags a
    definitionId
    ,
    propertyKey
    , or version mismatch.
How to run — invoke this skill's local script:
bash
node "<SKILL_PATH>/scripts/build-spec.cjs" --projectRoot "<MSW project root>"
If the current working directory is already the MSW project root,
--projectRoot
can be omitted. Requires Node.js on
PATH
(no other dependencies — pure stdlib
fs
/
path
).
Optional overrides (long flags, case-insensitive):
FlagDefaultNotes
--projectRoot
current working directoryMSW project root to scan
--outputPath
<ProjectRoot>/.behaviourDocs/bt-spec.md
folder is created if missing
--coreVersion
read from
<ProjectRoot>/Environment/config
(
CoreVersion
field)
required if the config is missing
Example with overrides:
bash
node "<SKILL_PATH>/scripts/build-spec.cjs" --projectRoot "C:/path/to/project" --coreVersion 26.5.0.0
The script throws if
Environment/config
is absent and
--coreVersion
is not passed — there is no fallback default.
What the spec contains:
  1. Project metadata — project root,
    CoreVersion
    , generated time, discovered node counts.
  2. Built-in composite node names and their fixed
    definitionId
    /
    btNodeType
    .
  3. Custom action nodes —
    Name
    ,
    definitionId
    ,
    btNodeType
    , visible property names.
  4. Custom decorator nodes — same shape as action nodes.
  5. Type map — mlua type to serialized
    MODNativeType.type
    plus Blackboard
    ObjectValue
    shape.
UUIDs come from real
.codeblock
files in the project — the spec never invents them.
@HideFromInspector
properties are filtered out automatically. Fixed authoring rules, file skeletons, and validation checklists live in this skill's
references/
rather than in the generated spec.
After (re)building, read the freshly written
<ProjectRoot>/.behaviourDocs/bt-spec.md
and continue with the steps below. The compact spec intentionally lists only property names; when constructing
nodeProperties
, resolve each property's mlua type/default from the paired
.mlua
file, then use the type map in
bt-spec.md
§4 for
propertyType.type
.
Also read
references/skeleton-minimal.json
for the smallest valid tree,
references/skeleton-full.json
for a Composite+Decorator+Action+Blackboard example with all optional fields populated,
references/node-catalog.md
for fixed graph rules, and any existing
.behaviourtree
in the project (
**/*.behaviourtree
) to mirror conventions. Replace
{CORE_VERSION}
in the skeletons with the
CoreVersion
from
bt-spec.md
— both at the top level and inside every
MOD.Core.*
type string in Blackboard variables and
nodeProperties
.
该规范是所有项目专属数据的唯一可信来源:每个自定义动作/装饰器节点的
definitionId
btNodeType
、可见
propertyKey
名称,以及与项目
CoreVersion
绑定的序列化
Type.type
字符串。
何时(重新)构建:
  • 首次在项目中处理行为树(尚未创建
    .behaviourDocs/bt-spec.md
    )。
  • 任何影响行为树节点表面定义的变更之后:
    • 新增/重命名/删除配对
      .mlua
      文件继承自
      ActionNode
      /
      DecoratorNode
      .codeblock
    • 此类
      .mlua
      文件中新增/删除/重命名
      property
    • Environment/config
      中的
      CoreVersion
      升级(序列化类型字符串带有版本标记)。
  • 用户表示最近新增/修改了行为树代码块或
    .mlua
    属性——过期的UUID/缺失的属性会导致生成的行为树静默失效。
  • 下游验证(步骤7)标记了
    definitionId
    propertyKey
    或版本不匹配问题。
运行方式 — 调用该技能的本地脚本:
bash
node "<SKILL_PATH>/scripts/build-spec.cjs" --projectRoot "<MSW project root>"
如果当前工作目录已是MSW项目根目录,则可省略
--projectRoot
参数。要求Node.js已添加到
PATH
(无其他依赖——仅使用标准库
fs
/
path
)。
可选覆盖参数(长标记,大小写不敏感):
标记默认值说明
--projectRoot
当前工作目录要扫描的MSW项目根目录
--outputPath
<ProjectRoot>/.behaviourDocs/bt-spec.md
目标目录不存在时会自动创建
--coreVersion
<ProjectRoot>/Environment/config
CoreVersion
字段读取
当配置文件缺失时为必填项
带覆盖参数的示例:
bash
node "<SKILL_PATH>/scripts/build-spec.cjs" --projectRoot "C:/path/to/project" --coreVersion 26.5.0.0
如果
Environment/config
文件缺失且未传入
--coreVersion
参数,脚本会抛出错误——无 fallback 默认值。
规范包含内容:
  1. 项目元数据——项目根目录、
    CoreVersion
    、生成时间、已发现节点数量。
  2. 内置组合节点名称及其固定的
    definitionId
    /
    btNodeType
  3. 自定义动作节点——
    Name
    definitionId
    btNodeType
    、可见属性名称。
  4. 自定义装饰器节点——与动作节点结构相同。
  5. 类型映射——mlua类型到序列化
    MODNativeType.type
    以及Blackboard
    ObjectValue
    结构。
UUID来自项目中的真实
.codeblock
文件——规范绝不会自行生成UUID。
@HideFromInspector
属性会被自动过滤。固定的编写规则、文件骨架和验证清单存储在该技能的
references/
目录中,而非生成的规范内。
(重新)构建后,阅读新生成的
<ProjectRoot>/.behaviourDocs/bt-spec.md
并继续执行后续步骤。精简规范仅列出属性名称;构建
nodeProperties
时,需从配对的
.mlua
文件中解析每个属性的mlua类型/默认值,然后使用
bt-spec.md
第4节中的类型映射获取
propertyType.type
同时阅读
references/skeleton-minimal.json
了解最小有效行为树结构,阅读
references/skeleton-full.json
查看包含组合+装饰器+动作+Blackboard的完整示例(所有可选字段均已填充),阅读
references/node-catalog.md
了解固定图规则,以及项目中已有的
.behaviourtree
文件(
**/*.behaviourtree
)以遵循现有约定。将骨架中的
{CORE_VERSION}
替换为
bt-spec.md
中的
CoreVersion
——包括顶层字段以及Blackboard变量和
nodeProperties
中所有
MOD.Core.*
类型字符串内的版本标记。

1. Collect input from the user

1. 收集用户输入

Confirm via context, or ask via AskUserQuestion if anything is ambiguous:
ItemDescriptionExample
name
Display name for the tree
"PatrolAndChase"
Save path
.behaviourtree
location (relative to project root)
RootDesk/MyDesk/PatrolAndChase.behaviourtree
Tree shapeIntended node graph (root composite + children)
Sequence → [Chase, MoveTo]
Custom nodesAction/decorator codeblocks the tree references
Chase
,
MoveTo
,
Jump
Blackboard variablesVariable name + type + initial value
TargetEntity: Entity
,
MoveSpeed: number = 10.0
Node propertiesFor each custom node, which property maps to which Blackboard variable
Chase.TargetEntityKey = "TargetEntity"
Custom-node existence check (mandatory): every custom action/decorator name the user mentions must appear in
bt-spec.md
§2 / §3. If a referenced node is not in the spec, stop and ask the user — do not invent a UUID, do not assume a node exists by name, and do not skip rerunning Step 0.
通过上下文确认,若存在歧义则通过AskUserQuestion询问:
描述示例
name
行为树的显示名称
"PatrolAndChase"
保存路径
.behaviourtree
文件的存储位置(相对于项目根目录)
RootDesk/MyDesk/PatrolAndChase.behaviourtree
树结构预期的节点图(根组合节点+子节点)
Sequence → [Chase, MoveTo]
自定义节点行为树引用的动作/装饰器代码块
Chase
,
MoveTo
,
Jump
Blackboard变量变量名称+类型+初始值
TargetEntity: Entity
,
MoveSpeed: number = 10.0
节点属性每个自定义节点的属性与Blackboard变量的映射关系
Chase.TargetEntityKey = "TargetEntity"
自定义节点存在性检查(必填): 用户提及的每个自定义动作/装饰器名称必须出现在
bt-spec.md
的第2/3节中。若引用的节点未在规范中找到,立即停止并询问用户——不得自行生成UUID,不得假设节点按名称存在,不得跳过重新执行步骤0。

2. Mint UUIDs

2. 生成UUID

You need:
  • One UUID for the file → goes into
    EntryKey
    and
    ContentProto.Json.id
    (both identical, both prefixed
    behaviourtree://
    ).
  • One UUID for each node in
    Nodes
    (
    nodeId
    ).
bash
node -e "console.log(require('node:crypto').randomUUID())"
Mint up front, write into a scratch table, then assemble. Don't reuse the file UUID as a
nodeId
.
需要生成:
  • 一个文件UUID → 填入
    EntryKey
    ContentProto.Json.id
    (两者完全相同,均以
    behaviourtree://
    为前缀)。
  • Nodes
    中每个节点对应一个UUID(
    nodeId
    )。
bash
node -e "console.log(require('node:crypto').randomUUID())"
提前生成所有UUID,写入临时表后再进行组装。不得将文件UUID复用为
nodeId

3. Resolve every
definitionId

3. 解析所有
definitionId

Node category
definitionId
value
btNodeType
Built-in composite (
SequenceNode
,
SelectorNode
,
ParallelNode
)
Same string as
nodeName
1
Custom action nodevalue from
bt-spec.md
§2
0
Custom decorator nodevalue from
bt-spec.md
§3
2
Custom-node UUIDs come from
bt-spec.md
(which read them from real
.codeblock
files) — never any other source.
节点类别
definitionId
btNodeType
内置组合节点(
SequenceNode
SelectorNode
ParallelNode
nodeName
相同的字符串
1
自定义动作节点来自
bt-spec.md
第2节的值
0
自定义装饰器节点来自
bt-spec.md
第3节的值
2
自定义节点的UUID来自
bt-spec.md
(从真实
.codeblock
文件读取)——绝不能来自其他来源。

4. Build the Blackboard

4. 构建Blackboard

For each variable, copy the
Type.type
string and
ObjectValue
shape verbatim from
bt-spec.md
§4. The version-tagged substring (
Version=<CoreVersion>
) must match exactly — a typo silently breaks deserialization.
Variables
is an ordered array; each entry:
{ Name, Type: { "$type": "MODNativeType", type: "<from spec>" }, ObjectValue: <from spec> }
. The
ObjectValue
does not include a
$type
discriminator (unlike
Value
in
.model
files).
For
Component
/
ComponentRef
,
ComponentId
is
<entity-uuid>:<ComponentName>
(engine component) or
<entity-uuid>:<scriptCodeblockUuid>:<ScriptComponentName>
(script component). Mirror an existing serialized example in the project.
Numeric
ObjectValue
s use float literal form (
3.0
, not
3
).
对于每个变量,直接从
bt-spec.md
第4节复制
Type.type
字符串和
ObjectValue
结构。带版本标记的子串(
Version=<CoreVersion>
)必须完全匹配——拼写错误会导致反序列化静默失败。
Variables
是有序数组;每个条目格式:
{ Name, Type: { "$type": "MODNativeType", type: "<来自规范>" }, ObjectValue: <来自规范> }
ObjectValue
不包含
$type
鉴别符(与
.model
文件中的
Value
不同)。
对于
Component
/
ComponentRef
ComponentId
格式为
<entity-uuid>:<ComponentName>
(引擎组件)或
<entity-uuid>:<scriptCodeblockUuid>:<ScriptComponentName>
(脚本组件)。参考项目中已有的序列化示例。
数值型
ObjectValue
使用浮点数字面量(
3.0
,而非
3
)。

4.5 Resolve node property values

4.5 解析节点属性值

For each custom node that needs
nodeProperties
:
  1. Confirm the
    propertyKey
    exists in
    bt-spec.md
    §2 / §3 for that node.
  2. Find the paired
    .mlua
    by searching for
    script <NodeName> extends ActionNode
    or
    script <NodeName> extends DecoratorNode
    under the project. If multiple files match, prefer the one whose sibling
    .codeblock
    has the exact
    definitionId
    UUID from
    bt-spec.md
    ; if still ambiguous, ask the user.
  3. Read the visible
    property
    declarations in that
    .mlua
    , ignoring
    @HideFromInspector
    properties. This gives the mlua type and default value.
  4. Include a
    nodeProperties
    entry only when the user provided a value, the behavior requires a non-default value, or a
    *Key
    property must point at a Blackboard variable. Omit optional properties that can safely use the
    .mlua
    default.
  5. For
    *Key
    string properties, set
    propertyValue
    to the Blackboard variable name. Infer the variable by name and getter usage when obvious (
    MoveSpeedKey
    ->
    MoveSpeed
    ,
    TargetEntityKey
    ->
    TargetEntity
    ). If more than one Blackboard variable could match, ask.
  6. For literal properties, use the user-provided value. If no value is provided and the
    .mlua
    default is meaningful, omit the property instead of serializing a guessed value.
  7. If
    OnBehave
    checks a property for
    nil
    , empty string, or invalid enum and no value can be inferred, ask the user before writing the tree.
nodeProperties
entry shape:
json
{
  "propertyKey": "<property name>",
  "propertyType": { "$type": "MODNativeType", "type": "<type from bt-spec.md §4>" },
  "propertyValue": <value>
}
对于每个需要
nodeProperties
的自定义节点:
  1. 确认
    propertyKey
    存在于该节点对应的
    bt-spec.md
    第2/3节中。
  2. 通过搜索项目中包含
    script <NodeName> extends ActionNode
    script <NodeName> extends DecoratorNode
    的文件找到配对的
    .mlua
    。若存在多个匹配文件,优先选择其同级
    .codeblock
    definitionId
    bt-spec.md
    中UUID完全一致的文件;若仍存在歧义,询问用户。
  3. 读取该
    .mlua
    中的可见
    property
    声明,忽略
    @HideFromInspector
    属性。由此获取mlua类型和默认值。
  4. 仅当用户提供了值、行为需要非默认值,或
    *Key
    属性必须指向Blackboard变量时,才添加
    nodeProperties
    条目。可安全使用
    .mlua
    默认值的可选属性应省略。
  5. 对于
    *Key
    字符串属性,将
    propertyValue
    设置为Blackboard变量名称。当名称和 getter 使用明显匹配时(如
    MoveSpeedKey
    MoveSpeed
    TargetEntityKey
    TargetEntity
    ),自动推断变量。若存在多个可能匹配的Blackboard变量,询问用户。
  6. 对于字面量属性,使用用户提供的值。若未提供值且
    .mlua
    默认值有效,则省略该属性,而非序列化猜测的值。
  7. OnBehave
    检查属性是否为
    nil
    、空字符串或无效枚举,且无法推断值,则在写入行为树前询问用户。
nodeProperties
条目结构:
json
{
  "propertyKey": "<属性名称>",
  "propertyType": { "$type": "MODNativeType", "type": "<来自bt-spec.md第4节的类型>" },
  "propertyValue": <值>
}

5. Assemble Nodes

5. 组装Nodes

Hard graph constraints (validate before writing):
  • RootNode is not a parent node. It must not have
    childNodes
    . It only stores
    startNodeId
    , and
    startNodeId
    points to exactly one node in
    Nodes
    .
  • If the tree needs several top-level behaviors, use either one Composite as the single
    startNodeId
    , or one Decorator as the single
    startNodeId
    whose
    decoChildNodes
    wraps a Composite or another Decorator chain that eventually wraps a Composite. Put the multiple behaviors under that Composite's
    childNodes
    .
  • Exactly one node in
    Nodes
    may have
    nodeParentId: ""
    : the node referenced by
    RootNode.startNodeId
    . Do not create multiple root-level Action/Composite/Decorator nodes.
  • Composite (
    btNodeType: 1
    ) is the only node category that can own multiple children through
    childNodes
    .
  • Decorator (
    btNodeType: 2
    ) is only a wrapper/parent for exactly one Action, Composite, or Decorator node. It can also be the child of another Decorator, so Decorator-to-Decorator chains are valid. It must use singular
    decoChildNodes
    (a single
    nodeId
    string) for that one child, not
    childNodes
    ; the wrapped child must also record the Decorator's id in its
    nodeParentId
    .
  • Decorators applying to the same Action MUST be chained — never flattened as siblings. Each decorator owns exactly one downstream subtree. If two or more decorators are meant to gate/modify the same Action, build a single chain
    Composite → ADeco → BDeco → CDeco → Action
    where each decorator's
    decoChildNodes
    points to the next decorator (and finally the Action). Concretely: within one chain leading to a single Action, no two decorators may share the same
    nodeParentId
    — each decorator's parent is the previous decorator, and only the topmost decorator's parent is the Composite. Sibling decorators under one Composite are still valid when each wraps a different downstream subtree. ✅
    Composite → ADeco → BDeco → CDeco → Action
    (chain — every decorator has a unique parent within the chain). ❌
    Composite → [ADeco→Action, BDeco→Action, CDeco→Action]
    (Action duplicated to bypass chaining). ❌
    Composite → [ADeco, BDeco, CDeco, Action]
    (decorators flattened — they don't wrap the Action and are effectively orphaned).
  • Action (
    btNodeType: 0
    ) is a leaf — never has children.
Node-write invariants:
  • Every
    nodeId
    is unique within the file.
  • nodeParentId
    of every non-root node points to a real
    nodeId
    that is a Composite or Decorator. It must never point to
    RootNode
    , because
    RootNode
    is not represented as a node in
    Nodes
    .
  • If a node's parent is a Composite, that Composite must include the node id in
    childNodes
    .
  • If a node's parent is a Decorator, that Decorator's
    decoChildNodes
    must equal that node's
    nodeId
    . This is valid even when both parent and child are Decorators.
  • Composite
    childNodes
    ↔ child
    nodeParentId
    is bidirectionally consistent.
  • Action nodes omit
    childNodes
    . Decorator nodes omit
    childNodes
    and use exactly one
    decoChildNodes
    (single string
    nodeId
    ) instead.
  • Never write
    probability
    .
    The editor strips this field on round-trip, and the supported composites (
    SequenceNode
    ,
    SelectorNode
    ,
    ParallelNode
    ) do not consume per-child weights. Older generated trees in the project may still carry
    "probability": 1.0
    on every node; treat that as legacy on read but do not write it on new nodes.
  • Decorator nodes (
    btNodeType: 2
    ) omit
    nodePosition
    .
    The editor positions a Decorator automatically relative to the child it wraps, and writes no
    nodePosition
    field for it on save. Only Composites and Actions carry
    nodePosition
    . The
    RootNode
    block also carries its own
    nodePosition
    (separate from the start node).
  • Empty collection fields are omitted, not serialized as
    []
    .
    A Composite with no children yet should omit
    childNodes
    entirely; a node with no overrides should omit
    nodeProperties
    entirely. Empty arrays are an editor-draft artifact — do not author them.
  • Decorator child field is
    decoChildNodes
    (canonical — this is what the editor preserves on save;
    ChildNodeId
    is silently stripped on round-trip). It is a single string holding the wrapped child's
    nodeId
    (not an array). When reading legacy files you may still encounter
    ChildNodeId
    on hand-authored decorators; treat it as the same field. When writing, always emit
    decoChildNodes
    .
  • RootNode.startNodeId
    references one of the
    nodeId
    s — an Action, Composite, or Decorator — and that node is the only node with
    nodeParentId: ""
    .
*Key
-suffix String properties carry the name of a Blackboard variable (resolved at runtime via
BlackBoard:GetXxx
). Non-
Key
properties carry the literal value.
严格的图约束(写入前必须验证):
  • RootNode不是父节点。它不得包含
    childNodes
    。仅存储
    startNodeId
    ,且
    startNodeId
    指向
    Nodes
    恰好一个节点。
  • 若行为树需要多个顶层行为,可将单个组合节点作为唯一的
    startNodeId
    ,或将单个装饰器作为唯一的
    startNodeId
    ,其
    decoChildNodes
    包裹一个组合节点或另一个装饰器链(最终包裹组合节点)。将多个行为置于该组合节点的
    childNodes
    下。
  • Nodes
    中恰好有一个节点的
    nodeParentId: ""
    :即
    RootNode.startNodeId
    引用的节点。不得创建多个顶层动作/组合/装饰器节点。
  • 组合节点
    btNodeType: 1
    )是唯一可通过
    childNodes
    拥有多个子节点的节点类别。
  • 装饰器节点
    btNodeType: 2
    )仅能作为恰好一个动作、组合或装饰器节点的包装器/父节点。它也可以是另一个装饰器的子节点,因此装饰器到装饰器的链是有效的。必须使用单数形式的
    decoChildNodes
    (单个
    nodeId
    字符串)指向该子节点,而非
    childNodes
    ;被包裹的子节点必须将装饰器的ID记录在其
    nodeParentId
    中。
  • 应用于同一动作的装饰器必须链式排列——绝不能平级作为兄弟节点。每个装饰器仅拥有一个下游子树。若两个或多个装饰器用于控制/修改同一动作,需构建单个链
    Composite → ADeco → BDeco → CDeco → Action
    ,其中每个装饰器的
    decoChildNodes
    指向下一个装饰器(最终指向动作)。具体规则:在指向单个动作的链中,任意两个装饰器不得拥有相同的
    nodeParentId
    ——每个装饰器的父节点是前一个装饰器,只有最顶层的装饰器父节点是组合节点。当每个装饰器包裹不同的下游子树时,组合节点下的兄弟装饰器仍然有效。✅
    Composite → ADeco → BDeco → CDeco → Action
    (链式结构——链中每个装饰器的父节点唯一)。❌
    Composite → [ADeco→Action, BDeco→Action, CDeco→Action]
    (动作重复以绕过链式结构)。❌
    Composite → [ADeco, BDeco, CDeco, Action]
    (装饰器平级排列——未包裹动作,相当于孤立节点)。
  • 动作节点
    btNodeType: 0
    )是叶子节点——绝不能有子节点。
节点写入规则:
  • 文件内每个
    nodeId
    必须唯一。
  • 所有非根节点的
    nodeParentId
    必须指向真实的组合或装饰器节点的
    nodeId
    。绝不能指向
    RootNode
    ,因为
    RootNode
    未在
    Nodes
    中作为节点表示。
  • 若节点的父节点是组合节点,该组合节点必须将该节点ID包含在
    childNodes
    中。
  • 若节点的父节点是装饰器节点,该装饰器的
    decoChildNodes
    必须等于该节点的
    nodeId
    。即使父节点和子节点均为装饰器,此规则依然有效。
  • 组合节点的
    childNodes
    ↔ 子节点的
    nodeParentId
    必须双向一致
  • 动作节点省略
    childNodes
    。装饰器节点省略
    childNodes
    ,并使用恰好一个
    decoChildNodes
    (单个
    nodeId
    字符串)替代。
  • 绝不能写入
    probability
    字段
    。编辑器在往返序列化时会移除该字段,且支持的组合节点(
    SequenceNode
    SelectorNode
    ParallelNode
    )不使用子节点权重。项目中旧的生成树可能仍在每个节点上带有
    "probability": 1.0
    ;读取时视为遗留字段,但写入新节点时不得包含。
  • 装饰器节点(
    btNodeType: 2
    )省略
    nodePosition
    。编辑器会自动根据其包裹的子节点定位装饰器,保存时不会为其写入
    nodePosition
    字段。仅组合节点和动作节点带有
    nodePosition
    RootNode
    块也带有自己的
    nodePosition
    (与起始节点分离)。
  • 空集合字段应省略,而非序列化为
    []
    。尚无子节点的组合节点应完全省略
    childNodes
    ;无覆盖属性的节点应完全省略
    nodeProperties
    。空数组是编辑器草稿阶段的产物——编写时不得包含。
  • 装饰器子节点字段为
    decoChildNodes
    (标准格式——编辑器保存时会保留此格式;
    ChildNodeId
    在往返序列化时会被静默移除)。它是一个存储被包裹子节点
    nodeId
    的单个字符串(而非数组)。读取遗留文件时可能仍会在手动编写的装饰器上遇到
    ChildNodeId
    ;视为同一字段处理。写入时,始终输出
    decoChildNodes
  • RootNode.startNodeId
    引用其中一个
    nodeId
    ——动作、组合或装饰器节点——且该节点是唯一拥有
    nodeParentId: ""
    的节点。
后缀为
*Key
的字符串属性存储Blackboard变量的名称(运行时通过
BlackBoard:GetXxx
解析)。非
Key
属性存储字面量值。

6. nodePosition format

6. nodePosition格式

nodePosition
is a JSON object with numeric
x
/
y
:
json
"nodePosition": { "x": 0.0, "y": 0.0 }
Use float literals (
0.0
, not
0
). The legacy string form
"(0.000, 0.000)"
may still appear in older hand-authored trees — read it as equivalent, but always write the object form (the BT editor canonicalizes to this shape on save, so the string form re-serializes to a noisy diff the first time the file is opened).
Editor axes: the BT editor uses a math-convention canvas — +x is right, +y is up (upper-right quadrant is positive). So a child placed at a higher y than its parent appears above the parent on screen.
Layout rule — draw the tree downward: depth grows along −y (children sit below their parent), and siblings spread along ±x around the parent's x. Typical spacing: 200 units between depth levels and 200 units between siblings.
RootNode
block vs the start node — do not stack them at the same position.
RootNode.nodePosition
is the canvas anchor and stays at
{ "x": 0.0, "y": 0.0 }
. The start node (the node referenced by
startNodeId
) must sit one level below that anchor — putting it at
(0, 0)
makes it visually overlap the RootNode marker on the editor canvas. Treat the RootNode anchor as depth 0 and the start node as depth 1.
  • RootNode.nodePosition
    :
    { "x": 0.0, "y": 0.0 }
    (fixed anchor — never moves)
  • Start node (depth 1, referenced by
    startNodeId
    ):
    { "x": 0.0, "y": -200.0 }
  • Single child of the start node (depth 2):
    { "x": 0.0, "y": -400.0 }
  • Two children of the start node (depth 2):
    { "x": -100.0, "y": -400.0 }
    and
    { "x": 100.0, "y": -400.0 }
  • Each additional level: parent.y − 200
Never place a child at a y greater than or equal to its parent's y — that draws upward and overlaps the parent visually. The same rule applies between
RootNode
and the start node: the start node must be at
y ≤ -200
(strictly below the anchor).
Decorator nodes do not carry
nodePosition
.
The editor lays them out automatically relative to the wrapped child. Omit the field on every
btNodeType: 2
node; it appears only on
RootNode
, Composites (
btNodeType: 1
), and Actions (
btNodeType: 0
).
nodePosition
是一个包含数值型
x
/
y
JSON对象
json
"nodePosition": { "x": 0.0, "y": 0.0 }
使用浮点数字面量(
0.0
,而非
0
)。旧的手动编写树中可能仍存在遗留字符串格式
"(0.000, 0.000)"
;读取时视为等效,但写入时始终使用对象格式(行为树编辑器保存时会规范化为此格式,因此字符串格式首次打开文件时会产生无意义的差异)。
编辑器坐标轴:行为树编辑器使用数学规范画布——+x向右,+y向上(右上象限为正)。因此,子节点的y值高于父节点时,在屏幕上显示为位于父节点上方
布局规则——向下绘制树:深度沿 −y 方向增长(子节点位于父节点下方),兄弟节点沿 ±x 方向围绕父节点x轴分布。典型间距:深度层级间200单位,兄弟节点间200单位。
RootNode
块与起始节点——不得重叠放置
RootNode.nodePosition
是画布锚点,固定为
{ "x": 0.0, "y": 0.0 }
。起始节点(
startNodeId
引用的节点)必须位于锚点下方一层——若置于
(0, 0)
会在编辑器画布上与RootNode标记视觉重叠。将RootNode锚点视为深度0,起始节点视为深度1。
  • RootNode.nodePosition
    { "x": 0.0, "y": 0.0 }
    (固定锚点——绝不移动)
  • 起始节点(深度1,
    startNodeId
    引用):
    { "x": 0.0, "y": -200.0 }
  • 起始节点的单个子节点(深度2):
    { "x": 0.0, "y": -400.0 }
  • 起始节点的两个子节点(深度2):
    { "x": -100.0, "y": -400.0 }
    { "x": 100.0, "y": -400.0 }
  • 每增加一层:parent.y − 200
绝不能将子节点的y值设置为大于或等于父节点的y值——这会导致向上绘制并与父节点视觉重叠。此规则同样适用于
RootNode
和起始节点:起始节点的y值必须 ≤ -200.0(严格位于锚点下方)。
装饰器节点不携带
nodePosition
。编辑器会自动根据其包裹的子节点进行布局。所有
btNodeType: 2
节点均省略该字段;仅
RootNode
、组合节点(
btNodeType: 1
)和动作节点(
btNodeType: 0
)携带该字段。

7. Write and validate

7. 写入与验证

Write the JSON file, then run this checklist. In particular:
  • EntryKey
    is
    behaviourtree://{uuid}
    and matches
    ContentProto.Json.id
    exactly.
  • Top-level
    Id
    ,
    GameId
    ,
    Content
    are
    ""
    .
    Usage
    ,
    UseService
    ,
    DynamicLoading
    are
    0
    .
    UsePublish
    is
    1
    .
    CoreVersion
    matches the project (
    Environment/config
    ).
    StudioVersion
    is
    0.1.0.0
    .
    ContentType
    is
    x-mod/behaviourtree
    .
    ContentProto.Use
    is
    Json
    .
  • RootNode
    has no
    childNodes
    ;
    RootNode.startNodeId
    matches exactly one
    nodeId
    in
    Nodes
    ; that start node has
    nodeParentId: ""
    ; and no other node has
    nodeParentId: ""
    .
  • Every
    nodeParentId
    is
    ""
    or an existing
    nodeId
    .
  • All
    nodeId
    values are unique.
  • For every Composite, the set of
    childNodes
    IDs equals the set of nodes whose
    nodeParentId
    is this Composite.
  • Every Action has no
    childNodes
    . Every Decorator has no
    childNodes
    , has exactly one
    decoChildNodes
    (single
    nodeId
    string —
    ChildNodeId
    is the legacy variant; the editor strips it on round-trip), and that id points to exactly one Action, Composite, or Decorator child whose
    nodeParentId
    points back to the Decorator. Decorator-to-Decorator parent/child chains are valid and must be checked with the same
    decoChildNodes
    nodeParentId
    rule.
  • Decorator chain rule: when multiple decorators apply to the same Action, they form a single chain (
    Composite → ADeco → BDeco → … → Action
    ). Verify by walking each Action upward to its enclosing Composite: the decorators encountered along that one path must all have unique
    nodeParentId
    values (i.e. each decorator's parent is the previous decorator, never another decorator that already appeared in the chain). Two decorators in the same chain sharing a
    nodeParentId
    is invalid. (Sibling decorators under one Composite that wrap different downstream subtrees are fine — uniqueness is per-chain, not global.)
  • No node serializes
    "probability"
    . (Legacy
    1.0
    values may appear on read but are never authored.)
  • Every Composite and Action carries
    nodePosition
    in object form
    { "x": <num>, "y": <num> }
    with float literals — no legacy
    "(x.xxx, y.yyy)"
    strings on write. Decorator nodes carry no
    nodePosition
    at all.
  • Start node is not stacked on the RootNode anchor.
    RootNode.nodePosition
    is
    { "x": 0.0, "y": 0.0 }
    and the node referenced by
    startNodeId
    has
    y ≤ -200.0
    (typically
    { "x": 0.0, "y": -200.0 }
    ). If the start node is a Decorator (no
    nodePosition
    ), the first wrapped Composite/Action down the chain must satisfy this offset instead.
  • No node serializes empty arrays — a Composite with no children omits
    childNodes
    ; a node with no overrides omits
    nodeProperties
    . Do not write
    "childNodes": []
    or
    "nodeProperties": []
    .
  • Every custom node's
    definitionId
    is copied from
    bt-spec.md
    (never invented).
  • Every
    nodeProperties[].propertyKey
    matches a property in
    bt-spec.md
    for that node.
  • Every
    *Key
    property's
    propertyValue
    matches a
    Blackboard.Variables[].Name
    of the right type.
  • Every type string is copied verbatim from
    bt-spec.md
    §4 — version-tagged, typo-fragile.
  • Version cross-check: every
    MOD.Core.*
    type string's
    Version=X.Y.Z.Z
    substring (in
    Blackboard.Variables[].Type.type
    and
    Nodes[].nodeProperties[].propertyType.type
    ) equals the file's top-level
    CoreVersion
    . Mismatch silently breaks deserialization — common when
    bt-spec.md
    is stale relative to the project's current
    CoreVersion
    . If they differ, re-run Step 0 before writing. (
    System.*
    types use the immutable
    Version=4.0.0.0
    and are exempt.)
  • JSON parses:
    bash
    node -e "JSON.parse(require('node:fs').readFileSync(process.argv[1],'utf8'))" "<path>"
If any check fails, fix it before reporting done.

写入JSON文件后,执行以下检查清单。重点检查:
  • EntryKey
    格式为
    behaviourtree://{uuid}
    且与
    ContentProto.Json.id
    完全匹配。
  • 顶层字段
    Id
    GameId
    Content
    均为
    ""
    Usage
    UseService
    DynamicLoading
    均为
    0
    UsePublish
    1
    CoreVersion
    与项目(
    Environment/config
    )一致。
    StudioVersion
    0.1.0.0
    ContentType
    x-mod/behaviourtree
    ContentProto.Use
    Json
  • RootNode
    childNodes
    RootNode.startNodeId
    Nodes
    中恰好一个
    nodeId
    匹配;该起始节点的
    nodeParentId: ""
    ;且无其他节点的
    nodeParentId: ""
  • 所有
    nodeParentId
    要么为
    ""
    ,要么指向已存在的
    nodeId
  • 所有
    nodeId
    值唯一。
  • 每个组合节点的
    childNodes
    ID集合与
    nodeParentId
    指向该组合节点的节点集合完全一致。
  • 所有动作节点无
    childNodes
    。所有装饰器节点无
    childNodes
    ,且恰好有一个
    decoChildNodes
    (单个
    nodeId
    字符串——
    ChildNodeId
    是遗留变体;编辑器往返序列化时会移除),且该ID指向恰好一个动作、组合或装饰器子节点,其
    nodeParentId
    指向该装饰器。装饰器到装饰器的父/子链有效,必须使用相同的
    decoChildNodes
    nodeParentId
    规则进行检查。
  • 装饰器链规则:当多个装饰器应用于同一动作时,必须形成单个链(
    Composite → ADeco → BDeco → … → Action
    )。通过从每个动作向上遍历至其所属的组合节点进行验证:该路径上遇到的所有装饰器必须拥有唯一
    nodeParentId
    值(即每个装饰器的父节点是前一个装饰器,而非链中已出现的其他装饰器)。同一链中两个装饰器拥有相同的
    nodeParentId
    是无效的。(组合节点下包裹不同下游子树的兄弟装饰器是允许的——唯一性是针对单条链,而非全局。)
  • 无节点序列化
    "probability"
    字段。(读取时可能会遇到遗留的
    1.0
    值,但编写时绝不包含。)
  • 所有组合节点和动作节点均携带对象格式的
    nodePosition
    { "x": <数值>, "y": <数值> }
    ,且使用浮点数字面量——写入时不得使用遗留的
    "(x.xxx, y.yyy)"
    字符串。装饰器节点完全不携带
    nodePosition
  • 起始节点未与RootNode锚点重叠
    RootNode.nodePosition
    { "x": 0.0, "y": 0.0 }
    startNodeId
    引用的节点的y值 ≤ -200.0(通常为
    { "x": 0.0, "y": -200.0 }
    )。若起始节点是装饰器(无
    nodePosition
    ),则链中第一个被包裹的组合/动作节点必须满足此偏移要求。
  • 无节点序列化空数组——无子女的组合节点省略
    childNodes
    ;无覆盖属性的节点省略
    nodeProperties
    。不得写入
    "childNodes": []
    "nodeProperties": []
  • 所有自定义节点的
    definitionId
    均从
    bt-spec.md
    复制(绝不自行生成)。
  • 所有
    nodeProperties[].propertyKey
    均与该节点在
    bt-spec.md
    中的属性匹配。
  • 所有
    *Key
    属性的
    propertyValue
    均与对应类型的
    Blackboard.Variables[].Name
    匹配。
  • 所有类型字符串均直接从
    bt-spec.md
    第4节复制——带有版本标记,对拼写错误敏感。
  • 版本交叉检查:所有
    MOD.Core.*
    类型字符串中的
    Version=X.Y.Z.Z
    子串(位于
    Blackboard.Variables[].Type.type
    Nodes[].nodeProperties[].propertyType.type
    中)与文件顶层的
    CoreVersion
    完全一致。版本不匹配会导致反序列化静默失败——常见于
    bt-spec.md
    相对于项目当前
    CoreVersion
    过期的情况。若版本不一致,写入前重新执行步骤0。(
    System.*
    类型使用固定的
    Version=4.0.0.0
    ,不受此规则限制。)
  • JSON可正常解析:
    bash
    node -e "JSON.parse(require('node:fs').readFileSync(process.argv[1],'utf8'))" "<路径>"
若任何检查未通过,修复后再报告完成。

📂 Files in / consumed by this skill

📂 该技能包含/使用的文件

  • scripts/build-spec.cjs
    — Node.js script that scans the project and emits
    <ProjectRoot>/.behaviourDocs/bt-spec.md
    . Invoked in Step 0.
  • <ProjectRoot>/.behaviourDocs/bt-spec.md
    — compact generated catalog. Source of truth for node names,
    definitionId
    ,
    btNodeType
    , property names, and type strings. Written by the script above; consumed by Steps 1–7.
  • references/skeleton-minimal.json
    — smallest valid tree (empty Blackboard, single Composite root with no children).
  • references/skeleton-full.json
    — Composite → Decorator → Action with
    nodeProperties
    (literal +
    Key
    -suffix) and a populated
    Blackboard
    . Use this as the shape reference whenever the tree is non-trivial.
  • references/node-catalog.md
    — narrative explanation of
    btNodeType
    values, valid graph shapes, the
    Key
    -suffix convention, and how the spec builder discovers nodes (kept for reference; the runtime catalog itself lives in
    bt-spec.md
    ).

  • scripts/build-spec.cjs
    — Node.js脚本,扫描项目并生成
    <ProjectRoot>/.behaviourDocs/bt-spec.md
    。在步骤0中调用。
  • <ProjectRoot>/.behaviourDocs/bt-spec.md
    — 精简的生成目录。节点名称、
    definitionId
    btNodeType
    、属性名称和类型字符串的唯一可信来源。由上述脚本生成;供步骤1–7使用。
  • references/skeleton-minimal.json
    — 最小有效行为树(空Blackboard,无子女的单个组合根节点)。
  • references/skeleton-full.json
    — 包含组合→装饰器→动作结构,带有
    nodeProperties
    (字面量+后缀为Key的属性)和已填充的
    Blackboard
    。当行为树非极简时,以此作为结构参考。
  • references/node-catalog.md
    — 关于
    btNodeType
    值、有效图结构、Key后缀约定以及规范构建器如何发现节点的说明文档(仅作参考;运行时目录本身存储在
    bt-spec.md
    中)。

🔁 Edit workflow (existing file)

🔁 编辑工作流(现有文件)

  1. Read the entire file — never Edit blind. UUIDs and the parent/child graph must stay consistent.
  2. Never change the file's wrapper UUID (
    EntryKey
    /
    ContentProto.Json.id
    ) — external references break.
  3. Adding a node: mint a fresh
    nodeId
    , append to
    Nodes
    , update the parent Composite's
    childNodes
    , set the new node's
    nodeParentId
    .
  4. Removing a node: remove from
    Nodes
    , remove its ID from any Composite's
    childNodes
    . If it was a Composite, decide whether to re-parent or remove its children — never leave dangling
    nodeParentId
    references.
  5. If the edit involves a custom node name, property, or type that may have changed in the project since the spec was last built, re-run Step 0 first.
  6. Re-run the Step 7 validation checklist after every edit.
  1. 读取整个文件——绝不盲目编辑。UUID和父/子图必须保持一致。
  2. 绝不能修改文件的包装UUID(
    EntryKey
    /
    ContentProto.Json.id
    )——否则会破坏外部引用。
  3. 添加节点:生成新的
    nodeId
    ,追加到
    Nodes
    ,更新父组合节点的
    childNodes
    ,设置新节点的
    nodeParentId
  4. 删除节点:从
    Nodes
    中移除,从任何组合节点的
    childNodes
    中移除其ID。若删除的是组合节点,需决定是重新父化还是移除其子节点——绝不能留下悬空的
    nodeParentId
    引用。
  5. 若编辑涉及自定义节点名称、属性或类型,且自上次构建规范后项目中可能发生了变更,先重新执行步骤0
  6. 每次编辑后重新执行步骤7的验证检查清单。